/**
 * A/B Testing (Experiments) routes.
 * Admin: CRUD experiments. Authenticated users: get their variant assignment.
 */

import type { FastifyInstance } from 'fastify';
import { UnauthorizedError, ForbiddenError, NotFoundError } from '../../lib/errors.js';
import { getRequestProductId } from '../../lib/request-context.js';
import { CreateExperimentSchema, UpdateExperimentSchema } from './types.js';
import {
  listExperiments,
  getExperiment,
  createExperiment,
  updateExperiment,
  deleteExperiment,
  getOrCreateAssignment,
  listAssignmentsForExperiment,
} from './repository.js';

function requireAuth(req: { jwtPayload?: { sub: string; role?: string } }): string {
  if (!req.jwtPayload?.sub) throw new UnauthorizedError('Authentication required');
  return req.jwtPayload.sub;
}

function requireAdmin(req: { jwtPayload?: { sub: string; role?: string } }): void {
  requireAuth(req);
  if (req.jwtPayload?.role !== 'admin') throw new ForbiddenError('Admin access required');
}

export async function experimentRoutes(app: FastifyInstance): Promise<void> {
  // ── Admin: List experiments ────────────────────────────────
  app.get('/experiments', async req => {
    requireAdmin(req);
    const productId = getRequestProductId(req);
    return listExperiments(productId);
  });

  // ── Admin: Get experiment ─────────────────────────────────
  app.get<{ Params: { id: string } }>('/experiments/:id', async req => {
    requireAdmin(req);
    const experiment = await getExperiment(req.params.id);
    if (!experiment) throw new NotFoundError('Experiment not found');
    return experiment;
  });

  // ── Admin: Create experiment ──────────────────────────────
  app.post('/experiments', async (req, reply) => {
    requireAdmin(req);
    const productId = getRequestProductId(req);
    const input = CreateExperimentSchema.parse(req.body);
    const experiment = await createExperiment(productId, input);
    reply.status(201);
    return experiment;
  });

  // ── Admin: Update experiment ──────────────────────────────
  app.put<{ Params: { id: string } }>('/experiments/:id', async req => {
    requireAdmin(req);
    const updates = UpdateExperimentSchema.parse(req.body);
    const experiment = await updateExperiment(req.params.id, updates);
    if (!experiment) throw new NotFoundError('Experiment not found');
    return experiment;
  });

  // ── Admin: Delete experiment ──────────────────────────────
  app.delete<{ Params: { id: string } }>('/experiments/:id', async (req, reply) => {
    requireAdmin(req);
    const ok = await deleteExperiment(req.params.id);
    if (!ok) throw new NotFoundError('Experiment not found');
    reply.status(204);
  });

  // ── Admin: List assignments for experiment ────────────────
  app.get<{ Params: { id: string } }>('/experiments/:id/assignments', async req => {
    requireAdmin(req);
    return listAssignmentsForExperiment(req.params.id);
  });

  // ── User: Get my variant for an experiment ────────────────
  app.get<{ Params: { key: string } }>('/experiments/assign/:key', async req => {
    const userId = requireAuth(req);
    const productId = getRequestProductId(req);
    const experiments = await listExperiments(productId);
    const experiment = experiments.find(e => e.key === req.params.key && e.status === 'running');
    if (!experiment) throw new NotFoundError('Experiment not found or not running');
    const assignment = await getOrCreateAssignment(experiment, userId);
    return { experimentKey: experiment.key, variant: assignment.variantKey };
  });
}
