import { Command } from 'commander';
import { writeFileSync, readFileSync, existsSync } from 'node:fs';
import { resolve, join } from 'node:path';
import { homedir } from 'node:os';
import { loadConfig, saveConfig, mergeConfig, getConfigPath, DEFAULT_CONFIG } from '../config/index.js';
import type { McpctlConfig, ConfigLoaderDeps } from '../config/index.js';
import { formatJson, formatYaml } from '../formatters/index.js';
import { saveCredentials, loadCredentials } from '../auth/index.js';
import { createConfigSetupCommand } from './config-setup.js';
import type { CredentialsDeps, StoredCredentials } from '../auth/index.js';
import type { ApiClient } from '../api-client.js';

interface McpConfig {
  mcpServers: Record<string, { command?: string; args?: string[]; url?: string; env?: Record<string, string> }>;
}

export interface ConfigCommandDeps {
  configDeps: Partial<ConfigLoaderDeps>;
  log: (...args: string[]) => void;
}

export interface ConfigApiDeps {
  client: ApiClient;
  credentialsDeps: Partial<CredentialsDeps>;
  log: (...args: string[]) => void;
}

const defaultDeps: ConfigCommandDeps = {
  configDeps: {},
  log: (...args) => console.log(...args),
};

export function createConfigCommand(deps?: Partial<ConfigCommandDeps>, apiDeps?: ConfigApiDeps): Command {
  const { configDeps, log } = { ...defaultDeps, ...deps };

  const config = new Command('config').description('Manage mcpctl configuration');

  config
    .command('view')
    .description('Show current configuration')
    .option('-o, --output <format>', 'output format (json, yaml)', 'json')
    .action((opts: { output: string }) => {
      const cfg = loadConfig(configDeps);
      const out = opts.output === 'yaml' ? formatYaml(cfg) : formatJson(cfg);
      log(out);
    });

  config
    .command('set')
    .description('Set a configuration value')
    .argument('<key>', 'configuration key (e.g., daemonUrl, outputFormat)')
    .argument('<value>', 'value to set')
    .action((key: string, value: string) => {
      const updates: Record<string, unknown> = {};

      // Handle typed conversions
      if (key === 'cacheTTLMs') {
        updates[key] = parseInt(value, 10);
      } else if (key === 'registries') {
        updates[key] = value.split(',').map((s) => s.trim());
      } else if (key === 'daemonUrl') {
        // Backward compat: map daemonUrl to mcplocalUrl
        updates['mcplocalUrl'] = value;
      } else {
        updates[key] = value;
      }

      const updated = mergeConfig(updates as Partial<McpctlConfig>, configDeps);
      saveConfig(updated, configDeps);
      log(`Set ${key} = ${value}`);
    });

  config
    .command('path')
    .description('Show configuration file path')
    .action(() => {
      log(getConfigPath(configDeps?.configDir));
    });

  config
    .command('reset')
    .description('Reset configuration to defaults')
    .action(() => {
      saveConfig(DEFAULT_CONFIG, configDeps);
      log('Configuration reset to defaults');
    });

  // claude/claude-generate: generate .mcp.json pointing at mcpctl mcp bridge
  function registerClaudeCommand(name: string, hidden: boolean): void {
    const cmd = config
      .command(name)
      .description(hidden ? '' : 'Generate .mcp.json that connects a project via mcpctl mcp bridge')
      .option('-p, --project <name>', 'Project name')
      .option('-o, --output <path>', 'Output file path', '.mcp.json')
      .option('--inspect', 'Include mcpctl-inspect MCP server for traffic monitoring')
      .option('--stdout', 'Print to stdout instead of writing a file')
      .action((opts: { project?: string; output: string; inspect?: boolean; stdout?: boolean }) => {
        if (!opts.project && !opts.inspect) {
          log('Error: at least one of --project or --inspect is required');
          process.exitCode = 1;
          return;
        }

        const servers: McpConfig['mcpServers'] = {};
        if (opts.project) {
          servers[opts.project] = {
            command: 'mcpctl',
            args: ['mcp', '-p', opts.project],
          };
        }
        if (opts.inspect) {
          servers['mcpctl-inspect'] = {
            command: 'mcpctl',
            args: ['console', '--stdin-mcp'],
          };
        }

        if (opts.stdout) {
          log(JSON.stringify({ mcpServers: servers }, null, 2));
          return;
        }

        const outputPath = resolve(opts.output);
        let finalConfig: McpConfig = { mcpServers: servers };

        // Always merge with existing .mcp.json — never overwrite other servers
        if (existsSync(outputPath)) {
          try {
            const existing = JSON.parse(readFileSync(outputPath, 'utf-8')) as McpConfig;
            finalConfig = {
              mcpServers: {
                ...existing.mcpServers,
                ...servers,
              },
            };
          } catch {
            // If existing file is invalid, start fresh
          }
        }

        writeFileSync(outputPath, JSON.stringify(finalConfig, null, 2) + '\n');
        const serverCount = Object.keys(finalConfig.mcpServers).length;
        log(`Wrote ${outputPath} (${serverCount} server(s))`);
      });
    if (hidden) {
      // Commander shows empty-description commands but they won't clutter help output
      void cmd; // suppress unused lint
    }
  }

  registerClaudeCommand('claude', false);
  registerClaudeCommand('claude-generate', true); // backward compat

  config.addCommand(createConfigSetupCommand({ configDeps }));

  if (apiDeps) {
    const { client, credentialsDeps, log: apiLog } = apiDeps;

    config
      .command('impersonate')
      .description('Impersonate another user or return to original identity')
      .argument('[email]', 'Email of user to impersonate')
      .option('--quit', 'Stop impersonating and return to original identity')
      .action(async (email: string | undefined, opts: { quit?: boolean }) => {
        const configDir = credentialsDeps?.configDir ?? join(homedir(), '.mcpctl');
        const backupPath = join(configDir, 'credentials-backup');

        if (opts.quit) {
          if (!existsSync(backupPath)) {
            apiLog('No impersonation session to quit');
            process.exitCode = 1;
            return;
          }

          const backupRaw = readFileSync(backupPath, 'utf-8');
          const backup = JSON.parse(backupRaw) as StoredCredentials;
          saveCredentials(backup, credentialsDeps);

          // Remove backup file
          const { unlinkSync } = await import('node:fs');
          unlinkSync(backupPath);

          apiLog(`Returned to ${backup.user}`);
          return;
        }

        if (!email) {
          apiLog('Email is required when not using --quit');
          process.exitCode = 1;
          return;
        }

        // Save current credentials as backup
        const currentCreds = loadCredentials(credentialsDeps);
        if (!currentCreds) {
          apiLog('Not logged in. Run "mcpctl login" first.');
          process.exitCode = 1;
          return;
        }

        writeFileSync(backupPath, JSON.stringify(currentCreds, null, 2) + '\n', 'utf-8');

        try {
          const result = await client.post<{ token: string; user: { email: string } }>(
            '/api/v1/auth/impersonate',
            { email },
          );

          saveCredentials({
            token: result.token,
            mcpdUrl: currentCreds.mcpdUrl,
            user: result.user.email,
          }, credentialsDeps);

          apiLog(`Impersonating ${result.user.email}. Use 'mcpctl config impersonate --quit' to return.`);
        } catch (err) {
          // Restore backup on failure
          const backup = JSON.parse(readFileSync(backupPath, 'utf-8')) as StoredCredentials;
          saveCredentials(backup, credentialsDeps);
          const { unlinkSync } = await import('node:fs');
          unlinkSync(backupPath);

          apiLog(`Impersonate failed: ${(err as Error).message}`);
          process.exitCode = 1;
        }
      });
  }

  return config;
}
