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

/**
 * `mcpctl review` — triage UX for the proposal queue. Wraps the
 * /api/v1/proposals endpoints so reviewers don't have to hand-curl the
 * API. Subcommands:
 *
 *   mcpctl review pending                 List pending proposals
 *   mcpctl review next                    Show oldest pending
 *   mcpctl review show <id>               Full detail of one proposal
 *   mcpctl review approve <id>            Approve (creates resource + revision)
 *   mcpctl review reject <id> --reason    Reject with reviewer note
 *   mcpctl review diff <id>               Diff proposal body vs current resource
 */

interface Proposal {
  id: string;
  resourceType: 'prompt' | 'skill';
  name: string;
  body: Record<string, unknown>;
  projectId: string | null;
  agentId: string | null;
  createdBySession: string | null;
  createdByUserId: string | null;
  status: 'pending' | 'approved' | 'rejected';
  reviewerNote: string;
  approvedRevisionId: string | null;
  createdAt: string;
  updatedAt: string;
  project?: { name: string } | null;
  agent?: { name: string } | null;
}

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

export function createReviewCommand(deps: ReviewCommandDeps): Command {
  const { client, log } = deps;

  const cmd = new Command('review').description('Triage proposed prompts and skills');

  cmd.command('pending')
    .alias('list')
    .description('List pending proposals')
    .option('--type <kind>', 'Filter by resource type: prompt or skill')
    .action(async (opts: { type?: string }) => {
      const params = new URLSearchParams({ status: 'pending' });
      if (opts.type) params.set('resourceType', opts.type);
      const proposals = await client.get<Proposal[]>(`/api/v1/proposals?${params.toString()}`);
      if (proposals.length === 0) {
        log('No pending proposals.');
        return;
      }
      log(formatTable(proposals));
    });

  cmd.command('next')
    .description('Show the oldest pending proposal')
    .option('--type <kind>', 'Filter by resource type: prompt or skill')
    .action(async (opts: { type?: string }) => {
      const params = new URLSearchParams({ status: 'pending' });
      if (opts.type) params.set('resourceType', opts.type);
      const proposals = await client.get<Proposal[]>(`/api/v1/proposals?${params.toString()}`);
      if (proposals.length === 0) {
        log('No pending proposals.');
        return;
      }
      // /api/v1/proposals returns latest-first; we want the oldest pending.
      const oldest = proposals[proposals.length - 1] as Proposal;
      log(formatDetail(oldest));
    });

  cmd.command('show')
    .description('Show full detail of a proposal')
    .argument('<id>', 'Proposal ID')
    .action(async (id: string) => {
      const proposal = await client.get<Proposal>(`/api/v1/proposals/${id}`);
      log(formatDetail(proposal));
    });

  cmd.command('approve')
    .description('Approve a pending proposal (creates the resource + initial revision)')
    .argument('<id>', 'Proposal ID')
    .action(async (id: string) => {
      const updated = await client.post<Proposal>(`/api/v1/proposals/${id}/approve`, {});
      log(`approved proposal '${updated.name}' (resourceType: ${updated.resourceType})`);
      if (updated.approvedRevisionId) {
        log(`  resulting revision: ${updated.approvedRevisionId}`);
      }
    });

  cmd.command('reject')
    .description('Reject a pending proposal with a reviewer note')
    .argument('<id>', 'Proposal ID')
    .option('--reason <text>', 'Reviewer note explaining the rejection')
    .action(async (id: string, opts: { reason?: string }) => {
      if (!opts.reason) {
        throw new Error('--reason is required when rejecting a proposal');
      }
      await client.post(`/api/v1/proposals/${id}/reject`, { reviewerNote: opts.reason });
      log(`rejected proposal ${id}`);
    });

  cmd.command('diff')
    .description('Show what would change if this proposal were approved')
    .argument('<id>', 'Proposal ID')
    .action(async (id: string) => {
      const proposal = await client.get<Proposal>(`/api/v1/proposals/${id}`);
      const proposedContent = (proposal.body as { content?: string }).content ?? '';

      // Find existing resource (if any) to diff against. Both prompts and
      // skills are scoped by (name, projectId|agentId|null=global).
      let existingContent: string | null = null;
      const projectName = proposal.project?.name;
      const agentName = proposal.agent?.name;
      try {
        if (proposal.resourceType === 'prompt') {
          const params = new URLSearchParams();
          if (projectName) params.set('project', projectName);
          const list = await client.get<Array<{ name: string; content: string }>>(`/api/v1/prompts?${params.toString()}`);
          const match = list.find((p) => p.name === proposal.name);
          if (match) existingContent = match.content;
        } else {
          const params = new URLSearchParams();
          if (projectName) params.set('project', projectName);
          else if (agentName) params.set('agent', agentName);
          const list = await client.get<Array<{ name: string; content: string }>>(`/api/v1/skills?${params.toString()}`);
          const match = list.find((s) => s.name === proposal.name);
          if (match) existingContent = match.content;
        }
      } catch {
        // 404 from no project / agent means nothing to diff against.
      }

      if (existingContent === null) {
        log(`Proposal would create a new ${proposal.resourceType} '${proposal.name}'.`);
        log('');
        log('--- proposed body ---');
        log(proposedContent);
        return;
      }

      log(`Proposal would update the existing ${proposal.resourceType} '${proposal.name}'.`);
      log('');
      log('--- current ---');
      log(existingContent);
      log('--- proposed ---');
      log(proposedContent);
    });

  return cmd;
}

// ── Formatting ──

function formatTable(proposals: Proposal[]): string {
  const lines: string[] = [];
  const idW = Math.max(2, ...proposals.map((p) => p.id.length));
  const typeW = 6; // 'skill' / 'prompt'
  const nameW = Math.max(4, ...proposals.map((p) => p.name.length));
  const scopeW = Math.max(5, ...proposals.map((p) => scopeLabel(p).length));
  const sessW = 8;

  const header = `${pad('ID', idW)}  ${pad('TYPE', typeW)}  ${pad('NAME', nameW)}  ${pad('SCOPE', scopeW)}  ${pad('SESSION', sessW)}  AGE`;
  lines.push(header);
  for (const p of proposals) {
    const age = ageOf(p.createdAt);
    lines.push(
      `${pad(p.id, idW)}  ${pad(p.resourceType, typeW)}  ${pad(p.name, nameW)}  ${pad(scopeLabel(p), scopeW)}  ${pad((p.createdBySession ?? '—').slice(0, 8), sessW)}  ${age}`,
    );
  }
  return lines.join('\n');
}

function formatDetail(p: Proposal): string {
  const lines: string[] = [];
  lines.push(`=== Proposal: ${p.name} (${p.resourceType}) ===`);
  lines.push(`ID:           ${p.id}`);
  lines.push(`Status:       ${p.status}`);
  lines.push(`Scope:        ${scopeLabel(p)}`);
  lines.push(`Created:      ${p.createdAt}  (session ${p.createdBySession ?? '—'})`);
  if (p.reviewerNote) lines.push(`Reviewer note: ${p.reviewerNote}`);
  if (p.approvedRevisionId) lines.push(`Approved as revision: ${p.approvedRevisionId}`);
  lines.push('');
  lines.push('--- body ---');
  const content = (p.body as { content?: string }).content;
  if (typeof content === 'string') {
    lines.push(content);
  } else {
    lines.push(JSON.stringify(p.body, null, 2));
  }
  return lines.join('\n');
}

function scopeLabel(p: Proposal): string {
  if (p.project?.name) return `project:${p.project.name}`;
  if (p.agent?.name) return `agent:${p.agent.name}`;
  return 'global';
}

function pad(s: string, w: number): string {
  if (s.length >= w) return s;
  return s + ' '.repeat(w - s.length);
}

function ageOf(iso: string): string {
  const t = Date.parse(iso);
  if (Number.isNaN(t)) return '?';
  const sec = Math.floor((Date.now() - t) / 1000);
  if (sec < 60) return `${String(sec)}s`;
  const min = Math.floor(sec / 60);
  if (min < 60) return `${String(min)}m`;
  const hr = Math.floor(min / 60);
  if (hr < 24) return `${String(hr)}h`;
  const days = Math.floor(hr / 24);
  return `${String(days)}d`;
}
