import { Bot } from 'grammy';
import type { NativeAgent } from '../../backends/index.js';
import type { TelegramConfig } from '../../config/index.js';
import type { HookEngine } from '../../hooks/index.js';
import { isAllowedChat, createMessageHandler, createResetHandler } from './handlers.js';
import { parseConfirmationCallback } from './confirmations.js';

export interface TelegramBotConfig {
  telegram: TelegramConfig;
  agent: NativeAgent;
  hookEngine?: HookEngine;
}

export function createTelegramBot(config: TelegramBotConfig): Bot {
  const bot = new Bot(config.telegram.bot_token);
  const handleMessage = createMessageHandler(config.agent);
  const handleReset = createResetHandler(config.agent);
  const allowedChatIds = config.telegram.allowed_chat_ids;
  const hookEngine = config.hookEngine;

  // Middleware to check chat ID
  bot.use(async (ctx, next) => {
    const chatId = ctx.chat?.id;
    if (chatId === undefined || !isAllowedChat(chatId, allowedChatIds)) {
      console.log(`Rejected message from unauthorized chat: ${chatId}`);
      return;
    }
    await next();
  });

  // Handle confirmation callbacks
  bot.on('callback_query:data', async (ctx) => {
    const data = ctx.callbackQuery.data;
    const parsed = parseConfirmationCallback(data);

    if (!parsed || !hookEngine) {
      await ctx.answerCallbackQuery({ text: 'Invalid action' });
      return;
    }

    const resolved = hookEngine.resolveConfirmation(parsed.id, {
      approved: parsed.approved,
      reason: parsed.approved ? undefined : 'Denied by user',
    });

    if (resolved) {
      await ctx.answerCallbackQuery({
        text: parsed.approved ? '✅ Approved' : '❌ Denied',
      });
      await ctx.editMessageText(
        ctx.callbackQuery.message?.text + `\n\n${parsed.approved ? '✅ Approved' : '❌ Denied'}`,
        { parse_mode: 'Markdown' },
      );
    } else {
      await ctx.answerCallbackQuery({ text: 'Confirmation expired or not found' });
    }
  });

  // Command handlers
  bot.command('start', async (ctx) => {
    await ctx.reply('Flynn is ready. Send me a message!');
  });

  bot.command('reset', async (ctx) => {
    handleReset();
    await ctx.reply('Conversation reset.');
  });

  bot.command('status', async (ctx) => {
    const pending = hookEngine?.getPendingConfirmations() ?? [];
    const currentTier = config.agent.getModelTier();
    const tierDisplay = currentTier === 'local' ? '🏠 Local (Qwen)' : '☁️ Cloud (Claude)';
    const statusMsg = `Flynn is running.\nModel: ${tierDisplay}\nPending confirmations: ${pending.length}`;
    await ctx.reply(statusMsg);
  });

  bot.command('local', async (ctx) => {
    config.agent.setModelTier('local');
    await ctx.reply('🏠 Switched to local model (Qwen 2.5 14B)');
  });

  bot.command('cloud', async (ctx) => {
    config.agent.setModelTier('default');
    await ctx.reply('☁️ Switched to cloud model (Claude Opus 4.5)');
  });

  bot.command('model', async (ctx) => {
    const currentTier = config.agent.getModelTier();
    const tierDisplay = currentTier === 'local' ? '🏠 Local (Qwen 2.5 14B)' : '☁️ Cloud (Claude Opus 4.5)';
    await ctx.reply(`Current model: ${tierDisplay}\n\nCommands:\n/local - Switch to local model\n/cloud - Switch to cloud model`);
  });

  // Message handler
  bot.on('message:text', async (ctx) => {
    const text = ctx.message.text;

    // Show typing indicator
    await ctx.replyWithChatAction('typing');

    // Set up tool status display for this message
    let statusMsgId: number | undefined;
    config.agent.setOnToolUse(async (event) => {
      try {
        if (event.type === 'start') {
          const argsSnippet = event.args ? ` ${JSON.stringify(event.args).slice(0, 100)}` : '';
          const statusText = `⚡ ${event.tool}${argsSnippet}`;
          const msg = await ctx.reply(statusText);
          statusMsgId = msg.message_id;
          // Keep typing indicator active
          await ctx.replyWithChatAction('typing');
        } else if (event.type === 'end' && event.result && statusMsgId) {
          const icon = event.result.success ? '✓' : '✗';
          const detail = event.result.success
            ? `${event.result.output.split('\n').length} lines`
            : (event.result.error ?? 'error');
          try {
            await ctx.api.editMessageText(
              ctx.chat.id,
              statusMsgId,
              `${icon} ${event.tool}: ${detail}`,
            );
          } catch {
            // Edit may fail if message was deleted; ignore
          }
          statusMsgId = undefined;
        }
      } catch {
        // Don't let status display errors break the tool loop
      }
    });

    try {
      const response = await handleMessage(text);

      // Clear tool use callback
      config.agent.setOnToolUse(undefined);

      // Telegram has a 4096 character limit per message
      if (response.length <= 4096) {
        await ctx.reply(response, { parse_mode: 'Markdown' });
      } else {
        // Split into chunks
        const chunks = splitMessage(response, 4096);
        for (const chunk of chunks) {
          await ctx.reply(chunk, { parse_mode: 'Markdown' });
        }
      }
    } catch (error) {
      config.agent.setOnToolUse(undefined);
      console.error('Error processing message:', error);
      await ctx.reply('Sorry, an error occurred while processing your message.');
    }
  });

  return bot;
}

function splitMessage(text: string, maxLength: number): string[] {
  const chunks: string[] = [];
  let remaining = text;

  while (remaining.length > 0) {
    if (remaining.length <= maxLength) {
      chunks.push(remaining);
      break;
    }

    // Try to split at a newline
    let splitIndex = remaining.lastIndexOf('\n', maxLength);
    if (splitIndex === -1 || splitIndex < maxLength / 2) {
      // Fall back to splitting at space
      splitIndex = remaining.lastIndexOf(' ', maxLength);
    }
    if (splitIndex === -1 || splitIndex < maxLength / 2) {
      // Hard split
      splitIndex = maxLength;
    }

    chunks.push(remaining.slice(0, splitIndex));
    remaining = remaining.slice(splitIndex).trimStart();
  }

  return chunks;
}
