import { createModuleLogger } from '../../utils/logger.js';
import { readJSON, atomicWriteJSON, ensureDir } from '../data-store/file-ops.js';
import { config } from '../../utils/config.js';
import { resolve } from 'path';
import { UserbotClient, type MessageHandler } from './client.js';
import type { DataStore } from '../data-store/index.js';

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

interface UserbotSession {
  characterId: string;
  phoneNumber: string;
  session: string;
  active: boolean;
  createdAt: string;
}

export class UserbotManager {
  private clients = new Map<string, UserbotClient>();
  private sessionsPath: string;
  private messageHandler?: MessageHandler;
  private dataStore?: DataStore;

  constructor(dataStore?: DataStore) {
    this.sessionsPath = resolve(config.dataDir, 'config', 'userbot-sessions.json');
    this.dataStore = dataStore;
  }

  setMessageHandler(handler: MessageHandler): void {
    this.messageHandler = handler;
    // Update existing clients
    for (const client of this.clients.values()) {
      client.onMessage(handler);
    }
  }

  /**
   * Login a new userbot. Requires interactive code input.
   * Returns session string for persistence.
   * Uses character-based apiCredentials from CharacterDNA when available.
   */
  async login(
    characterId: string,
    phoneNumber: string,
    codeCallback: () => Promise<string>,
    passwordCallback?: () => Promise<string>,
  ): Promise<{ sessionString: string }> {
    log.info({ characterId, phoneNumber }, 'Logging in userbot');

    // Load character-based API credentials
    const { telegramApiId, telegramApiHash } = await this.getCharacterCredentials(characterId);

    const client = new UserbotClient({ characterId, phoneNumber, telegramApiId, telegramApiHash });
    if (this.messageHandler) {
      client.onMessage(this.messageHandler);
    }

    const sessionString = await client.start(codeCallback, passwordCallback);

    // Save session
    const sessions = await this.loadSessions();
    const existing = sessions.findIndex(s => s.characterId === characterId);
    const entry: UserbotSession = {
      characterId,
      phoneNumber,
      session: sessionString,
      active: true,
      createdAt: new Date().toISOString(),
    };
    if (existing >= 0) {
      sessions[existing] = entry;
    } else {
      sessions.push(entry);
    }
    await this.saveSessions(sessions);

    this.clients.set(characterId, client);
    log.info({ characterId }, 'Userbot logged in and session saved');

    return { sessionString };
  }

  /**
   * Reconnect all saved sessions on startup
   */
  async reconnectAll(): Promise<void> {
    const sessions = await this.loadSessions();
    const activeSessions = sessions.filter(s => s.active);

    log.info({ count: activeSessions.length }, 'Reconnecting saved userbot sessions');

    for (const sess of activeSessions) {
      try {
        // Load character-based API credentials (fall back to global config if unavailable)
        let telegramApiId: number | undefined;
        let telegramApiHash: string | undefined;
        try {
          const creds = await this.getCharacterCredentials(sess.characterId);
          telegramApiId = creds.telegramApiId;
          telegramApiHash = creds.telegramApiHash;
        } catch {
          log.warn({ characterId: sess.characterId }, 'Could not load character credentials for reconnect, falling back to global config');
        }

        const client = new UserbotClient({
          characterId: sess.characterId,
          phoneNumber: sess.phoneNumber,
          session: sess.session,
          telegramApiId,
          telegramApiHash,
        });
        if (this.messageHandler) {
          client.onMessage(this.messageHandler);
        }
        await client.reconnect();
        this.clients.set(sess.characterId, client);
        log.info({ characterId: sess.characterId }, 'Userbot reconnected');
      } catch (err: any) {
        log.error({ characterId: sess.characterId, error: err.message }, 'Failed to reconnect userbot');
      }
    }
  }

  async disconnect(characterId: string): Promise<void> {
    const client = this.clients.get(characterId);
    if (client) {
      await client.disconnect();
      this.clients.delete(characterId);
    }

    // Mark session as inactive
    const sessions = await this.loadSessions();
    const sess = sessions.find(s => s.characterId === characterId);
    if (sess) {
      sess.active = false;
      await this.saveSessions(sessions);
    }

    log.info({ characterId }, 'Userbot disconnected');
  }

  async disconnectAll(): Promise<void> {
    for (const [characterId, client] of this.clients) {
      try {
        await client.disconnect();
      } catch (err: any) {
        log.warn({ characterId, error: err.message }, 'Error disconnecting userbot');
      }
    }
    this.clients.clear();
    log.info('All userbots disconnected');
  }

  getClient(characterId: string): UserbotClient | undefined {
    return this.clients.get(characterId);
  }

  getActiveClients(): Map<string, UserbotClient> {
    return new Map(this.clients);
  }

  async getSessions(): Promise<UserbotSession[]> {
    return this.loadSessions();
  }

  private async loadSessions(): Promise<UserbotSession[]> {
    try {
      const data = await readJSON<UserbotSession[]>(this.sessionsPath);
      return Array.isArray(data) ? data : [];
    } catch {
      return [];
    }
  }

  private async saveSessions(sessions: UserbotSession[]): Promise<void> {
    await ensureDir(resolve(config.dataDir, 'config'));
    await atomicWriteJSON(this.sessionsPath, sessions);
  }

  /**
   * Load character-based API credentials from DataStore.
   * Throws a meaningful error if credentials are missing.
   */
  private async getCharacterCredentials(characterId: string): Promise<{ telegramApiId: number; telegramApiHash: string }> {
    if (!this.dataStore) {
      throw new Error(`API credentials for character "${characterId}" cannot be loaded: DataStore is not available`);
    }

    const character = await this.dataStore.getCharacter(characterId);
    if (!character) {
      throw new Error(`Character "${characterId}" not found. Cannot load API credentials.`);
    }

    const creds = character.apiCredentials;
    if (!creds || !creds.telegramApiId || !creds.telegramApiHash) {
      throw new Error(
        `API credentials are not configured for character "${characterId}" (${character.identity?.name || characterId}). ` +
        `Please set telegramApiId and telegramApiHash in the character's API credentials settings.`
      );
    }

    return { telegramApiId: creds.telegramApiId, telegramApiHash: creds.telegramApiHash };
  }
}
