import { loadSession, validateSession, signIn, saveSession } from '../../auth.js';
import { readMeta, writeMeta } from './daemon.js';
import { getGatewayUrl, promptLine, promptSecret } from './login.js';

interface MintedToken {
  id: string;
  label: string;
  plaintext: string;
}

/**
 * Call POST /api/admin/tokens with the session cookie and return the minted token.
 * Exits the process on network or auth errors.
 */
export async function mintAdminToken(
  gatewayUrl: string,
  cookie: string,
  label: string,
): Promise<MintedToken> {
  let res: Response;
  try {
    res = await fetch(`${gatewayUrl}/api/admin/tokens`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Cookie: cookie,
        Origin: gatewayUrl,
      },
      body: JSON.stringify({ label, scope: 'admin' }),
    });
  } 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()}) — your session may be expired.`,
    );
    console.error('Run: mosaic gateway login');
    process.exit(2);
  }

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

  const data = (await res.json()) as { id: string; label: string; plaintext: string };
  return { id: data.id, label: data.label, plaintext: data.plaintext };
}

/**
 * Persist the new token into meta.json and print the confirmation banner.
 *
 * Emits a warning when the target gateway differs from the locally installed one,
 * so operators are aware that meta.json may not reflect the intended gateway.
 */
export function persistToken(gatewayUrl: string, minted: MintedToken): void {
  const meta = readMeta() ?? {
    version: 'unknown',
    installedAt: new Date().toISOString(),
    entryPoint: '',
    host: new URL(gatewayUrl).hostname,
    port: parseInt(new URL(gatewayUrl).port || '14242', 10),
  };

  // Warn when the target gateway does not match the locally installed one
  const targetHost = new URL(gatewayUrl).hostname;
  if (targetHost !== meta.host) {
    console.warn(
      `Warning: token was minted against ${gatewayUrl} but is being saved to the local` +
        ` meta.json (host: ${meta.host}). Copy the token manually if targeting a remote gateway.`,
    );
  }

  writeMeta({ ...meta, adminToken: minted.plaintext });

  const preview = `${minted.plaintext.slice(0, 8)}...`;
  console.log();
  console.log(`Token minted:  ${minted.label}`);
  console.log(`Preview:       ${preview}`);
  console.log('Token saved to meta.json. Use it with admin endpoints.');
}

/**
 * Require a valid session for the given gateway URL.
 * Returns the session cookie or exits if not authenticated.
 */
export async function requireSession(gatewayUrl: string): Promise<string> {
  const session = loadSession(gatewayUrl);
  if (session) {
    const valid = await validateSession(gatewayUrl, session.cookie);
    if (valid) return session.cookie;
  }
  console.error('Not signed in or session expired.');
  console.error('Run: mosaic gateway login');
  process.exit(2);
}

/**
 * Ensure a valid session for the gateway, prompting for credentials if needed.
 * On sign-in failure, prints the error and exits non-zero.
 * Returns the session cookie.
 */
export async function ensureSession(gatewayUrl: string): Promise<string> {
  // Try the stored session first
  const session = loadSession(gatewayUrl);
  if (session) {
    const valid = await validateSession(gatewayUrl, session.cookie);
    if (valid) return session.cookie;
    console.log('Stored session is invalid or expired. Please sign in again.');
  } else {
    console.log(`No session found for ${gatewayUrl}. Please sign in.`);
  }

  // Prompt for credentials — password must not be echoed to the terminal
  const email = await promptLine('Email: ');
  // Do not trim password — it may contain intentional leading/trailing whitespace
  const password = await promptSecret('Password: ');

  const auth = await signIn(gatewayUrl, email, password).catch((err: unknown) => {
    console.error(err instanceof Error ? err.message : String(err));
    process.exit(2);
  });

  saveSession(gatewayUrl, auth);
  console.log(`Signed in as ${auth.email}`);
  return auth.cookie;
}

/**
 * `mosaic gateway config rotate-token` — requires an existing valid session.
 */
export async function runRotateToken(gatewayUrl?: string): Promise<void> {
  const url = getGatewayUrl(gatewayUrl);
  const cookie = await requireSession(url);
  const label = `CLI rotated token (${new Date().toISOString().slice(0, 10)})`;
  const minted = await mintAdminToken(url, cookie, label);
  persistToken(url, minted);
}

/**
 * `mosaic gateway config recover-token` — prompts for login if no session exists.
 */
export async function runRecoverToken(gatewayUrl?: string): Promise<void> {
  const url = getGatewayUrl(gatewayUrl);
  const cookie = await ensureSession(url);
  const label = `CLI recovery token (${new Date().toISOString().slice(0, 16).replace('T', ' ')})`;
  const minted = await mintAdminToken(url, cookie, label);
  persistToken(url, minted);
}
