import type { Command } from 'commander';
import { getGatewayUrl } from './gateway/login.js';
import { requireSession } from './gateway/token-ops.js';

// ─── Types ───────────────────────────────────────────────────────────────────

interface UserDto {
  id: string;
  name: string;
  email: string;
  role: string;
  banned: boolean;
  banReason: string | null;
  createdAt: string;
  updatedAt: string;
}

interface UserListDto {
  users: UserDto[];
  total: number;
}

// ─── HTTP helpers ────────────────────────────────────────────────────────────

async function adminGet<T>(gatewayUrl: string, cookie: string, path: string): Promise<T> {
  let res: Response;
  try {
    res = await fetch(`${gatewayUrl}${path}`, {
      headers: { Cookie: cookie, Origin: gatewayUrl },
    });
  } catch (err) {
    console.error(
      `Could not reach gateway at ${gatewayUrl}: ${err instanceof Error ? err.message : String(err)}`,
    );
    process.exit(1);
  }

  if (res.status === 401 || res.status === 403) {
    console.error(`Session rejected by the gateway (${res.status.toString()}).`);
    console.error('Run: mosaic gateway login');
    process.exit(2);
  }

  if (!res.ok) {
    const body = await res.text().catch(() => '');
    console.error(`Gateway returned error (${res.status.toString()}): ${body.slice(0, 200)}`);
    process.exit(3);
  }

  return res.json() as Promise<T>;
}

async function adminPost<T>(
  gatewayUrl: string,
  cookie: string,
  path: string,
  body: unknown,
): Promise<T> {
  let res: Response;
  try {
    res = await fetch(`${gatewayUrl}${path}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Cookie: cookie,
        Origin: gatewayUrl,
      },
      body: JSON.stringify(body),
    });
  } catch (err) {
    console.error(
      `Could not reach gateway at ${gatewayUrl}: ${err instanceof Error ? err.message : String(err)}`,
    );
    process.exit(1);
  }

  if (res.status === 401 || res.status === 403) {
    console.error(`Session rejected by the gateway (${res.status.toString()}).`);
    console.error('Run: mosaic gateway login');
    process.exit(2);
  }

  if (!res.ok) {
    const body = await res.text().catch(() => '');
    console.error(`Gateway returned error (${res.status.toString()}): ${body.slice(0, 200)}`);
    process.exit(3);
  }

  return res.json() as Promise<T>;
}

async function adminDelete(gatewayUrl: string, cookie: string, path: string): Promise<void> {
  let res: Response;
  try {
    res = await fetch(`${gatewayUrl}${path}`, {
      method: 'DELETE',
      headers: { Cookie: cookie, Origin: gatewayUrl },
    });
  } catch (err) {
    console.error(
      `Could not reach gateway at ${gatewayUrl}: ${err instanceof Error ? err.message : String(err)}`,
    );
    process.exit(1);
  }

  if (res.status === 401 || res.status === 403) {
    console.error(`Session rejected by the gateway (${res.status.toString()}).`);
    console.error('Run: mosaic gateway login');
    process.exit(2);
  }

  if (!res.ok && res.status !== 204) {
    const body = await res.text().catch(() => '');
    console.error(`Gateway returned error (${res.status.toString()}): ${body.slice(0, 200)}`);
    process.exit(3);
  }
}

// ─── Formatters ──────────────────────────────────────────────────────────────

function printUser(u: UserDto): void {
  console.log(`  ID:        ${u.id}`);
  console.log(`  Name:      ${u.name}`);
  console.log(`  Email:     ${u.email}`);
  console.log(`  Role:      ${u.role}`);
  console.log(`  Banned:    ${u.banned ? `yes (${u.banReason ?? 'no reason'})` : 'no'}`);
  console.log(`  Created:   ${new Date(u.createdAt).toLocaleString()}`);
  console.log('');
}

// ─── Register function ───────────────────────────────────────────────────────

/**
 * Register `mosaic auth` subcommands on an existing Commander program.
 *
 * Location rationale: placed in packages/mosaic rather than packages/auth because
 * the CLI needs session helpers (loadSession, validateSession, requireSession)
 * and gateway URL resolution (getGatewayUrl) that live in packages/mosaic.
 * Keeping packages/auth as a pure server-side library avoids adding commander
 * and CLI tooling as dependencies there.
 */
export function registerAuthCommand(parent: Command): void {
  const auth = parent
    .command('auth')
    .description('Manage gateway authentication, users, SSO providers, and sessions')
    .configureHelp({ sortSubcommands: true })
    .action(() => {
      auth.outputHelp();
    });

  // ─── users ──────────────────────────────────────────────────────────────

  const users = auth
    .command('users')
    .description('Manage gateway users')
    .configureHelp({ sortSubcommands: true })
    .action(() => {
      users.outputHelp();
    });

  users
    .command('list')
    .description('List all users on the gateway')
    .option('-g, --gateway <url>', 'Gateway URL')
    .option('-l, --limit <n>', 'Maximum number of users to display', '100')
    .action(async (opts: { gateway?: string; limit: string }) => {
      const url = getGatewayUrl(opts.gateway);
      const cookie = await requireSession(url);
      const limit = parseInt(opts.limit, 10);

      const result = await adminGet<UserListDto>(url, cookie, '/api/admin/users');

      const subset = result.users.slice(0, limit);
      if (subset.length === 0) {
        console.log('No users found.');
        return;
      }

      console.log(`Users (${subset.length.toString()} of ${result.total.toString()}):\n`);
      for (const u of subset) {
        printUser(u);
      }
    });

  users
    .command('create')
    .description('Create a new gateway user (interactive prompts)')
    .option('-g, --gateway <url>', 'Gateway URL')
    .action(async (opts: { gateway?: string }) => {
      const url = getGatewayUrl(opts.gateway);
      const cookie = await requireSession(url);

      const {
        text,
        password: clackPassword,
        select,
        intro,
        outro,
        isCancel,
      } = await import('@clack/prompts');

      intro('Create a new Mosaic gateway user');

      const name = await text({ message: 'Full name:', placeholder: 'Jane Doe' });
      if (isCancel(name)) {
        outro('Cancelled.');
        process.exit(0);
      }

      const email = await text({ message: 'Email:', placeholder: 'jane@example.com' });
      if (isCancel(email)) {
        outro('Cancelled.');
        process.exit(0);
      }

      const pw = await clackPassword({ message: 'Password:' });
      if (isCancel(pw)) {
        outro('Cancelled.');
        process.exit(0);
      }

      const role = await select({
        message: 'Role:',
        options: [
          { value: 'member', label: 'member' },
          { value: 'admin', label: 'admin' },
        ],
      });
      if (isCancel(role)) {
        outro('Cancelled.');
        process.exit(0);
      }

      const created = await adminPost<UserDto>(url, cookie, '/api/admin/users', {
        name: name as string,
        email: email as string,
        password: pw as string,
        role: role as string,
      });

      outro(`User created: ${created.email} (${created.id})`);
    });

  users
    .command('delete <id>')
    .description('Delete a gateway user by ID')
    .option('-g, --gateway <url>', 'Gateway URL')
    .option('-y, --yes', 'Skip confirmation prompt')
    .action(async (id: string, opts: { gateway?: string; yes?: boolean }) => {
      const url = getGatewayUrl(opts.gateway);
      const cookie = await requireSession(url);

      if (!opts.yes) {
        const { confirm, isCancel } = await import('@clack/prompts');
        const confirmed = await confirm({
          message: `Delete user ${id}? This cannot be undone.`,
        });
        if (isCancel(confirmed) || !confirmed) {
          console.log('Aborted.');
          process.exit(0);
        }
      }

      await adminDelete(url, cookie, `/api/admin/users/${id}`);
      console.log(`User ${id} deleted.`);
    });

  // ─── sso ────────────────────────────────────────────────────────────────

  const sso = auth
    .command('sso')
    .description('Manage SSO provider configuration')
    .configureHelp({ sortSubcommands: true })
    .action(() => {
      sso.outputHelp();
    });

  sso
    .command('list')
    .description('List configured SSO providers (reads gateway discovery endpoint if available)')
    .option('-g, --gateway <url>', 'Gateway URL')
    .action(async (opts: { gateway?: string }) => {
      // The admin SSO discovery endpoint is not yet wired server-side.
      // The buildSsoDiscovery helper in @mosaicstack/auth reads env-vars on the
      // server; there is no GET /api/admin/sso endpoint in apps/gateway/src/admin/.
      // Stub until a gateway admin route is wired.
      console.log(
        'not yet wired — admin endpoint missing (GET /api/admin/sso not implemented server-side)',
      );
      console.log(
        'Hint: SSO providers are configured via environment variables (AUTHENTIK_*, WORKOS_*, KEYCLOAK_*).',
      );
      // Suppress unused variable warning
      void opts;
    });

  sso
    .command('test <provider>')
    .description('Smoke-test a configured SSO provider')
    .option('-g, --gateway <url>', 'Gateway URL')
    .action(async (provider: string, opts: { gateway?: string }) => {
      // No server-side SSO smoke-test endpoint exists yet.
      console.log(
        `not yet wired — admin endpoint missing (POST /api/admin/sso/${provider}/test not implemented server-side)`,
      );
      void opts;
    });

  // ─── sessions ────────────────────────────────────────────────────────────

  const authSessions = auth
    .command('sessions')
    .description('Manage BetterAuth user sessions stored on the gateway')
    .configureHelp({ sortSubcommands: true })
    .action(() => {
      authSessions.outputHelp();
    });

  authSessions
    .command('list')
    .description('List active user sessions')
    .option('-g, --gateway <url>', 'Gateway URL')
    .action(async (opts: { gateway?: string }) => {
      // No GET /api/admin/auth-sessions endpoint exists in apps/gateway/src/admin/.
      // Stub until a gateway admin route is wired.
      console.log(
        'not yet wired — admin endpoint missing (GET /api/admin/auth-sessions not implemented server-side)',
      );
      void opts;
    });
}
