/**
 * `mosaic federation` command group — federation grant + peer management (FED-M2-08).
 *
 * All HTTP calls go to the local gateway admin API using an admin token
 * resolved from CLI options or meta.json.
 *
 * Subcommands:
 *   grant create --peer-id <uuid> --user-id <uuid> --scope <json> [--expires-at <iso>]
 *   grant list   [--peer-id <uuid>] [--user-id <uuid>] [--status pending|active|revoked|expired]
 *   grant show   <id>
 *   grant revoke <id> [--reason <text>]
 *   grant token  <id> [--ttl 900]
 *
 *   peer list
 *   peer add <enrollment-url>
 */

import type { Command } from 'commander';
import { readMeta } from './gateway/daemon.js';

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

interface FedParentOpts {
  host: string;
  port: string;
  token?: string;
  json?: boolean;
}

interface ResolvedOpts {
  baseUrl: string;
  token?: string;
  json: boolean;
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function resolveOpts(raw: FedParentOpts): ResolvedOpts {
  const meta = readMeta();
  const host = raw.host ?? meta?.host ?? 'localhost';
  const port = parseInt(raw.port, 10) || meta?.port || 14242;
  const token = raw.token ?? meta?.adminToken;
  return {
    baseUrl: `http://${host}:${port.toString()}`,
    token,
    json: raw.json ?? false,
  };
}

function requireToken(opts: ResolvedOpts): string {
  if (!opts.token) {
    console.error(
      'Error: admin token required. Use -t/--token <token> or ensure meta.json has adminToken.',
    );
    process.exit(1);
  }
  return opts.token;
}

async function apiRequest<T>(
  opts: ResolvedOpts,
  method: string,
  path: string,
  body?: unknown,
): Promise<T> {
  const token = requireToken(opts);
  const url = `${opts.baseUrl}${path}`;

  const res = await fetch(url, {
    method,
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${token}`,
    },
    body: body !== undefined ? JSON.stringify(body) : undefined,
  });

  const text = await res.text();

  if (!res.ok) {
    let message = text;
    try {
      const parsed = JSON.parse(text) as { message?: string };
      message = parsed.message ?? text;
    } catch {
      // use raw text
    }
    throw new Error(`HTTP ${res.status.toString()}: ${message}`);
  }

  if (!text) return undefined as unknown as T;
  return JSON.parse(text) as T;
}

function printJson(data: unknown, useJson: boolean): void {
  if (useJson) {
    console.log(JSON.stringify(data, null, 2));
  }
}

function printTable(rows: Record<string, unknown>[]): void {
  if (rows.length === 0) {
    console.log('(none)');
    return;
  }
  for (const row of rows) {
    for (const [key, val] of Object.entries(row)) {
      console.log(`  ${key}: ${String(val ?? '')}`);
    }
    console.log('');
  }
}

// ---------------------------------------------------------------------------
// Command registration
// ---------------------------------------------------------------------------

export function registerFederationCommand(program: Command): void {
  const fed = program
    .command('federation')
    .alias('fed')
    .description('Manage federation grants and peers')
    .option('-h, --host <host>', 'Gateway host', 'localhost')
    .option('-p, --port <port>', 'Gateway port', '14242')
    .option('-t, --token <token>', 'Admin token')
    .option('--json', 'Machine-readable JSON output')
    .action(() => fed.outputHelp());

  // ─── grant subcommands ─────────────────────────────────────────────────

  const grant = fed
    .command('grant')
    .description('Manage federation grants')
    .action(() => grant.outputHelp());

  grant
    .command('create')
    .description('Create a new federation grant')
    .requiredOption('--peer-id <uuid>', 'Peer UUID')
    .requiredOption('--user-id <uuid>', 'Subject user UUID')
    .requiredOption('--scope <json>', 'Grant scope as JSON string')
    .option('--expires-at <iso>', 'Optional expiry (ISO 8601)')
    .action(
      async (cmdOpts: { peerId: string; userId: string; scope: string; expiresAt?: string }) => {
        const opts = resolveOpts(fed.opts() as FedParentOpts);
        try {
          let scope: Record<string, unknown>;
          try {
            scope = JSON.parse(cmdOpts.scope) as Record<string, unknown>;
          } catch {
            console.error('Error: --scope must be valid JSON');
            process.exit(1);
          }

          const body: Record<string, unknown> = {
            peerId: cmdOpts.peerId,
            subjectUserId: cmdOpts.userId,
            scope,
          };
          if (cmdOpts.expiresAt) body['expiresAt'] = cmdOpts.expiresAt;

          const result = await apiRequest<Record<string, unknown>>(
            opts,
            'POST',
            '/api/admin/federation/grants',
            body,
          );

          if (opts.json) {
            printJson(result, true);
          } else {
            console.log(`Grant created: ${String(result['id'])}`);
            console.log(`  Peer:   ${String(result['peerId'])}`);
            console.log(`  User:   ${String(result['subjectUserId'])}`);
            console.log(`  Status: ${String(result['status'])}`);
          }
        } catch (err) {
          console.error(err instanceof Error ? err.message : String(err));
          process.exit(1);
        }
      },
    );

  grant
    .command('list')
    .description('List federation grants')
    .option('--peer-id <uuid>', 'Filter by peer UUID')
    .option('--user-id <uuid>', 'Filter by subject user UUID')
    .option('--status <status>', 'Filter by status (pending|active|revoked|expired)')
    .action(async (cmdOpts: { peerId?: string; userId?: string; status?: string }) => {
      const opts = resolveOpts(fed.opts() as FedParentOpts);
      try {
        const params = new URLSearchParams();
        if (cmdOpts.peerId) params.set('peerId', cmdOpts.peerId);
        if (cmdOpts.userId) params.set('subjectUserId', cmdOpts.userId);
        if (cmdOpts.status) params.set('status', cmdOpts.status);

        const qs = params.toString() ? `?${params.toString()}` : '';
        const result = await apiRequest<Record<string, unknown>[]>(
          opts,
          'GET',
          `/api/admin/federation/grants${qs}`,
        );

        if (opts.json) {
          printJson(result, true);
        } else {
          console.log(`Grants (${result.length.toString()}):\n`);
          printTable(result);
        }
      } catch (err) {
        console.error(err instanceof Error ? err.message : String(err));
        process.exit(1);
      }
    });

  grant
    .command('show <id>')
    .description('Get a single grant by ID')
    .action(async (id: string) => {
      const opts = resolveOpts(fed.opts() as FedParentOpts);
      try {
        const result = await apiRequest<Record<string, unknown>>(
          opts,
          'GET',
          `/api/admin/federation/grants/${id}`,
        );

        if (opts.json) {
          printJson(result, true);
        } else {
          for (const [key, val] of Object.entries(result)) {
            console.log(`  ${key}: ${String(val ?? '')}`);
          }
        }
      } catch (err) {
        console.error(err instanceof Error ? err.message : String(err));
        process.exit(1);
      }
    });

  grant
    .command('revoke <id>')
    .description('Revoke an active grant')
    .option('--reason <text>', 'Revocation reason')
    .action(async (id: string, cmdOpts: { reason?: string }) => {
      const opts = resolveOpts(fed.opts() as FedParentOpts);
      try {
        const body: Record<string, unknown> = {};
        if (cmdOpts.reason) body['reason'] = cmdOpts.reason;

        const result = await apiRequest<Record<string, unknown>>(
          opts,
          'PATCH',
          `/api/admin/federation/grants/${id}/revoke`,
          body,
        );

        if (opts.json) {
          printJson(result, true);
        } else {
          console.log(`Grant ${id} revoked.`);
          if (result['revokedReason']) console.log(`  Reason: ${String(result['revokedReason'])}`);
        }
      } catch (err) {
        console.error(err instanceof Error ? err.message : String(err));
        process.exit(1);
      }
    });

  grant
    .command('token <id>')
    .description('Generate a single-use enrollment token for a grant')
    .option('--ttl <seconds>', 'Token lifetime in seconds (60-900)', '900')
    .action(async (id: string, cmdOpts: { ttl: string }) => {
      const opts = resolveOpts(fed.opts() as FedParentOpts);
      try {
        const ttlSeconds = parseInt(cmdOpts.ttl, 10) || 900;
        const result = await apiRequest<{
          token: string;
          expiresAt: string;
          enrollmentUrl: string;
        }>(opts, 'POST', `/api/admin/federation/grants/${id}/tokens`, { ttlSeconds });

        if (opts.json) {
          printJson(result, true);
        } else {
          console.log('Enrollment token generated:');
          console.log(`  Token:         ${result.token}`);
          console.log(`  Expires at:    ${result.expiresAt}`);
          console.log(`  Enrollment URL: ${result.enrollmentUrl}`);
          console.log('');
          console.log('Share the enrollment URL with the remote peer operator.');
        }
      } catch (err) {
        console.error(err instanceof Error ? err.message : String(err));
        process.exit(1);
      }
    });

  // ─── peer subcommands ──────────────────────────────────────────────────

  const peer = fed
    .command('peer')
    .description('Manage federation peers')
    .action(() => peer.outputHelp());

  peer
    .command('list')
    .description('List all federation peers')
    .action(async () => {
      const opts = resolveOpts(fed.opts() as FedParentOpts);
      try {
        const result = await apiRequest<Record<string, unknown>[]>(
          opts,
          'GET',
          '/api/admin/federation/peers',
        );

        if (opts.json) {
          printJson(result, true);
        } else {
          console.log(`Peers (${result.length.toString()}):\n`);
          printTable(result);
        }
      } catch (err) {
        console.error(err instanceof Error ? err.message : String(err));
        process.exit(1);
      }
    });

  peer
    .command('add <enrollment-url>')
    .description('Enroll as a peer using a remote enrollment URL')
    .action(async (enrollmentUrl: string) => {
      const opts = resolveOpts(fed.opts() as FedParentOpts);
      try {
        // 1. Validate enrollment URL
        let parsedUrl: URL;
        try {
          parsedUrl = new URL(enrollmentUrl);
        } catch {
          console.error(`Error: invalid enrollment URL: ${enrollmentUrl}`);
          process.exit(1);
        }

        if (parsedUrl.protocol !== 'https:' && parsedUrl.protocol !== 'http:') {
          console.error('Error: enrollment URL must use http or https');
          process.exit(1);
        }

        const hostname = parsedUrl.hostname;
        const commonName = hostname.replace(/\./g, '-');

        console.log(`Enrolling as peer with remote: ${enrollmentUrl}`);
        console.log(`  Common name: ${commonName}`);

        // 2. Generate key pair and CSR via local gateway
        console.log('Generating key pair and CSR...');
        const keypairResult = await apiRequest<{ peerId: string; csrPem: string }>(
          opts,
          'POST',
          '/api/admin/federation/peers/keypair',
          { commonName, displayName: hostname },
        );

        const { peerId, csrPem } = keypairResult;
        console.log(`  Peer ID: ${peerId}`);

        // 3. Submit CSR to remote enrollment endpoint
        console.log('Submitting CSR to remote enrollment endpoint...');
        const remoteRes = await fetch(enrollmentUrl, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ csrPem }),
        });

        if (!remoteRes.ok) {
          const errText = await remoteRes.text();
          throw new Error(`Remote enrollment failed (${remoteRes.status.toString()}): ${errText}`);
        }

        const remoteResult = (await remoteRes.json()) as { certPem: string; certChainPem: string };

        if (!remoteResult.certPem) {
          throw new Error('Remote enrollment response missing certPem');
        }

        // 4. Store the signed certificate in the local gateway
        console.log('Storing signed certificate...');
        await apiRequest<Record<string, unknown>>(
          opts,
          'PATCH',
          `/api/admin/federation/peers/${peerId}/cert`,
          { certPem: remoteResult.certPem },
        );

        console.log(`\nPeer enrolled successfully.`);
        console.log(`  ID:    ${peerId}`);
        console.log(`  State: active`);
      } catch (err) {
        console.error(err instanceof Error ? err.message : String(err));
        process.exit(1);
      }
    });
}
