import { randomUUID } from 'node:crypto';
import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import type { Sql } from '../db.js';
import { VIRTUAL_MODELS } from '../services/gateway.js';
import { jsonbStringArray } from '../services/jsonb.js';

/**
 * P7.4: Route policy CRUD + dispatch log.
 *
 * GET    /api/policies              — list policies
 * POST   /api/policies             — create/update a policy (upsert by virtual_model)
 * DELETE /api/policies/:id          — delete a policy
 * GET    /api/policies/dispatch-log — recent gateway dispatches
 * GET    /api/policies/virtual-models — the available virtual model tokens
 */
export function registerPolicyRoutes(app: FastifyInstance, sql: Sql): void {
  app.get('/api/policies/virtual-models', async (_req: FastifyRequest, reply: FastifyReply) => {
    return reply.send({ virtualModels: VIRTUAL_MODELS });
  });

  app.get('/api/policies', async (_req: FastifyRequest, reply: FastifyReply) => {
    const rows = await sql<{
      id: string;
      name: string;
      virtual_model: string;
      candidates: string;
      fallback: string | null;
      enabled: boolean;
      created_at: string;
      updated_at: string;
    }[]>`
      SELECT id, name, virtual_model, candidates, fallback, enabled, created_at, updated_at
      FROM route_policies
      ORDER BY virtual_model
    `;
    return reply.send({
      policies: rows.map((r) => ({
        id: r.id,
        name: r.name,
        virtualModel: r.virtual_model,
        candidates: safeParseArray(r.candidates),
        fallback: r.fallback,
        enabled: r.enabled,
        createdAt: r.created_at,
        updatedAt: r.updated_at,
      })),
    });
  });

  app.post('/api/policies', async (req: FastifyRequest, reply: FastifyReply) => {
    const body = req.body as Record<string, unknown>;
    const id = (body.id as string) ?? randomUUID();
    const name = body.name as string;
    const virtualModel = body.virtualModel as string;
    const candidates = body.candidates as unknown;
    const fallback = (body.fallback as string) ?? null;
    const enabled = body.enabled !== false;

    if (!name || !virtualModel) {
      return reply.status(400).send({ error: 'name and virtualModel are required' });
    }
    if (!(VIRTUAL_MODELS as readonly string[]).includes(virtualModel)) {
      return reply.status(400).send({ error: `virtualModel must be one of ${VIRTUAL_MODELS.join(', ')}` });
    }
    const candidateList = Array.isArray(candidates)
      ? candidates.filter((c): c is string => typeof c === 'string')
      : [];

    // Upsert by virtual_model (UNIQUE) so there is one policy per virtual model.
    await sql`
      INSERT INTO route_policies (id, name, virtual_model, candidates, fallback, enabled, updated_at)
      VALUES (${id}, ${name}, ${virtualModel}, ${sql.json(candidateList as never)}, ${fallback}, ${enabled}, clock_timestamp())
      ON CONFLICT (virtual_model) DO UPDATE SET
        name = EXCLUDED.name,
        candidates = EXCLUDED.candidates,
        fallback = EXCLUDED.fallback,
        enabled = EXCLUDED.enabled,
        updated_at = clock_timestamp()
    `;
    return reply.status(201).send({ id });
  });

  app.delete('/api/policies/:id', async (req: FastifyRequest, reply: FastifyReply) => {
    const { id } = req.params as { id: string };
    await sql`DELETE FROM route_policies WHERE id = ${id}`;
    return reply.send({ ok: true });
  });

  app.get('/api/policies/dispatch-log', async (req: FastifyRequest, reply: FastifyReply) => {
    const query = req.query as Record<string, string | undefined>;
    const virtualModel = query.virtualModel;

    const rows = virtualModel
      ? await sql<DispatchLogRow[]>`
          SELECT id, ts, virtual_model, chosen_provider_id, chosen_model, candidates_tried, status, source, error, duration_ms
          FROM route_dispatch_log WHERE virtual_model = ${virtualModel}
          ORDER BY ts DESC LIMIT 200
        `
      : await sql<DispatchLogRow[]>`
          SELECT id, ts, virtual_model, chosen_provider_id, chosen_model, candidates_tried, status, source, error, duration_ms
          FROM route_dispatch_log
          ORDER BY ts DESC LIMIT 200
        `;

    return reply.send({
      dispatches: rows.map((r) => ({
        id: r.id,
        ts: r.ts,
        virtualModel: r.virtual_model,
        chosenProviderId: r.chosen_provider_id,
        chosenModel: r.chosen_model,
        candidatesTried: safeParseArray(r.candidates_tried),
        status: r.status,
        source: r.source,
        error: r.error,
        durationMs: r.duration_ms,
      })),
    });
  });
}

interface DispatchLogRow {
  id: number;
  ts: string;
  virtual_model: string;
  chosen_provider_id: string | null;
  chosen_model: string | null;
  candidates_tried: unknown;
  status: string;
  source: string | null;
  error: string | null;
  duration_ms: number | null;
}

// jsonb columns come back parsed from porsager; jsonbStringArray tolerates both.
const safeParseArray = jsonbStringArray;
