import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import type { Authenticator } from '../auth/authenticator.js';
import type { AgentHarness, HarnessFactory } from '../harness/agent-harness.js';
import type { InboundMessage } from '../types/messages.js';
import { randomUUID } from 'crypto';

import type { ChannelAdapter, ChannelCapabilities } from '../workspace/index.js';

export interface TelegramHandlerConfig {
  authenticator: Authenticator;
  telegramBotToken: string;
  createHarness: HarnessFactory;
}

interface TelegramUpdate {
  update_id: number;
  message?: {
    message_id: number;
    from: {
      id: number;
      first_name: string;
      username?: string;
    };
    chat: {
      id: number;
      type: string;
    };
    text?: string;
    photo?: Array<{
      file_id: string;
      file_size: number;
    }>;
  };
}

interface TelegramSession {
  harness: AgentHarness;
  lastActivity: number;
}

/**
 * Telegram webhook handler
 */
export class TelegramHandler {
  private config: TelegramHandlerConfig;
  private sessions = new Map<string, TelegramSession>();
  private chatIds = new Map<string, number>(); // sessionId -> chatId

  constructor(config: TelegramHandlerConfig) {
    this.config = config;
  }

  /**
   * Register Telegram webhook routes
   */
  register(app: FastifyInstance): void {
    app.post('/webhook/telegram', async (request: FastifyRequest, reply: FastifyReply) => {
      await this.handleWebhook(request, reply, app);
    });
  }

  /**
   * Handle Telegram webhook
   */
  private async handleWebhook(
    request: FastifyRequest,
    reply: FastifyReply,
    app: FastifyInstance
  ): Promise<void> {
    const logger = app.log;

    try {
      const update = request.body as TelegramUpdate;

      if (!update.message?.text) {
        // Ignore non-text messages for now
        reply.code(200).send({ ok: true });
        return;
      }

      const telegramUserId = update.message.from.id.toString();
      const chatId = update.message.chat.id;
      const text = update.message.text;

      logger.info({ telegramUserId, chatId, text }, 'Received Telegram message');

      // Authenticate
      const authContext = await this.config.authenticator.authenticateTelegram(telegramUserId);
      if (!authContext) {
        logger.warn({ telegramUserId }, 'Telegram user not authenticated');
        await this.sendTelegramMessage(
          chatId,
          'Please link your Telegram account to Dexorder first.'
        );
        reply.code(200).send({ ok: true });
        return;
      }

      // Store chatId for this session
      this.chatIds.set(authContext.sessionId, chatId);

      // Create Telegram channel adapter
      const telegramAdapter: ChannelAdapter = {
        sendSnapshot: () => {
          // Telegram doesn't support sync protocol
        },
        sendPatch: () => {
          // Telegram doesn't support sync protocol
        },
        sendText: (msg) => {
          this.sendTelegramMessage(chatId, msg.text).catch((err) => {
            logger.error({ error: err }, 'Failed to send Telegram text');
          });
        },
        sendChunk: () => {
          // Telegram doesn't support streaming; full response sent after handleMessage resolves
        },
        sendImage: (msg) => {
          this.sendTelegramPhoto(chatId, msg.data, msg.mimeType, msg.caption).catch((err) => {
            logger.error({ error: err }, 'Failed to send Telegram image');
          });
        },
        getCapabilities: (): ChannelCapabilities => ({
          supportsSync: false,
          supportsImages: true,
          supportsMarkdown: true,
          supportsStreaming: false,
          supportsTradingViewEmbed: false,
        }),
      };

      // Get or create harness
      let session = this.sessions.get(authContext.sessionId);
      if (!session) {
        const harness = this.config.createHarness({
          userId: authContext.userId,
          sessionId: authContext.sessionId,
          license: authContext.license,
          mcpServerUrl: authContext.mcpServerUrl,
          logger,
          channelAdapter: telegramAdapter,
          channelType: authContext.channelType,
          channelUserId: authContext.channelUserId,
        });
        await harness.initialize();
        session = { harness, lastActivity: Date.now() };
        this.sessions.set(authContext.sessionId, session);
      } else {
        // Update channel adapter and activity timestamp for existing session
        session.harness.setChannelAdapter(telegramAdapter);
        session.lastActivity = Date.now();
      }

      // Process message
      const inboundMessage: InboundMessage = {
        messageId: randomUUID(),
        userId: authContext.userId,
        sessionId: authContext.sessionId,
        content: text,
        attachments: [], // TODO: Add image support for Telegram
        timestamp: new Date(),
      };

      const response = await session.harness.handleMessage(inboundMessage);

      // Send response back to Telegram
      await this.sendTelegramMessage(chatId, response.content);

      reply.code(200).send({ ok: true });
    } catch (error) {
      logger.error({ error }, 'Error handling Telegram webhook');
      reply.code(500).send({ ok: false, error: 'Internal server error' });
    }
  }

  /**
   * Send text message to Telegram chat
   */
  private async sendTelegramMessage(chatId: number, text: string): Promise<void> {
    const url = `https://api.telegram.org/bot${this.config.telegramBotToken}/sendMessage`;

    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          chat_id: chatId,
          text,
          parse_mode: 'Markdown',
        }),
      });

      if (!response.ok) {
        throw new Error(`Telegram API error: ${response.statusText}`);
      }
    } catch (error) {
      console.error('Failed to send Telegram message:', error);
      throw error;
    }
  }

  /**
   * Send photo to Telegram chat
   * Converts base64 image data to a buffer and sends via sendPhoto API
   */
  private async sendTelegramPhoto(
    chatId: number,
    base64Data: string,
    mimeType: string,
    caption?: string
  ): Promise<void> {
    const url = `https://api.telegram.org/bot${this.config.telegramBotToken}/sendPhoto`;

    try {
      // Convert base64 to buffer
      const imageBuffer = Buffer.from(base64Data, 'base64');

      // Determine filename from mimeType
      const extension = mimeType.split('/')[1] || 'png';
      const filename = `image.${extension}`;

      // Create FormData for multipart upload
      const formData = new FormData();
      formData.append('chat_id', chatId.toString());
      formData.append('photo', new Blob([imageBuffer], { type: mimeType }), filename);
      if (caption) {
        formData.append('caption', caption);
      }

      const response = await fetch(url, {
        method: 'POST',
        body: formData,
      });

      if (!response.ok) {
        const errorText = await response.text();
        throw new Error(`Telegram API error: ${response.statusText} - ${errorText}`);
      }
    } catch (error) {
      console.error('Failed to send Telegram photo:', error);
      throw error;
    }
  }

  /**
   * Clean up sessions that have been idle longer than maxAgeMs.
   * Triggers Iceberg flush for each expired session via harness.cleanup().
   */
  async cleanupSessions(maxAgeMs = 2 * 60 * 60 * 1000): Promise<void> {
    const now = Date.now();
    const expired: string[] = [];

    for (const [sessionId, session] of this.sessions) {
      if (now - session.lastActivity > maxAgeMs) {
        expired.push(sessionId);
      }
    }

    for (const sessionId of expired) {
      const session = this.sessions.get(sessionId);
      if (session) {
        await session.harness.cleanup().catch(() => {});
        this.sessions.delete(sessionId);
        this.chatIds.delete(sessionId);
      }
    }
  }

  /**
   * Flush and clean up all active sessions.
   * Called during graceful shutdown.
   */
  async endAllSessions(): Promise<void> {
    const cleanups = Array.from(this.sessions.values()).map(s => s.harness.cleanup());
    await Promise.allSettled(cleanups);
    this.sessions.clear();
    this.chatIds.clear();
  }
}
