/* eslint-disable @typescript-eslint/no-unused-vars */
/**
 * A/B Testing — REST API Routes.
 * Admin CRUD, user assignment, event tracking, results, suggestions.
 */

import type { FastifyInstance } from 'fastify';
import {
  UnauthorizedError,
  ForbiddenError,
  NotFoundError,
  BadRequestError,
} from '../../lib/errors.js';
import { getRequestProductId } from '../../lib/request-context.js';
import type { TargetingContext } from './targeting.js';
import {
  CreateExperimentSchema,
  UpdateExperimentSchema,
  TrackEventSchema,
  AdjustAllocationSchema,
  type ExperimentDoc,
} from './types.js';
import {
  listExperiments,
  getExperiment,
  createExperiment,
  updateExperiment,
  deleteExperiment,
  listRunningExperiments,
  listVariants,
  getVariant,
  getOrCreateAssignment,
  trackEvent,
  updateVariantAllocation,
  updateVariantStats,
  listSuggestions,
  updateVariantBayesianResults,
} from './repository.js';
import {
  generateExperimentResult,
  checkEarlyStopping,
  calculateCredibleInterval,
  probabilityVariantBeatsControl,
} from './statistics.js';
import { evaluateAutoPromotion } from './guardrails.js';
import { matchesTargeting } from './targeting.js';

// ─────────────────────────────────────────────────────────────────────────────
// Auth Helpers
// ─────────────────────────────────────────────────────────────────────────────

interface JwtPayload {
  sub: string;
  role?: string;
}

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

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

// ─────────────────────────────────────────────────────────────────────────────
// Routes
// ─────────────────────────────────────────────────────────────────────────────

export async function abTestingRoutes(app: FastifyInstance): Promise<void> {
  // ───────────────────────────────────────────────────────────────────────────
  // Admin: Experiment Management
  // ───────────────────────────────────────────────────────────────────────────

  // List experiments
  app.get('/ab-testing/experiments', async req => {
    requireAdmin(req);
    const productId = getRequestProductId(req);
    return listExperiments(productId);
  });

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

    // Include variants
    const variants = await listVariants(req.params.id);
    return { ...experiment, variants };
  });

  // Create experiment
  app.post('/ab-testing/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;
  });

  // Update experiment
  app.patch<{ Params: { id: string } }>('/ab-testing/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;
  });

  // Delete experiment
  app.delete<{ Params: { id: string } }>('/ab-testing/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: Experiment Lifecycle
  // ───────────────────────────────────────────────────────────────────────────

  // Start experiment
  app.post<{ Params: { id: string } }>('/ab-testing/experiments/:id/start', async req => {
    requireAdmin(req);
    const experiment = await updateExperiment(req.params.id, { status: 'running' });
    if (!experiment) throw new NotFoundError('Experiment not found');
    return experiment;
  });

  // Pause experiment
  app.post<{ Params: { id: string } }>('/ab-testing/experiments/:id/pause', async req => {
    requireAdmin(req);
    const experiment = await updateExperiment(req.params.id, { status: 'paused' });
    if (!experiment) throw new NotFoundError('Experiment not found');
    return experiment;
  });

  // Stop experiment
  app.post<{ Params: { id: string } }>('/ab-testing/experiments/:id/stop', async req => {
    requireAdmin(req);
    const experiment = await updateExperiment(req.params.id, { status: 'stopped' });
    if (!experiment) throw new NotFoundError('Experiment not found');
    return experiment;
  });

  // Complete experiment with winner
  app.post<{ Params: { id: string }; Body: { winnerVariantId: string } }>(
    '/ab-testing/experiments/:id/complete',
    async req => {
      requireAdmin(req);
      const { winnerVariantId } = req.body;
      const experiment = await getExperiment(req.params.id);
      if (!experiment) throw new NotFoundError('Experiment not found');

      const variants = await listVariants(req.params.id);
      const winner = variants.find(v => v.id === winnerVariantId);
      if (!winner) throw new BadRequestError('Invalid winner variant ID');

      // Update all variants to 0% except winner to 100%
      for (const v of variants) {
        await updateVariantAllocation(v.id, req.params.id, v.id === winnerVariantId ? 100 : 0);
      }

      const updated = await updateExperiment(req.params.id, { status: 'completed' });
      return { experiment: updated, winner };
    }
  );

  // Adjust traffic allocation
  app.post<{ Params: { id: string } }>('/ab-testing/experiments/:id/allocation', async req => {
    requireAdmin(req);
    const { variantId, newAllocationPercent } = AdjustAllocationSchema.parse(req.body);
    await updateVariantAllocation(variantId, req.params.id, newAllocationPercent);
    return { success: true };
  });

  // ───────────────────────────────────────────────────────────────────────────
  // User: Assignment
  // ───────────────────────────────────────────────────────────────────────────

  // Get variant assignment for user
  app.post<{ Body: { experimentKey: string; context?: TargetingContext } }>(
    '/ab-testing/assign',
    async req => {
      const userId = requireAuth(req);
      const productId = getRequestProductId(req);
      const { experimentKey, context = {} } = req.body;

      // Find running experiment by key
      const experiments = await listRunningExperiments(productId);
      const experiment = experiments.find(e => e.name === experimentKey || e.id === experimentKey);

      if (!experiment) {
        return { assigned: false, reason: 'Experiment not found or not running' };
      }

      // Check targeting
      if (!matchesTargeting(context, experiment.targeting)) {
        return { assigned: false, reason: 'User does not match targeting criteria' };
      }

      const result = await getOrCreateAssignment(experiment, userId, context);

      if (!result) {
        return { assigned: false, reason: 'Not enrolled in experiment' };
      }

      return {
        assigned: true,
        experimentId: experiment.id,
        variantId: result.variant.id,
        variantName: result.variant.name,
        isControl: result.variant.isControl,
        flagConfig: result.variant.flagConfig,
        isNew: result.isNew,
      };
    }
  );

  // Batch assignment for multiple experiments
  app.post<{ Body: { experimentKeys: string[]; context?: TargetingContext } }>(
    '/ab-testing/assign/batch',
    async req => {
      const userId = requireAuth(req);
      const productId = getRequestProductId(req);
      const { experimentKeys, context = {} } = req.body;

      const results: Record<string, unknown> = {};

      for (const key of experimentKeys) {
        const experiments = await listRunningExperiments(productId);
        const experiment = experiments.find(e => e.name === key || e.id === key);

        if (!experiment) {
          results[key] = { assigned: false, reason: 'Experiment not found' };
          continue;
        }

        const result = await getOrCreateAssignment(experiment, userId, context);

        if (!result) {
          results[key] = { assigned: false, reason: 'Not enrolled' };
        } else {
          results[key] = {
            assigned: true,
            variantId: result.variant.id,
            variantName: result.variant.name,
            isControl: result.variant.isControl,
            flagConfig: result.variant.flagConfig,
          };
        }
      }

      return { assignments: results };
    }
  );

  // ───────────────────────────────────────────────────────────────────────────
  // Event Tracking
  // ───────────────────────────────────────────────────────────────────────────

  // Track experiment event
  app.post<{
    Body: {
      experimentId: string;
      metricName: string;
      metricType: string;
      value: number;
      converted?: boolean;
      eventMetadata?: Record<string, unknown>;
    };
  }>('/ab-testing/events', async (req, reply) => {
    const userId = requireAuth(req);
    const input = TrackEventSchema.parse(req.body);

    const experiment = await getExperiment(input.experimentId);
    if (!experiment) throw new NotFoundError('Experiment not found');
    if (experiment.status !== 'running') {
      throw new BadRequestError('Experiment is not running');
    }

    // Get assignment
    const result = await getOrCreateAssignment(experiment, userId, { platform: input.platform });
    if (!result) throw new BadRequestError('User not assigned to experiment');

    await trackEvent(
      input.experimentId,
      userId,
      result.assignment.id,
      result.variant.id,
      input.metricName,
      input.metricType,
      input.value,
      input.converted ?? true,
      input.platform,
      input.appVersion,
      input.eventMetadata
    );

    // Update variant primary metric if matches
    if (input.metricName === experiment.primaryMetric.name) {
      const currentConversions = result.variant.stats.conversions ?? 0;
      const updatedConversions = currentConversions + (input.converted ? 1 : 0);
      const updatedParticipants = Math.max(result.variant.stats.participants || 1, 1);
      await updateVariantStats(result.variant.id, experiment.id, {
        conversions: updatedConversions,
        conversionRate: updatedConversions / updatedParticipants,
        primaryMetricValue: input.value,
        // Update Beta posterior for conversions
        betaAlpha: updatedConversions + 1,
        betaBeta: updatedParticipants - updatedConversions + 1,
      });
    }

    reply.status(201);
    return { tracked: true };
  });

  // ───────────────────────────────────────────────────────────────────────────
  // Results & Statistics
  // ───────────────────────────────────────────────────────────────────────────

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

    const variants = await listVariants(req.params.id);
    const daysRunning = experiment.startedAt
      ? Math.floor((Date.now() - new Date(experiment.startedAt).getTime()) / (1000 * 60 * 60 * 24))
      : 0;

    return generateExperimentResult(experiment, variants, daysRunning);
  });

  // Check early stopping
  app.get<{ Params: { id: string } }>('/ab-testing/experiments/:id/stop-check', async req => {
    requireAdmin(req);
    const experiment = await getExperiment(req.params.id);
    if (!experiment) throw new NotFoundError('Experiment not found');

    const variants = await listVariants(req.params.id);
    const daysRunning = experiment.startedAt
      ? Math.floor((Date.now() - new Date(experiment.startedAt).getTime()) / (1000 * 60 * 60 * 24))
      : 0;

    const earlyStop = checkEarlyStopping(experiment, variants, daysRunning);
    const autoPromo = evaluateAutoPromotion(
      experiment,
      variants,
      daysRunning,
      experiment.primaryMetric.type === 'revenue'
    );

    return {
      shouldStop: earlyStop.shouldStop,
      reason: earlyStop.reason,
      confidence: earlyStop.confidence,
      winnerVariantId: earlyStop.winnerVariantId,
      canAutoPromote: autoPromo.canPromote,
      violations: autoPromo.violations,
      warnings: autoPromo.warnings,
    };
  });

  // Get variant statistics
  app.get<{ Params: { experimentId: string; variantId: string } }>(
    '/ab-testing/experiments/:experimentId/variants/:variantId/stats',
    async req => {
      requireAdmin(req);
      const experiment = await getExperiment(req.params.experimentId);
      if (!experiment) throw new NotFoundError('Experiment not found');

      const variant = await getVariant(req.params.variantId, req.params.experimentId);
      if (!variant) throw new NotFoundError('Variant not found');

      const credibleInterval = calculateCredibleInterval(variant, experiment.primaryMetric.type);
      const controlVariant = (await listVariants(req.params.experimentId)).find(v => v.isControl);

      const probBeatsControl = controlVariant
        ? probabilityVariantBeatsControl(variant, controlVariant, experiment.primaryMetric.type)
        : 0.5;

      return {
        variant,
        credibleInterval,
        probabilityBeatsControl: probBeatsControl,
      };
    }
  );

  // ───────────────────────────────────────────────────────────────────────────
  // AI Suggestions
  // ───────────────────────────────────────────────────────────────────────────

  // List AI-generated experiment suggestions
  app.get('/ab-testing/suggestions', async req => {
    requireAdmin(req);
    const productId = getRequestProductId(req);
    return listSuggestions(productId);
  });

  // Generate hypothesis from pattern (placeholder for LLM integration)
  app.post<{ Body: { featureName: string; pattern: string } }>(
    '/ab-testing/hypotheses',
    async req => {
      requireAdmin(req);
      const { featureName, pattern } = req.body;

      // Placeholder: In real implementation, call Azure OpenAI
      const hypothesis = {
        primary: `Changing ${featureName} will improve user engagement based on observed ${pattern}`,
        alternatives: [
          `Alternative: Simplify ${featureName} for faster completion`,
          `Alternative: Add visual cues to ${featureName}`,
        ],
        expectedEffectSize: 0.05,
        successMetric: 'conversion_rate',
        riskAssessment: 'low' as const,
        impactScore: 75,
        difficultyScore: 30,
        powerPrediction: 85,
      };

      return { hypothesis, featureName, pattern };
    }
  );
}
