/**
 * Experiments Service
 *
 * A/B testing and experiment tracking system for the staff dashboard.
 * Provides experiment CRUD, variant assignment, conversion tracking,
 * and statistical significance calculations.
 */

import { nanoid } from 'nanoid';
import type {
  Experiment,
  ExperimentVariant,
  ExperimentFilters,
  ExperimentMetrics,
  CreateExperimentRequest,
  UpdateExperimentRequest,
  SignificanceResult,
  ExperimentEvent,
  VariantAssignment,
} from '@/lib/types/experiments.types';

// ============================================================================
// In-Memory Storage (Replace with database in production)
// ============================================================================

// In a real implementation, this would use Prisma/database
// For now, using in-memory storage with realistic mock data
const experiments: Map<string, Experiment> = new Map();
const experimentEvents: ExperimentEvent[] = [];
const variantAssignments: Map<string, VariantAssignment> = new Map();

// ============================================================================
// Statistical Significance Calculation
// ============================================================================

/**
 * Calculate z-score for A/B test significance
 */
function normalCDF(z: number): number {
  const a1 = 0.254829592;
  const a2 = -0.284496736;
  const a3 = 1.421413741;
  const a4 = -1.453152027;
  const a5 = 1.061405429;
  const p = 0.3275911;

  const sign = z < 0 ? -1 : 1;
  const absZ = Math.abs(z) / Math.sqrt(2);

  const t = 1.0 / (1.0 + p * absZ);
  const y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-absZ * absZ);

  return 0.5 * (1.0 + sign * y);
}

/**
 * Calculate statistical significance between control and treatment
 */
export function calculateSignificance(
  controlConversions: number,
  controlVisitors: number,
  treatmentConversions: number,
  treatmentVisitors: number,
  minimumSample: number = 100
): SignificanceResult {
  const totalSample = controlVisitors + treatmentVisitors;

  // Not enough data
  if (controlVisitors < 10 || treatmentVisitors < 10) {
    return {
      confident: false,
      confidenceLevel: 0,
      sampleSize: totalSample,
      requiredSample: minimumSample,
      zScore: 0,
      pValue: 1,
    };
  }

  const p1 = controlConversions / controlVisitors;
  const p2 = treatmentConversions / treatmentVisitors;
  const pPooled = (controlConversions + treatmentConversions) / totalSample;

  // Avoid division by zero
  if (pPooled === 0 || pPooled === 1) {
    return {
      confident: false,
      confidenceLevel: 0,
      sampleSize: totalSample,
      requiredSample: minimumSample,
      zScore: 0,
      pValue: 1,
    };
  }

  const se = Math.sqrt(pPooled * (1 - pPooled) * (1 / controlVisitors + 1 / treatmentVisitors));

  // Avoid division by zero
  if (se === 0) {
    return {
      confident: false,
      confidenceLevel: 0,
      sampleSize: totalSample,
      requiredSample: minimumSample,
      zScore: 0,
      pValue: 1,
    };
  }

  const zScore = (p2 - p1) / se;
  const pValue = 2 * (1 - normalCDF(Math.abs(zScore)));

  // 95% confidence = z > 1.96
  const confident = Math.abs(zScore) > 1.96 && totalSample >= minimumSample;
  const confidenceLevel = (1 - pValue) * 100;

  return {
    confident,
    confidenceLevel: Math.round(confidenceLevel * 10) / 10,
    sampleSize: totalSample,
    requiredSample: minimumSample,
    zScore: Math.round(zScore * 1000) / 1000,
    pValue: Math.round(pValue * 10000) / 10000,
  };
}

// ============================================================================
// Experiment CRUD Operations
// ============================================================================

/**
 * Initialize with sample experiments for demo purposes
 */
function initializeSampleExperiments(): void {
  if (experiments.size > 0) return;

  const sampleExperiments: Experiment[] = [
    {
      id: 'exp_homepage_cta_v1',
      name: 'Homepage CTA Test',
      description: 'Testing "Get Started" vs "Book Demo" button text',
      type: 'homepage',
      status: 'running',
      variants: [
        {
          id: 'var_control',
          name: 'Control - Get Started',
          trafficPercent: 50,
          conversions: 127,
          visitors: 2341,
          conversionRate: 5.42,
        },
        {
          id: 'var_treatment',
          name: 'Treatment - Book Demo',
          trafficPercent: 50,
          conversions: 156,
          visitors: 2298,
          conversionRate: 6.79,
        },
      ],
      winner: null,
      startedAt: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(),
      endedAt: null,
      createdAt: new Date(Date.now() - 15 * 24 * 60 * 60 * 1000).toISOString(),
      updatedAt: new Date().toISOString(),
      createdBy: 'system',
      significance: calculateSignificance(127, 2341, 156, 2298, 500),
      targetMetric: 'demo_booking',
      minimumSampleSize: 500,
    },
    {
      id: 'exp_pricing_layout_v1',
      name: 'Pricing Page Layout',
      description: 'Card layout vs comparison table',
      type: 'pricing',
      status: 'running',
      variants: [
        {
          id: 'var_cards',
          name: 'Card Layout',
          trafficPercent: 50,
          conversions: 89,
          visitors: 1567,
          conversionRate: 5.68,
        },
        {
          id: 'var_table',
          name: 'Comparison Table',
          trafficPercent: 50,
          conversions: 112,
          visitors: 1523,
          conversionRate: 7.35,
        },
      ],
      winner: null,
      startedAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
      endedAt: null,
      createdAt: new Date(Date.now() - 8 * 24 * 60 * 60 * 1000).toISOString(),
      updatedAt: new Date().toISOString(),
      createdBy: 'system',
      significance: calculateSignificance(89, 1567, 112, 1523, 500),
      targetMetric: 'checkout_started',
      minimumSampleSize: 500,
    },
    {
      id: 'exp_demo_form_v1',
      name: 'Demo Form Length',
      description: 'Short form (3 fields) vs Long form (6 fields)',
      type: 'demo_flow',
      status: 'completed',
      variants: [
        {
          id: 'var_short',
          name: 'Short Form',
          trafficPercent: 50,
          conversions: 234,
          visitors: 3456,
          conversionRate: 6.77,
        },
        {
          id: 'var_long',
          name: 'Long Form',
          trafficPercent: 50,
          conversions: 189,
          visitors: 3421,
          conversionRate: 5.52,
        },
      ],
      winner: 'var_short',
      startedAt: new Date(Date.now() - 45 * 24 * 60 * 60 * 1000).toISOString(),
      endedAt: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(),
      createdAt: new Date(Date.now() - 46 * 24 * 60 * 60 * 1000).toISOString(),
      updatedAt: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(),
      createdBy: 'system',
      significance: {
        confident: true,
        confidenceLevel: 97.2,
        sampleSize: 6877,
        requiredSample: 500,
        zScore: 2.21,
        pValue: 0.027,
      },
      targetMetric: 'demo_booking',
      minimumSampleSize: 500,
    },
    {
      id: 'exp_package_pricing_v1',
      name: 'Annual Discount Display',
      description: 'Show savings as percentage vs absolute amount',
      type: 'package',
      status: 'paused',
      variants: [
        {
          id: 'var_percent',
          name: 'Save 20%',
          trafficPercent: 50,
          conversions: 45,
          visitors: 890,
          conversionRate: 5.06,
        },
        {
          id: 'var_amount',
          name: 'Save $240/year',
          trafficPercent: 50,
          conversions: 52,
          visitors: 912,
          conversionRate: 5.70,
        },
      ],
      winner: null,
      startedAt: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(),
      endedAt: null,
      createdAt: new Date(Date.now() - 11 * 24 * 60 * 60 * 1000).toISOString(),
      updatedAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
      createdBy: 'system',
      significance: calculateSignificance(45, 890, 52, 912, 500),
      targetMetric: 'checkout_completed',
      minimumSampleSize: 500,
    },
  ];

  sampleExperiments.forEach((exp) => experiments.set(exp.id, exp));
}

/**
 * Get all experiments with filtering
 */
export async function getExperiments(
  filters: ExperimentFilters = {}
): Promise<{ experiments: Experiment[]; total: number; page: number; totalPages: number }> {
  initializeSampleExperiments();

  const page = filters.page || 1;
  const limit = filters.limit || 20;

  let filtered = Array.from(experiments.values());

  // Filter by status
  if (filters.status && filters.status !== 'all') {
    filtered = filtered.filter((e) => e.status === filters.status);
  }

  // Filter by type
  if (filters.type && filters.type !== 'all') {
    filtered = filtered.filter((e) => e.type === filters.type);
  }

  // Filter by search term
  if (filters.search) {
    const searchLower = filters.search.toLowerCase();
    filtered = filtered.filter(
      (e) =>
        e.name.toLowerCase().includes(searchLower) ||
        e.description.toLowerCase().includes(searchLower)
    );
  }

  // Sort by creation date (newest first)
  filtered.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());

  const total = filtered.length;
  const totalPages = Math.ceil(total / limit);
  const startIndex = (page - 1) * limit;
  const paginated = filtered.slice(startIndex, startIndex + limit);

  return { experiments: paginated, total, page, totalPages };
}

/**
 * Get a single experiment by ID
 */
export async function getExperimentById(id: string): Promise<Experiment | null> {
  initializeSampleExperiments();
  return experiments.get(id) || null;
}

/**
 * Get experiment metrics summary
 */
export async function getExperimentMetrics(): Promise<ExperimentMetrics> {
  initializeSampleExperiments();

  const allExperiments = Array.from(experiments.values());

  const activeExperiments = allExperiments.filter((e) => e.status === 'running').length;
  const completedExperiments = allExperiments.filter((e) => e.status === 'completed').length;
  const experimentsWithWinner = allExperiments.filter((e) => e.winner !== null).length;

  // Calculate average lift from winning variants
  let totalLift = 0;
  let liftCount = 0;

  allExperiments
    .filter((e) => e.winner && e.variants.length >= 2)
    .forEach((e) => {
      const control = e.variants[0];
      const winner = e.variants.find((v) => v.id === e.winner);
      if (control && winner && control.conversionRate > 0) {
        const lift = ((winner.conversionRate - control.conversionRate) / control.conversionRate) * 100;
        totalLift += lift;
        liftCount++;
      }
    });

  const totalConversions = allExperiments.reduce(
    (sum, e) => sum + e.variants.reduce((vs, v) => vs + v.conversions, 0),
    0
  );

  return {
    totalExperiments: allExperiments.length,
    activeExperiments,
    completedExperiments,
    averageLift: liftCount > 0 ? Math.round((totalLift / liftCount) * 10) / 10 : 0,
    totalConversions,
    experimentsWithWinner,
  };
}

/**
 * Create a new experiment
 */
export async function createExperiment(
  request: CreateExperimentRequest,
  createdBy: string
): Promise<Experiment> {
  initializeSampleExperiments();

  const id = `exp_${nanoid(12)}`;
  const now = new Date().toISOString();

  // Validate traffic percentages sum to 100
  const totalTraffic = request.variants.reduce((sum, v) => sum + v.trafficPercent, 0);
  if (Math.abs(totalTraffic - 100) > 0.01) {
    throw new Error('Variant traffic percentages must sum to 100%');
  }

  const variants: ExperimentVariant[] = request.variants.map((v) => ({
    id: `var_${nanoid(8)}`,
    name: v.name,
    description: v.description,
    trafficPercent: v.trafficPercent,
    conversions: 0,
    visitors: 0,
    conversionRate: 0,
  }));

  const experiment: Experiment = {
    id,
    name: request.name,
    description: request.description,
    type: request.type,
    status: 'draft',
    variants,
    winner: null,
    startedAt: null,
    endedAt: null,
    createdAt: now,
    updatedAt: now,
    createdBy,
    significance: {
      confident: false,
      confidenceLevel: 0,
      sampleSize: 0,
      requiredSample: request.minimumSampleSize || 500,
    },
    targetMetric: request.targetMetric,
    minimumSampleSize: request.minimumSampleSize || 500,
  };

  experiments.set(id, experiment);
  return experiment;
}

/**
 * Update an experiment
 */
export async function updateExperiment(request: UpdateExperimentRequest): Promise<Experiment | null> {
  initializeSampleExperiments();

  const experiment = experiments.get(request.id);
  if (!experiment) return null;

  const now = new Date().toISOString();

  // Update status
  if (request.status) {
    experiment.status = request.status;

    // Set timestamps based on status change
    if (request.status === 'running' && !experiment.startedAt) {
      experiment.startedAt = now;
    } else if (request.status === 'completed' && !experiment.endedAt) {
      experiment.endedAt = now;
    }
  }

  // Declare winner
  if (request.winner) {
    const variant = experiment.variants.find((v) => v.id === request.winner);
    if (!variant) {
      throw new Error('Invalid variant ID for winner');
    }
    experiment.winner = request.winner;
    experiment.status = 'completed';
    experiment.endedAt = now;
  }

  // Update traffic allocation
  if (request.variants) {
    const totalTraffic = request.variants.reduce((sum, v) => sum + v.trafficPercent, 0);
    if (Math.abs(totalTraffic - 100) > 0.01) {
      throw new Error('Variant traffic percentages must sum to 100%');
    }

    request.variants.forEach((update) => {
      const variant = experiment.variants.find((v) => v.id === update.id);
      if (variant) {
        variant.trafficPercent = update.trafficPercent;
      }
    });
  }

  experiment.updatedAt = now;
  experiments.set(experiment.id, experiment);

  return experiment;
}

/**
 * Delete an experiment
 */
export async function deleteExperiment(id: string): Promise<boolean> {
  initializeSampleExperiments();
  return experiments.delete(id);
}

// ============================================================================
// Experiment Event Tracking
// ============================================================================

/**
 * Assign a user/session to a variant (deterministic based on session)
 */
export function assignVariant(
  experimentId: string,
  sessionId: string,
  userId?: string
): VariantAssignment | null {
  initializeSampleExperiments();

  const experiment = experiments.get(experimentId);
  if (!experiment || experiment.status !== 'running') return null;

  // Check for existing assignment
  const assignmentKey = `${experimentId}:${sessionId}`;
  const existing = variantAssignments.get(assignmentKey);
  if (existing) return existing;

  // Deterministic assignment based on session hash
  let hash = 0;
  for (let i = 0; i < sessionId.length; i++) {
    hash = (hash * 31 + sessionId.charCodeAt(i)) | 0;
  }
  const bucket = Math.abs(hash % 100);

  // Find variant based on traffic split
  let cumulative = 0;
  let assignedVariant: ExperimentVariant | null = null;

  for (const variant of experiment.variants) {
    cumulative += variant.trafficPercent;
    if (bucket < cumulative) {
      assignedVariant = variant;
      break;
    }
  }

  // Fallback to first variant if somehow not assigned
  if (!assignedVariant) {
    assignedVariant = experiment.variants[0];
  }

  const assignment: VariantAssignment = {
    experimentId,
    variantId: assignedVariant.id,
    userId,
    sessionId,
    assignedAt: new Date().toISOString(),
  };

  variantAssignments.set(assignmentKey, assignment);
  return assignment;
}

/**
 * Track an experiment event (view or conversion)
 */
export async function trackExperimentEvent(
  experimentId: string,
  variantId: string,
  eventType: 'view' | 'conversion',
  sessionId: string,
  userId?: string,
  metadata?: Record<string, unknown>
): Promise<ExperimentEvent | null> {
  initializeSampleExperiments();

  const experiment = experiments.get(experimentId);
  if (!experiment) return null;

  const variant = experiment.variants.find((v) => v.id === variantId);
  if (!variant) return null;

  // Update variant metrics
  if (eventType === 'view') {
    variant.visitors++;
  } else if (eventType === 'conversion') {
    variant.conversions++;
  }

  // Recalculate conversion rate
  variant.conversionRate =
    variant.visitors > 0
      ? Math.round((variant.conversions / variant.visitors) * 10000) / 100
      : 0;

  // Recalculate significance if we have at least 2 variants
  if (experiment.variants.length >= 2) {
    const control = experiment.variants[0];
    const treatment = experiment.variants[1];
    experiment.significance = calculateSignificance(
      control.conversions,
      control.visitors,
      treatment.conversions,
      treatment.visitors,
      experiment.minimumSampleSize
    );
  }

  experiment.updatedAt = new Date().toISOString();
  experiments.set(experimentId, experiment);

  const event: ExperimentEvent = {
    experimentId,
    variantId,
    userId,
    sessionId,
    eventType,
    metadata,
    createdAt: new Date().toISOString(),
  };

  experimentEvents.push(event);
  return event;
}

/**
 * Get active experiments for a specific type (for frontend assignment)
 */
export async function getActiveExperimentsByType(
  type: Experiment['type']
): Promise<Experiment[]> {
  initializeSampleExperiments();

  return Array.from(experiments.values()).filter(
    (e) => e.type === type && e.status === 'running'
  );
}
