import { Hono } from 'hono';
import { decryptSecret, encryptSecret } from '../crypto/aes';
import { computeTotpCode, normalizeBase32Secret } from '../crypto/totp';
import {
  ensureShareTokenForAccount,
  newShareTokenForInsert,
} from '../lib/ensureShareToken';

type AccountRow = {
  id: string;
  label: string;
  issuer: string | null;
  encrypted_secret: string;
  algorithm: string;
  digits: number;
  period: number;
  created_at: number;
  sort_order: number;
  share_token: string | null;
};

function parseAccountBody(body: unknown): {
  label: string;
  issuer: string | null;
  secret: string;
  algorithm: string;
  digits: number;
  period: number;
} | null {
  if (!body || typeof body !== 'object') return null;
  const o = body as Record<string, unknown>;
  const label = typeof o.label === 'string' ? o.label.trim() : '';
  const issuer =
    typeof o.issuer === 'string' && o.issuer.trim()
      ? o.issuer.trim()
      : null;
  const secretRaw = typeof o.secret === 'string' ? o.secret.trim() : '';
  const algorithm =
    typeof o.algorithm === 'string' && o.algorithm.trim()
      ? o.algorithm.trim()
      : 'SHA1';
  const digits =
    typeof o.digits === 'number' && Number.isFinite(o.digits)
      ? Math.min(10, Math.max(6, Math.floor(o.digits)))
      : 6;
  const period =
    typeof o.period === 'number' && Number.isFinite(o.period)
      ? Math.min(120, Math.max(10, Math.floor(o.period)))
      : 30;
  if (!label || !secretRaw) return null;
  const secret = normalizeBase32Secret(secretRaw);
  if (secret.length < 4) return null;
  return { label, issuer, secret: secretRaw, algorithm, digits, period };
}

export function registerAccountRoutes(authed: Hono<{ Bindings: Env }>): void {
  authed.get('/accounts', async (c) => {
    const { results } = await c.env.DB.prepare(
      `SELECT id, label, issuer, encrypted_secret, algorithm, digits, period, created_at, sort_order, share_token
       FROM totp_accounts
       ORDER BY sort_order ASC, created_at ASC`,
    ).all<AccountRow>();

    const list = [];
    const now = Date.now();
    for (const row of results ?? []) {
      let secret: string;
      try {
        secret = await decryptSecret(c.env.MASTER_KEY, row.encrypted_secret);
      } catch {
        continue;
      }
      const { code, expiresInSeconds } = await computeTotpCode(
        normalizeBase32Secret(secret),
        {
          digits: row.digits,
          period: row.period,
          algorithm: row.algorithm,
          nowMs: now,
        },
      );
      const shareToken = await ensureShareTokenForAccount(
        c.env.DB,
        row.id,
        row.share_token,
      );
      list.push({
        id: row.id,
        label: row.label,
        issuer: row.issuer,
        algorithm: row.algorithm,
        digits: row.digits,
        period: row.period,
        code,
        expiresInSeconds,
        shareToken,
      });
    }
    return c.json({ accounts: list });
  });

  authed.post('/accounts', async (c) => {
    let body: unknown;
    try {
      body = await c.req.json();
    } catch {
      return c.json({ error: '无效的 JSON' }, 400);
    }
    const parsed = parseAccountBody(body);
    if (!parsed) {
      return c.json({ error: '缺少 label 或有效的 secret' }, 400);
    }
    const normalized = normalizeBase32Secret(parsed.secret);
    try {
      await computeTotpCode(normalized, {
        digits: parsed.digits,
        period: parsed.period,
        algorithm: parsed.algorithm,
      });
    } catch (e) {
      const msg = e instanceof Error ? e.message : '密钥无法生成验证码';
      return c.json({ error: msg }, 400);
    }

    const id = crypto.randomUUID();
    const now = Math.floor(Date.now() / 1000);
    const maxRow = await c.env.DB.prepare(
      'SELECT COALESCE(MAX(sort_order), -1) as m FROM totp_accounts',
    ).first<{ m: number }>();
    const sortOrder = (maxRow?.m ?? -1) + 1;

    const enc = await encryptSecret(c.env.MASTER_KEY, normalized);
    const shareToken = await newShareTokenForInsert(c.env.DB);

    await c.env.DB.prepare(
      `INSERT INTO totp_accounts (id, label, issuer, encrypted_secret, algorithm, digits, period, created_at, sort_order, share_token)
       VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
    )
      .bind(
        id,
        parsed.label,
        parsed.issuer,
        enc,
        parsed.algorithm.toUpperCase(),
        parsed.digits,
        parsed.period,
        now,
        sortOrder,
        shareToken,
      )
      .run();

    return c.json({ id, ok: true });
  });

  authed.patch('/accounts/:id', async (c) => {
    const id = c.req.param('id');
    let body: Record<string, unknown>;
    try {
      body = await c.req.json();
    } catch {
      return c.json({ error: '无效的 JSON' }, 400);
    }
    const row = await c.env.DB.prepare(
      `SELECT id, label, issuer, encrypted_secret, algorithm, digits, period
       FROM totp_accounts WHERE id = ?`,
    )
      .bind(id)
      .first<AccountRow>();
    if (!row) return c.json({ error: '未找到' }, 404);

    const updates: string[] = [];
    const values: unknown[] = [];

    if (typeof body.label === 'string' && body.label.trim()) {
      updates.push('label = ?');
      values.push(body.label.trim());
    }
    if (body.issuer !== undefined) {
      updates.push('issuer = ?');
      values.push(
        typeof body.issuer === 'string' && body.issuer.trim()
          ? body.issuer.trim()
          : null,
      );
    }
    if (typeof body.algorithm === 'string' && body.algorithm.trim()) {
      updates.push('algorithm = ?');
      values.push(body.algorithm.trim().toUpperCase());
    }
    if (typeof body.digits === 'number' && Number.isFinite(body.digits)) {
      updates.push('digits = ?');
      values.push(Math.min(10, Math.max(6, Math.floor(body.digits))));
    }
    if (typeof body.period === 'number' && Number.isFinite(body.period)) {
      updates.push('period = ?');
      values.push(Math.min(120, Math.max(10, Math.floor(body.period))));
    }
    if (typeof body.sort_order === 'number' && Number.isFinite(body.sort_order)) {
      updates.push('sort_order = ?');
      values.push(Math.floor(body.sort_order));
    }

    const nextAlgorithm =
      typeof body.algorithm === 'string' && body.algorithm.trim()
        ? body.algorithm.trim().toUpperCase()
        : row.algorithm;
    const nextDigits =
      typeof body.digits === 'number' && Number.isFinite(body.digits)
        ? Math.min(10, Math.max(6, Math.floor(body.digits)))
        : row.digits;
    const nextPeriod =
      typeof body.period === 'number' && Number.isFinite(body.period)
        ? Math.min(120, Math.max(10, Math.floor(body.period)))
        : row.period;

    if (typeof body.secret === 'string' && body.secret.trim()) {
      const normalized = normalizeBase32Secret(body.secret.trim());
      if (normalized.length < 4) {
        return c.json({ error: '无效的 secret' }, 400);
      }
      try {
        await computeTotpCode(normalized, {
          digits: nextDigits,
          period: nextPeriod,
          algorithm: nextAlgorithm,
        });
      } catch (e) {
        const msg = e instanceof Error ? e.message : '密钥无法生成验证码';
        return c.json({ error: msg }, 400);
      }
      const enc = await encryptSecret(c.env.MASTER_KEY, normalized);
      updates.push('encrypted_secret = ?');
      values.push(enc);
    } else if (
      body.algorithm !== undefined ||
      body.digits !== undefined ||
      body.period !== undefined
    ) {
      let secret: string;
      try {
        secret = await decryptSecret(c.env.MASTER_KEY, row.encrypted_secret);
      } catch {
        return c.json({ error: '无法读取现有密钥' }, 500);
      }
      try {
        await computeTotpCode(normalizeBase32Secret(secret), {
          digits: nextDigits,
          period: nextPeriod,
          algorithm: nextAlgorithm,
        });
      } catch (e) {
        const msg = e instanceof Error ? e.message : '参数无法生成验证码';
        return c.json({ error: msg }, 400);
      }
    }

    if (updates.length === 0) return c.json({ ok: true });

    const sql = `UPDATE totp_accounts SET ${updates.join(', ')} WHERE id = ?`;
    values.push(id);
    await c.env.DB.prepare(sql)
      .bind(...values)
      .run();
    return c.json({ ok: true });
  });

  authed.delete('/accounts/:id', async (c) => {
    const id = c.req.param('id');
    await c.env.DB.prepare('DELETE FROM totp_accounts WHERE id = ?')
      .bind(id)
      .run();
    return c.json({ ok: true });
  });
}
