import type { Command } from 'commander';
import type { Config, CouncilsConfig, ModelConfig, ModelProvider } from '../config/index.js';
import type { ChatResponseFormat } from '../models/types.js';
import { loadConfigSafe, getConfigPath } from './shared.js';
import { existsSync, mkdirSync, readFileSync } from 'fs';
import { resolve } from 'path';
import { homedir } from 'os';
import { setLogLevel } from '../logger.js';
import { GatewayRuntimeCommandClient, TuiRuntimeGatewayBridge } from './tuiRuntimeGateway.js';

// ANSI color codes for tool status display
const toolColors = {
  reset: '\x1b[0m',
  dim: '\x1b[2m',
  cyan: '\x1b[36m',
  green: '\x1b[32m',
  red: '\x1b[31m',
  bold: '\x1b[1m',
};

/** Format a tool name like "gmail.list" → "Gmail: List" */
function formatToolName(name: string): string {
  const parts = name.split('.');
  return parts.map((p, i) => {
    const capitalized = p.charAt(0).toUpperCase() + p.slice(1);
    return i === 0 && parts.length > 1 ? capitalized + ':' : capitalized;
  }).join(' ');
}

/** Format tool args as a compact, readable summary instead of raw JSON. */
function formatToolArgs(args: unknown): string {
  if (!args || typeof args !== 'object') {return '';}
  const entries = Object.entries(args as Record<string, unknown>);
  if (entries.length === 0) {return '';}

  const parts = entries.map(([key, value]) => {
    if (typeof value === 'string') {
      const display = value.length > 60 ? value.slice(0, 57) + '...' : value;
      return `${key}: "${display}"`;
    }
    if (typeof value === 'number' || typeof value === 'boolean') {
      return `${key}: ${value}`;
    }
    return `${key}: ${JSON.stringify(value)}`;
  });

  return parts.join(', ');
}

function loadSystemPrompt(): string {
  const paths = [
    resolve(process.cwd(), 'SOUL.md'),
    resolve(import.meta.dirname, '../../SOUL.md'),
  ];
  for (const soulPath of paths) {
    if (existsSync(soulPath)) {
      return readFileSync(soulPath, 'utf-8');
    }
  }
  return 'You are Flynn, a helpful personal AI assistant. Be direct, concise, and helpful. Use markdown when it improves readability.';
}

function buildProviderConfigMap(config: Config): Partial<Record<ModelProvider, ModelConfig>> {
  const providerConfigs: Partial<Record<ModelProvider, ModelConfig>> = {};
  const modelConfigs: ModelConfig[] = [
    config.models.default,
    ...(config.models.fast ? [config.models.fast] : []),
    ...(config.models.complex ? [config.models.complex] : []),
    ...(config.models.local ? [config.models.local] : []),
    ...Object.values(config.models.local_providers ?? {}),
  ];

  for (const modelConfig of modelConfigs) {
    providerConfigs[modelConfig.provider] = modelConfig;
    if (modelConfig.fallback) {
      providerConfigs[modelConfig.fallback.provider] = modelConfig.fallback;
    }
  }

  return providerConfigs;
}

export function registerTuiCommand(program: Command): void {
  program
    .command('tui')
    .description('Launch the interactive TUI')
    .option('-f, --fullscreen', 'Start in fullscreen mode')
    .option('-c, --config <path>', 'Config file path')
    .action(async (opts: { fullscreen?: boolean; config?: string }) => {
      const configPath = opts.config ?? getConfigPath();
      const { config, error } = loadConfigSafe(configPath);
      if (!config) {
        console.error(error);
        process.exit(1);
      }

      // Dynamic imports to keep CLI startup fast
      const { SessionStore, SessionManager } = await import('../session/index.js');

      // In the TUI, default to 'warn' so model-router and other info messages
      // don't clutter the interactive terminal.  Honour the user's explicit
      // choice if they set log_level to something more verbose.
      const tuiLogLevel = config.log_level === 'debug' ? 'debug' : 'warn';
      setLogLevel(tuiLogLevel);

      const gatewayRuntimeBridge = new TuiRuntimeGatewayBridge({
        client: new GatewayRuntimeCommandClient({
          url: `ws://127.0.0.1:${config.server.port}`,
          token: config.server.token,
        }),
        startDaemon: async () => {
          const { startDaemon } = await import('../daemon/index.js');
          const daemon = await startDaemon(config, {
            configPath,
            persistConfigPath: configPath,
          });
          return {
            shutdown: async () => {
              await daemon.lifecycle.shutdown();
            },
          };
        },
      });

      try {
        await gatewayRuntimeBridge.ensureReady();
      } catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        console.error(
          `Failed to attach to Flynn gateway runtime for TUI command unification: ${message}\n`
          + 'Tried auto-starting daemon/gateway. Check server.port/token config and run `flynn start` manually to verify.',
        );
        process.exit(1);
      }

      const { MinimalTui, startFullscreenTui } = await import('../frontends/tui/index.js');
      const { NativeAgent } = await import('../backends/index.js');
      const {
        createGmailTools,
        createGcalTools,
        createGdocsTools,
        createGdriveTools,
        createGtasksTools,
        createAgentsListTool,
        createAgentDelegateTool,
        createCouncilRunTool,
      } = await import('../tools/index.js');
      const { HookEngine } = await import('../hooks/index.js');
      const { Lifecycle } = await import('../daemon/lifecycle.js');
      const { initTools } = await import('../daemon/tools.js');
      const { createModelRouter } = await import('../daemon/index.js');
      const { AgentConfigRegistry } = await import('../agents/index.js');
      const { loadCouncilScaffoldSafe } = await import('../councils/scaffold.js');
      const { buildCouncilPreflightReport, shouldRunCouncilPreflight } = await import('../councils/preflight.js');

      const dataDir = process.env.FLYNN_DATA_DIR ?? resolve(homedir(), '.local/share/flynn');
      mkdirSync(dataDir, { recursive: true });

      const sessionStore = new SessionStore(resolve(dataDir, 'sessions.db'));
      const sessionManager = new SessionManager(sessionStore);

      // Reuse the daemon's model router factory — includes auto-fallback,
      // local_providers, retry config, and per-tier fallback logic.
      const { loadPreferences, savePreference } = await import('../preferences.js');
      const modelRouter = createModelRouter(config);

      // Restore persisted model tier and save future changes
      const prefs = loadPreferences(dataDir);
      if (prefs.modelTier) {
        modelRouter.setTier(prefs.modelTier as import('../models/router.js').ModelTier);
      }
      modelRouter.setOnTierChange((tier) => savePreference(dataDir, 'modelTier', tier));

      const { initPairingManager } = await import('../daemon/services.js');
      const pairingStore = config.pairing.enabled ? sessionStore.getPairingStore() : undefined;
      const pairingManager = initPairingManager(config, pairingStore);

      const systemPrompt = loadSystemPrompt();

      const hookEngine = new HookEngine(config.hooks);
      const lifecycle = new Lifecycle();
      const { toolRegistry, toolExecutor } = initTools({ config, lifecycle, hookEngine });

      // Register Gmail tools if configured
      if (config.automation.gmail?.enabled) {
        for (const tool of createGmailTools(config.automation.gmail)) {
          toolRegistry.register(tool);
        }
      }

      // Register Google Calendar tools if configured
      if (config.automation.gcal?.enabled) {
        for (const tool of createGcalTools(config.automation.gcal)) {
          toolRegistry.register(tool);
        }
      }

      // Register Google Docs tools if configured
      if (config.automation.gdocs?.enabled) {
        for (const tool of createGdocsTools(config.automation.gdocs)) {
          toolRegistry.register(tool);
        }
      }

      // Register Google Drive tools if configured
      if (config.automation.gdrive?.enabled) {
        for (const tool of createGdriveTools(config.automation.gdrive)) {
          toolRegistry.register(tool);
        }
      }

      // Register Google Tasks tools if configured
      if (config.automation.gtasks?.enabled) {
        for (const tool of createGtasksTools(config.automation.gtasks)) {
          toolRegistry.register(tool);
        }
      }

      const agentConfigRegistry = new AgentConfigRegistry();
      agentConfigRegistry.loadFromConfig(config.agent_configs);
      const delegateRunner = {
        async delegate(request: {
          tier: 'fast' | 'default' | 'complex' | 'local';
          systemPrompt: string;
          message: string;
          maxTokens?: number;
          responseFormat?: ChatResponseFormat;
        }) {
          const response = await modelRouter.chat({
            messages: [{ role: 'user', content: request.message }],
            system: request.systemPrompt,
            maxTokens: request.maxTokens,
            responseFormat: request.responseFormat,
          }, request.tier);
          return {
            content: response.content,
            usage: response.usage,
            tier: request.tier,
          };
        },
      };
      if (agentConfigRegistry.list().length > 0) {
        toolRegistry.register(createAgentsListTool(agentConfigRegistry));
        toolRegistry.register(createAgentDelegateTool({
          registry: agentConfigRegistry,
          orchestrator: delegateRunner,
        }));
        if (config.councils?.enabled) {
          toolRegistry.register(createCouncilRunTool({
            registry: agentConfigRegistry,
            orchestrator: delegateRunner,
            config: config.councils as CouncilsConfig,
            scaffold: loadCouncilScaffoldSafe(config.councils.scaffold_path),
          }));
        }
      }

      const session = sessionManager.getSession('tui', 'local');
      const modelProviderConfigs = buildProviderConfigMap(config);

      const agent = new NativeAgent({
        modelClient: modelRouter,
        systemPrompt,
        session,
        toolRegistry,
        toolExecutor,
        onToolUse: (event) => {
          if (event.type === 'start') {
            const label = formatToolName(event.tool);
            const argsStr = event.args ? ` ${toolColors.dim}(${formatToolArgs(event.args)})${toolColors.reset}` : '';
            process.stdout.write(`${toolColors.cyan}> ${toolColors.bold}${label}${toolColors.reset}${argsStr}\n`);
          } else if (event.type === 'end' && event.result) {
            const icon = event.result.success ? `${toolColors.green}done` : `${toolColors.red}error`;
            const detail = event.result.success
              ? `${toolColors.dim}(${event.result.output.split('\n').length} lines)${toolColors.reset}`
              : `${toolColors.dim}${event.result.error ?? 'unknown error'}${toolColors.reset}`;
            process.stdout.write(`  ${icon}${toolColors.reset} ${detail}\n`);
          }
        },
      });

      let cleanupPromise: Promise<void> | null = null;
      const cleanup = async () => {
        if (!cleanupPromise) {
          cleanupPromise = (async () => {
            await lifecycle.shutdown();
            sessionStore.close();
            await gatewayRuntimeBridge.shutdown();
          })();
        }
        return cleanupPromise;
      };

      let activeCtrlCHandler: (() => boolean) | null = null;
      let lastCtrlCAtMs = 0;
      const ctrlCExitWindowMs = 1_500;

      const signalHandler = (signal: NodeJS.Signals) => {
        if (signal === 'SIGINT' && activeCtrlCHandler) {
          // Minimal TUI owns Ctrl+C behavior via readline SIGINT handling.
          return;
        }

        if (signal === 'SIGINT') {
          const now = Date.now();
          const isDoublePress = lastCtrlCAtMs > 0 && (now - lastCtrlCAtMs) <= ctrlCExitWindowMs;
          lastCtrlCAtMs = now;

          if (!isDoublePress) {
            if (activeCtrlCHandler && !activeCtrlCHandler()) {
              return;
            }
            console.log('\nPress Ctrl+C again to quit (or use /quit).');
            return;
          }
        }

        console.log(`\nReceived ${signal}; shutting down TUI...`);
        void cleanup()
          .then(() => process.exit(0))
          .catch((error) => {
            console.error('TUI shutdown failed:', error);
            process.exit(1);
          });
      };

      process.on('SIGINT', signalHandler);
      process.on('SIGTERM', signalHandler);

      const transferSessionToTarget = (target: string): string => {
        const normalizedTarget = target.trim().toLowerCase();
        if (!normalizedTarget) {
          return 'Usage: /transfer <tui|telegram>';
        }

        if (normalizedTarget === 'tui') {
          return 'Session is already active on TUI (local)';
        }

        if (normalizedTarget === 'telegram') {
          if (!config.telegram || config.telegram.allowed_chat_ids.length === 0) {
            return 'Telegram not configured';
          }
          const telegramUserId = String(config.telegram.allowed_chat_ids[0]);
          sessionManager.transferSession('tui', 'local', 'telegram', telegramUserId);
          return `Session transferred to Telegram (${telegramUserId})`;
        }

        return `Unknown transfer target: ${target}. Supported targets: tui, telegram`;
      };

      const listAvailableTools = (): string => {
        const names = toolRegistry.filteredList().map((tool) => tool.name).sort();
        return [
          `Available tools (${names.length}):`,
          ...names.map((name) => `- ${name}`),
        ].join('\n');
      };

      const delegateToResearchAgent = async (task: string): Promise<string> => {
        const message = task.trim();
        if (!message) {
          return 'Usage: /research <question or task>';
        }
        modelRouter.clearAbort();
        const agentConfig = agentConfigRegistry.get('research');
        if (!agentConfig) {
          return 'Agent "research" not found. Configure agent_configs.research first.';
        }
        const tier = agentConfig.modelTier ?? 'default';
        const systemPrompt = agentConfig.systemPrompt
          ?? 'You are a research sub-agent. Produce concise, source-grounded findings.';
        const result = await delegateRunner.delegate({
          tier,
          systemPrompt,
          message,
          maxTokens: 4096,
        });
        return `[Agent: research | Tier: ${result.tier} | Tokens: ${result.usage.inputTokens}+${result.usage.outputTokens}]\n\n${result.content}`;
      };

      const runCouncilTask = async (task: string): Promise<string> => {
        const message = task.trim();
        if (!message) {
          return 'Usage: /council <question or task> | /council preflight';
        }
        modelRouter.clearAbort();
        if (!config.councils?.enabled) {
          return 'Councils are disabled. Set councils.enabled: true in config.';
        }
        if (shouldRunCouncilPreflight(message)) {
          return buildCouncilPreflightReport({
            config,
            registry: agentConfigRegistry,
            delegateRunner,
            activeTier: modelRouter.getTier(),
            includeLiveProbe: true,
          });
        }
        const tool = toolRegistry.get('council.run');
        if (!tool) {
          return 'Council tool is not registered. Verify councils config and restart Flynn.';
        }
        const result = await tool.execute({ task: message });
        if (!result.success) {
          return `Council run failed: ${result.error ?? 'unknown error'}`;
        }
        return result.output;
      };

      if (opts.fullscreen) {
        await startFullscreenTui({
          session,
          modelClient: modelRouter,
          modelRouter,
          systemPrompt,
          model: config.models.default.model,
          agent,
          hookEngine,
          pairingManager,
          localProviders: config.models.local_providers,
          currentLocalProvider: config.models.local?.provider,
          modelProviderConfigs,
          contextThresholdPct: config.compaction.threshold_pct,
          onTransfer: transferSessionToTarget,
          onTools: listAvailableTools,
          onResearch: delegateToResearchAgent,
          onCouncil: runCouncilTask,
          onRuntimeCommand: (input) => gatewayRuntimeBridge.executeRuntimeCommand(input),
          onExit: () => {
            void cleanup();
          },
        });
      } else {
        let switchingToFullscreen = false;

        const tui = new MinimalTui({
          session,
          modelClient: modelRouter,
          modelRouter,
          systemPrompt,
          agent,
          hookEngine,
          pairingManager,
          onTools: listAvailableTools,
          onResearch: delegateToResearchAgent,
          onCouncil: runCouncilTask,
          onRuntimeCommand: (input) => gatewayRuntimeBridge.executeRuntimeCommand(input),
          localProviders: config.models.local_providers,
          modelProviderConfigs,
          contextThresholdPct: config.compaction.threshold_pct,
          currentLocalProvider: config.models.local?.provider,
          onTransfer: transferSessionToTarget,
          onFullscreen: () => {
            switchingToFullscreen = true;
            tui.stop(true);
          },
        });
        activeCtrlCHandler = () => tui.handleCtrlCPress();

        await tui.start();
        activeCtrlCHandler = null;

        if (switchingToFullscreen) {
          console.clear();
          await startFullscreenTui({
            session,
            modelClient: modelRouter,
            modelRouter,
            systemPrompt,
            model: config.models.default.model,
            agent,
            hookEngine,
            pairingManager,
            localProviders: config.models.local_providers,
            currentLocalProvider: config.models.local?.provider,
            modelProviderConfigs,
            contextThresholdPct: config.compaction.threshold_pct,
            onTransfer: transferSessionToTarget,
            onTools: listAvailableTools,
            onResearch: delegateToResearchAgent,
            onCouncil: runCouncilTask,
            onRuntimeCommand: (input) => gatewayRuntimeBridge.executeRuntimeCommand(input),
            onExit: () => {
              void cleanup();
            },
          });
          return;
        }
      }

      await cleanup();
      process.off('SIGINT', signalHandler);
      process.off('SIGTERM', signalHandler);
    });
}
