import { Command } from 'commander';
import type { ApiClient } from '../api-client.js';

export interface BackupDeps {
  client: ApiClient;
  log: (...args: unknown[]) => void;
}

interface BackupStatus {
  enabled: boolean;
  repoUrl: string | null;
  publicKey: string | null;
  gitReachable: boolean;
  lastSyncAt: string | null;
  lastPushAt: string | null;
  lastError: string | null;
  pendingCount: number;
}

interface LogEntry {
  hash: string;
  date: string;
  author: string;
  message: string;
  manual: boolean;
}

export function createBackupCommand(deps: BackupDeps): Command {
  const cmd = new Command('backup')
    .description('Git-based backup status and management')
    .action(async () => {
      const status = await deps.client.get<BackupStatus>('/api/v1/backup/status');

      if (!status.enabled) {
        deps.log('Backup: disabled');
        deps.log('');
        deps.log('To enable, create a backup-ssh secret:');
        deps.log('  mcpctl create secret backup-ssh --data repoUrl=ssh://git@host/repo.git');
        deps.log('');
        deps.log('After creating the secret, restart mcpd. An SSH keypair will be');
        deps.log('auto-generated and stored in the secret. Run mcpctl backup to see');
        deps.log('the public key, then add it as a deploy key in your git host.');
        return;
      }

      deps.log(`Repo:      ${status.repoUrl}`);

      if (status.gitReachable) {
        if (status.pendingCount === 0) {
          deps.log('Status:    synced');
        } else {
          deps.log(`Status:    ${status.pendingCount} changes pending`);
        }
      } else {
        deps.log('Status:    disconnected');
      }

      if (status.lastSyncAt) {
        const ago = timeAgo(status.lastSyncAt);
        deps.log(`Last sync: ${ago}`);
      }
      if (status.lastPushAt) {
        const ago = timeAgo(status.lastPushAt);
        deps.log(`Last push: ${ago}`);
      }
      if (status.lastError) {
        deps.log(`Error:     ${status.lastError}`);
      }
      if (status.publicKey) {
        deps.log('');
        deps.log(`SSH key:   ${status.publicKey}`);
      }
    });

  cmd
    .command('log')
    .description('Show backup commit history')
    .option('-n, --limit <count>', 'number of commits to show', '20')
    .action(async (opts: { limit: string }) => {
      const { entries } = await deps.client.get<{ entries: LogEntry[] }>(
        `/api/v1/backup/log?limit=${opts.limit}`,
      );

      if (entries.length === 0) {
        deps.log('No backup history');
        return;
      }

      // Header
      const hashW = 9;
      const dateW = 20;
      const authorW = 15;
      deps.log(
        'COMMIT'.padEnd(hashW) +
          'DATE'.padEnd(dateW) +
          'AUTHOR'.padEnd(authorW) +
          'MESSAGE',
      );

      for (const e of entries) {
        const hash = e.hash.slice(0, 7);
        const date = new Date(e.date).toLocaleString('en-GB', {
          day: '2-digit', month: '2-digit', year: 'numeric',
          hour: '2-digit', minute: '2-digit',
        });
        const author = e.author.replace(/<.*>/, '').trim();
        const marker = e.manual ? ' [manual]' : '';
        deps.log(
          hash.padEnd(hashW) +
            date.padEnd(dateW) +
            author.slice(0, authorW - 1).padEnd(authorW) +
            e.message + marker,
        );
      }
    });

  // ── Restore subcommand group ──
  const restore = new Command('restore')
    .description('Restore mcpctl state from backup history');

  restore
    .command('list')
    .description('List available restore points')
    .option('-n, --limit <count>', 'number of entries', '30')
    .action(async (opts: { limit: string }) => {
      const { entries } = await deps.client.get<{ entries: LogEntry[] }>(
        `/api/v1/backup/log?limit=${opts.limit}`,
      );

      if (entries.length === 0) {
        deps.log('No restore points available');
        return;
      }

      deps.log(
        'COMMIT'.padEnd(9) +
          'DATE'.padEnd(20) +
          'USER'.padEnd(15) +
          'MESSAGE',
      );

      for (const e of entries) {
        const hash = e.hash.slice(0, 7);
        const date = new Date(e.date).toLocaleString('en-GB', {
          day: '2-digit', month: '2-digit', year: 'numeric',
          hour: '2-digit', minute: '2-digit',
        });
        const author = e.author.replace(/<.*>/, '').trim();
        deps.log(
          hash.padEnd(9) +
            date.padEnd(20) +
            author.slice(0, 14).padEnd(15) +
            e.message,
        );
      }
    });

  restore
    .command('diff <commit>')
    .description('Preview what restoring to a commit would change')
    .action(async (commit: string) => {
      const preview = await deps.client.post<{
        targetCommit: string;
        targetDate: string;
        targetMessage: string;
        added: string[];
        removed: string[];
        modified: string[];
      }>('/api/v1/backup/restore/preview', { commit });

      deps.log(`Target: ${preview.targetCommit.slice(0, 7)} — ${preview.targetMessage}`);
      deps.log(`Date:   ${new Date(preview.targetDate).toLocaleString()}`);
      deps.log('');

      if (preview.added.length === 0 && preview.removed.length === 0 && preview.modified.length === 0) {
        deps.log('No changes — already at this state.');
        return;
      }

      for (const f of preview.added) deps.log(`  + ${f}`);
      for (const f of preview.modified) deps.log(`  ~ ${f}`);
      for (const f of preview.removed) deps.log(`  - ${f}`);

      deps.log('');
      deps.log(`Total: ${preview.added.length} added, ${preview.modified.length} modified, ${preview.removed.length} removed`);
    });

  restore
    .command('to <commit>')
    .description('Restore to a specific commit')
    .option('--force', 'skip confirmation', false)
    .action(async (commit: string, opts: { force: boolean }) => {
      // Show preview first
      const preview = await deps.client.post<{
        targetCommit: string;
        targetDate: string;
        targetMessage: string;
        added: string[];
        removed: string[];
        modified: string[];
      }>('/api/v1/backup/restore/preview', { commit });

      const totalChanges = preview.added.length + preview.removed.length + preview.modified.length;

      if (totalChanges === 0) {
        deps.log('No changes — already at this state.');
        return;
      }

      deps.log(`Restoring to ${preview.targetCommit.slice(0, 7)} — ${preview.targetMessage}`);
      deps.log(`  ${preview.added.length} added, ${preview.modified.length} modified, ${preview.removed.length} removed`);

      if (!opts.force) {
        deps.log('');
        deps.log('Use --force to proceed. Current state will be saved as a timeline branch.');
        return;
      }

      const result = await deps.client.post<{
        branchName: string;
        applied: number;
        deleted: number;
        errors: string[];
      }>('/api/v1/backup/restore', { commit });

      deps.log('');
      deps.log(`Restored: ${result.applied} applied, ${result.deleted} deleted`);
      deps.log(`Previous state saved as branch '${result.branchName}'`);

      if (result.errors.length > 0) {
        deps.log('Errors:');
        for (const err of result.errors) {
          deps.log(`  - ${err}`);
        }
      }
    });

  cmd.addCommand(restore);

  return cmd;
}

function timeAgo(iso: string): string {
  const ms = Date.now() - new Date(iso).getTime();
  const secs = Math.floor(ms / 1000);
  if (secs < 60) return `${secs}s ago`;
  const mins = Math.floor(secs / 60);
  if (mins < 60) return `${mins}m ago`;
  const hours = Math.floor(mins / 60);
  if (hours < 24) return `${hours}h ago`;
  return `${Math.floor(hours / 24)}d ago`;
}
