import * as cron from 'node-cron';
import { createModuleLogger } from '../../utils/index.js';
import type { DataStore } from '../data-store/index.js';

const log = createModuleLogger('channel-manager');

export interface ChannelConfig {
  characterId: string;
  normalChannel?: {
    chatId: number;
    title: string;
    inviteLink?: string;
    memberCount: number;
  };
  vipChannel?: {
    chatId: number;
    title: string;
    inviteLink?: string;
    memberCount: number;
    starPrice: number;
  };
  publishSchedule?: string; // cron expression
  createdAt: string;
  updatedAt: string;
}

export interface TelegramBotClient {
  createChannel(title: string, description?: string): Promise<{ chatId: number; inviteLink?: string }>;
  sendMessage(chatId: number, text: string): Promise<void>;
  sendPhoto(chatId: number, photo: string, caption?: string): Promise<void>;
  sendPaidMedia(params: {
    chat_id: number;
    star_count: number;
    media: Array<{ type: 'photo'; media: string }>;
    caption?: string;
  }): Promise<void>;
  getChatMemberCount(chatId: number): Promise<number>;
}

type BotClientGetter = (characterId: string) => TelegramBotClient | undefined;

export class ChannelManager {
  private store: DataStore;
  private getBotClient: BotClientGetter;
  private configs: Map<string, ChannelConfig> = new Map();
  private cronJobs: Map<string, cron.ScheduledTask> = new Map();

  constructor(store: DataStore, getBotClient: BotClientGetter) {
    this.store = store;
    this.getBotClient = getBotClient;
  }

  async createChannels(
    characterId: string,
    botClient: TelegramBotClient,
    normalTitle: string,
    vipTitle: string,
  ): Promise<ChannelConfig> {
    log.info({ characterId }, 'Creating normal and VIP channels');

    const normalResult = await botClient.createChannel(normalTitle, 'Public promotional content');
    const vipResult = await botClient.createChannel(vipTitle, 'VIP exclusive content — Stars access');

    const now = new Date().toISOString();
    const config: ChannelConfig = {
      characterId,
      normalChannel: {
        chatId: normalResult.chatId,
        title: normalTitle,
        inviteLink: normalResult.inviteLink,
        memberCount: 0,
      },
      vipChannel: {
        chatId: vipResult.chatId,
        title: vipTitle,
        inviteLink: vipResult.inviteLink,
        memberCount: 0,
        starPrice: 50,
      },
      createdAt: now,
      updatedAt: now,
    };

    this.configs.set(characterId, config);
    await this.saveConfig(characterId, config);
    log.info({ characterId, normalChatId: normalResult.chatId, vipChatId: vipResult.chatId }, 'Channels created');
    return config;
  }

  async getChannelConfig(characterId: string): Promise<ChannelConfig | null> {
    const cached = this.configs.get(characterId);
    if (cached) return cached;

    const stored = await this.loadConfig(characterId);
    if (stored) {
      this.configs.set(characterId, stored);
    }
    return stored;
  }

  async updateChannelConfig(characterId: string, updates: Partial<ChannelConfig>): Promise<ChannelConfig> {
    const existing = await this.getChannelConfig(characterId);
    if (!existing) {
      throw new Error(`Channel config not found for character ${characterId}`);
    }

    const updated: ChannelConfig = {
      ...existing,
      ...updates,
      characterId: existing.characterId,
      createdAt: existing.createdAt,
      updatedAt: new Date().toISOString(),
    };

    if (updates.normalChannel) {
      updated.normalChannel = { ...existing.normalChannel!, ...updates.normalChannel };
    }
    if (updates.vipChannel) {
      updated.vipChannel = { ...existing.vipChannel!, ...updates.vipChannel };
    }

    this.configs.set(characterId, updated);
    await this.saveConfig(characterId, updated);

    // Reschedule cron if publishSchedule changed
    if (updates.publishSchedule !== undefined) {
      this.stopSchedule(characterId);
      if (updates.publishSchedule) {
        this.startSchedule(characterId, updates.publishSchedule);
      }
    }

    log.info({ characterId }, 'Channel config updated');
    return updated;
  }

  async publishToNormal(characterId: string, content: { text?: string; photoUrl?: string; caption?: string }): Promise<void> {
    const config = await this.getChannelConfig(characterId);
    if (!config?.normalChannel) {
      throw new Error(`Normal channel not configured for character ${characterId}`);
    }

    const botClient = this.getBotClient(characterId);
    if (!botClient) {
      throw new Error(`Bot client not available for character ${characterId}`);
    }

    const chatId = config.normalChannel.chatId;

    if (content.photoUrl) {
      await botClient.sendPhoto(chatId, content.photoUrl, content.caption);
    } else if (content.text) {
      await botClient.sendMessage(chatId, content.text);
    }

    log.info({ characterId, chatId }, 'Published to normal channel');
  }

  async publishToVIP(characterId: string, imageUrl: string, starCount: number, caption?: string): Promise<void> {
    const config = await this.getChannelConfig(characterId);
    if (!config?.vipChannel) {
      throw new Error(`VIP channel not configured for character ${characterId}`);
    }

    const botClient = this.getBotClient(characterId);
    if (!botClient) {
      throw new Error(`Bot client not available for character ${characterId}`);
    }

    const effectiveStarCount = starCount || config.vipChannel.starPrice;

    await botClient.sendPaidMedia({
      chat_id: config.vipChannel.chatId,
      star_count: effectiveStarCount,
      media: [{ type: 'photo', media: imageUrl }],
      caption,
    });

    log.info({ characterId, chatId: config.vipChannel.chatId, starCount: effectiveStarCount }, 'Published paid media to VIP channel');
  }

  async getStats(characterId: string): Promise<{ normalMembers: number; vipMembers: number } | null> {
    const config = await this.getChannelConfig(characterId);
    if (!config) return null;

    const botClient = this.getBotClient(characterId);
    if (!botClient) return null;

    let normalMembers = config.normalChannel?.memberCount ?? 0;
    let vipMembers = config.vipChannel?.memberCount ?? 0;

    try {
      if (config.normalChannel) {
        normalMembers = await botClient.getChatMemberCount(config.normalChannel.chatId);
        config.normalChannel.memberCount = normalMembers;
      }
      if (config.vipChannel) {
        vipMembers = await botClient.getChatMemberCount(config.vipChannel.chatId);
        config.vipChannel.memberCount = vipMembers;
      }
      config.updatedAt = new Date().toISOString();
      this.configs.set(characterId, config);
      await this.saveConfig(characterId, config);
    } catch (err) {
      log.warn({ characterId, err }, 'Failed to refresh member counts from Telegram');
    }

    return { normalMembers, vipMembers };
  }

  // --- Scheduling ---

  startSchedule(characterId: string, cronExpression: string): void {
    if (!cron.validate(cronExpression)) {
      throw new Error(`Invalid cron expression: ${cronExpression}`);
    }

    this.stopSchedule(characterId);

    const task = cron.schedule(cronExpression, () => {
      log.info({ characterId, cronExpression }, 'Scheduled publish triggered');
      // The actual publish logic is delegated to the automation layer
      // This emits a log entry; the orchestrator listens and calls publishToNormal/VIP
    });

    this.cronJobs.set(characterId, task);
    log.info({ characterId, cronExpression }, 'Publish schedule started');
  }

  stopSchedule(characterId: string): void {
    const existing = this.cronJobs.get(characterId);
    if (existing) {
      existing.stop();
      this.cronJobs.delete(characterId);
      log.info({ characterId }, 'Publish schedule stopped');
    }
  }

  stopAllSchedules(): void {
    for (const [characterId, task] of this.cronJobs) {
      task.stop();
      log.info({ characterId }, 'Publish schedule stopped');
    }
    this.cronJobs.clear();
  }

  // --- Persistence helpers ---

  private async saveConfig(characterId: string, config: ChannelConfig): Promise<void> {
    await this.store.saveCharacterChannelConfig(characterId, config);
  }

  private async loadConfig(characterId: string): Promise<ChannelConfig | null> {
    return this.store.getCharacterChannelConfig(characterId);
  }
}
