import db from '../config/database';
import { logger } from '../utils/logger';

export interface ABTest {
  id: string;
  tenantId: string;
  shopId: string;
  taskId: string;
  traceId: string;
  businessType: 'TOC' | 'TOB';
  name: string;
  description?: string;
  status: 'DRAFT' | 'RUNNING' | 'PAUSED' | 'COMPLETED' | 'ARCHIVED';
  targetMetric: 'CONVERSION' | 'REVENUE' | 'CTR' | 'BOUNCE_RATE' | 'CUSTOM';
  customMetricName?: string;
  startDate: Date;
  endDate?: Date;
  minSampleSize: number;
  confidenceLevel: number;
  variants: ABTestVariant[];
  trafficAllocation: TrafficAllocation;
  createdAt: Date;
  updatedAt: Date;
}

export interface ABTestVariant {
  id: string;
  testId: string;
  name: string;
  description?: string;
  control: boolean;
  trafficPercentage: number;
  config: Record<string, any>;
}

export interface TrafficAllocation {
  type: 'RANDOM' | 'USER_ID' | 'SESSION' | 'COOKIE';
  salt?: string;
}

export interface CreateABTestInput {
  tenantId: string;
  shopId: string;
  taskId: string;
  traceId: string;
  businessType: 'TOC' | 'TOB';
  name: string;
  description?: string;
  targetMetric: 'CONVERSION' | 'REVENUE' | 'CTR' | 'BOUNCE_RATE' | 'CUSTOM';
  customMetricName?: string;
  startDate: Date;
  endDate?: Date;
  minSampleSize?: number;
  confidenceLevel?: number;
  variants: CreateVariantInput[];
  trafficAllocation: TrafficAllocation;
}

export interface CreateVariantInput {
  name: string;
  description?: string;
  control?: boolean;
  trafficPercentage: number;
  config: Record<string, any>;
}

export interface TrafficAllocationInput {
  tenantId: string;
  shopId: string;
  taskId: string;
  traceId: string;
  businessType: 'TOC' | 'TOB';
  testId: string;
  userId: string;
  sessionId?: string;
}

export interface TrafficAllocationResult {
  testId: string;
  userId: string;
  assignedVariant: string;
  variantName: string;
  isControl: boolean;
  assignedAt: Date;
}

export interface ABTestStatisticsInput {
  tenantId: string;
  shopId: string;
  taskId: string;
  traceId: string;
  businessType: 'TOC' | 'TOB';
  testId: string;
  startDate?: Date;
  endDate?: Date;
}

export interface ABTestStatistics {
  testId: string;
  testName: string;
  status: string;
  totalParticipants: number;
  variantStats: VariantStatistics[];
  winner?: string;
  confidenceInterval: number;
  isStatisticallySignificant: boolean;
  generatedAt: Date;
}

export interface VariantStatistics {
  variantId: string;
  variantName: string;
  isControl: boolean;
  participants: number;
  conversions: number;
  conversionRate: number;
  revenue?: number;
  averageOrderValue?: number;
  improvementOverControl?: number;
  confidenceInterval?: [number, number];
  pValue?: number;
}

export class ABTestService {
  static async createABTest(input: CreateABTestInput): Promise<ABTest> {
    const {
      tenantId, shopId, taskId, traceId, businessType,
      name, description, targetMetric, customMetricName,
      startDate, endDate, minSampleSize, confidenceLevel,
      variants, trafficAllocation
    } = input;

    logger.info(`[ABTestService] Creating A/B test - name: ${name}, tenantId: ${tenantId}, traceId: ${traceId}`);

    const testId = `ABT-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;

    try {
      await db('cf_ab_tests').insert({
        id: testId,
        tenant_id: tenantId,
        shop_id: shopId,
        task_id: taskId,
        trace_id: traceId,
        business_type: businessType,
        name,
        description,
        status: 'DRAFT',
        target_metric: targetMetric,
        custom_metric_name: customMetricName,
        start_date: startDate,
        end_date: endDate,
        min_sample_size: minSampleSize || 1000,
        confidence_level: confidenceLevel || 0.95,
        traffic_allocation_type: trafficAllocation.type,
        traffic_allocation_salt: trafficAllocation.salt,
        created_at: new Date(),
        updated_at: new Date(),
      });

      const controlVariant = variants.find(v => v.control) || variants[0];
      const totalTraffic = variants.reduce((sum, v) => sum + v.trafficPercentage, 0);
      
      if (totalTraffic !== 100) {
        throw new Error(`Variant traffic percentages must sum to 100, got ${totalTraffic}`);
      }

      for (const variant of variants) {
        const variantId = `ABV-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
        await db('cf_ab_test_variants').insert({
          id: variantId,
          test_id: testId,
          name: variant.name,
          description: variant.description,
          control: variant.control || false,
          traffic_percentage: variant.trafficPercentage,
          config: JSON.stringify(variant.config),
        });
      }

      const createdTest = await db('cf_ab_tests')
        .where('id', testId)
        .first();

      const createdVariants = await db('cf_ab_test_variants')
        .where('test_id', testId);

      logger.info(`[ABTestService] A/B test created successfully - testId: ${testId}, variants: ${createdVariants.length}`);

      return {
        ...createdTest,
        variants: createdVariants.map(v => ({
          ...v,
          config: JSON.parse(v.config),
        })),
        trafficAllocation: {
          type: createdTest.traffic_allocation_type,
          salt: createdTest.traffic_allocation_salt,
        },
      };
    } catch (error: any) {
      logger.error(`[ABTestService] Failed to create A/B test - error: ${error.message}`);
      throw error;
    }
  }

  static async allocateTraffic(input: TrafficAllocationInput): Promise<TrafficAllocationResult> {
    const { tenantId, shopId, taskId, traceId, businessType, testId, userId, sessionId } = input;

    logger.info(`[ABTestService] Allocating traffic - testId: ${testId}, userId: ${userId}, traceId: ${traceId}`);

    try {
      const test = await db('cf_ab_tests')
        .where({ id: testId, tenant_id: tenantId, shop_id: shopId })
        .first();

      if (!test) {
        throw new Error(`A/B test not found: ${testId}`);
      }

      if (test.status !== 'RUNNING') {
        throw new Error(`A/B test is not running: ${testId}, current status: ${test.status}`);
      }

      const existingAssignment = await db('cf_ab_test_assignments')
        .where({ test_id: testId, user_id: userId })
        .first();

      if (existingAssignment) {
        const variant = await db('cf_ab_test_variants')
          .where('id', existingAssignment.variant_id)
          .first();

        return {
          testId,
          userId,
          assignedVariant: variant.id,
          variantName: variant.name,
          isControl: variant.control,
          assignedAt: existingAssignment.assigned_at,
        };
      }

      const variants = await db('cf_ab_test_variants')
        .where('test_id', testId)
        .orderBy('traffic_percentage', 'desc');

      const hash = this.generateHash(testId, userId, test.traffic_allocation_salt);
      const bucket = hash % 100;

      let cumulativePercentage = 0;
      let selectedVariant = variants[0];

      for (const variant of variants) {
        cumulativePercentage += variant.traffic_percentage;
        if (bucket < cumulativePercentage) {
          selectedVariant = variant;
          break;
        }
      }

      await db('cf_ab_test_assignments').insert({
        id: `ABA-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
        tenant_id: tenantId,
        shop_id: shopId,
        task_id: taskId,
        trace_id: traceId,
        business_type: businessType,
        test_id: testId,
        user_id: userId,
        session_id: sessionId,
        variant_id: selectedVariant.id,
        assigned_at: new Date(),
      });

      logger.info(`[ABTestService] Traffic allocated - userId: ${userId}, variant: ${selectedVariant.name}`);

      return {
        testId,
        userId,
        assignedVariant: selectedVariant.id,
        variantName: selectedVariant.name,
        isControl: selectedVariant.control,
        assignedAt: new Date(),
      };
    } catch (error: any) {
      logger.error(`[ABTestService] Traffic allocation failed - error: ${error.message}`);
      throw error;
    }
  }

  static async calculateStatistics(input: ABTestStatisticsInput): Promise<ABTestStatistics> {
    const { tenantId, shopId, taskId, traceId, businessType, testId, startDate, endDate } = input;

    logger.info(`[ABTestService] Calculating statistics - testId: ${testId}, traceId: ${traceId}`);

    try {
      const test = await db('cf_ab_tests')
        .where({ id: testId, tenant_id: tenantId, shop_id: shopId })
        .first();

      if (!test) {
        throw new Error(`A/B test not found: ${testId}`);
      }

      const variants = await db('cf_ab_test_variants')
        .where('test_id', testId);

      const controlVariant = variants.find(v => v.control);

      let query = db('cf_ab_test_assignments')
        .where('test_id', testId);

      if (startDate) {
        query = query.where('assigned_at', '>=', startDate);
      }
      if (endDate) {
        query = query.where('assigned_at', '<=', endDate);
      }

      const assignments = await query;
      const totalParticipants = assignments.length;

      const variantStats: VariantStatistics[] = [];

      for (const variant of variants) {
        const variantAssignments = assignments.filter(a => a.variant_id === variant.id);
        const participants = variantAssignments.length;

        const conversions = await db('cf_ab_test_conversions')
          .whereIn('assignment_id', variantAssignments.map(a => a.id))
          .count('id as count')
          .first();

        const conversionCount = parseInt(conversions?.count?.toString() || '0');
        const conversionRate = participants > 0 ? conversionCount / participants : 0;

        const stats: VariantStatistics = {
          variantId: variant.id,
          variantName: variant.name,
          isControl: variant.control,
          participants,
          conversions: conversionCount,
          conversionRate: Number(conversionRate.toFixed(4)),
        };

        if (controlVariant && !variant.control) {
          const controlStats = variantStats.find(v => v.isControl);
          if (controlStats && controlStats.conversionRate > 0) {
            stats.improvementOverControl = Number(
              ((conversionRate - controlStats.conversionRate) / controlStats.conversionRate * 100).toFixed(2)
            );
          }
        }

        variantStats.push(stats);
      }

      let winner: string | undefined;
      let isStatisticallySignificant = false;

      if (controlVariant) {
        const controlStats = variantStats.find(v => v.isControl);
        if (controlStats) {
          const bestVariant = variantStats
            .filter(v => !v.isControl)
            .sort((a, b) => (b.conversionRate || 0) - (a.conversionRate || 0))[0];

          if (bestVariant && bestVariant.improvementOverControl && bestVariant.improvementOverControl > 0) {
            winner = bestVariant.variantId;
            isStatisticallySignificant = totalParticipants >= test.min_sample_size;
          }
        }
      }

      logger.info(`[ABTestService] Statistics calculated - testId: ${testId}, participants: ${totalParticipants}`);

      return {
        testId,
        testName: test.name,
        status: test.status,
        totalParticipants,
        variantStats,
        winner,
        confidenceInterval: test.confidence_level,
        isStatisticallySignificant,
        generatedAt: new Date(),
      };
    } catch (error: any) {
      logger.error(`[ABTestService] Statistics calculation failed - error: ${error.message}`);
      throw error;
    }
  }

  private static generateHash(testId: string, userId: string, salt?: string): number {
    const str = `${testId}:${userId}:${salt || ''}`;
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      const char = str.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash;
    }
    return Math.abs(hash);
  }

  static async initializeTables(): Promise<void> {
    if (!(await db.schema.hasTable('cf_ab_tests'))) {
      await db.schema.createTable('cf_ab_tests', (table) => {
        table.string('id').primary();
        table.string('tenant_id').notNullable();
        table.string('shop_id').notNullable();
        table.string('task_id').notNullable();
        table.string('trace_id').notNullable();
        table.enum('business_type', ['TOC', 'TOB']).notNullable();
        table.string('name').notNullable();
        table.text('description');
        table.enum('status', ['DRAFT', 'RUNNING', 'PAUSED', 'COMPLETED', 'ARCHIVED']).defaultTo('DRAFT');
        table.enum('target_metric', ['CONVERSION', 'REVENUE', 'CTR', 'BOUNCE_RATE', 'CUSTOM']).notNullable();
        table.string('custom_metric_name');
        table.timestamp('start_date').notNullable();
        table.timestamp('end_date');
        table.integer('min_sample_size').defaultTo(1000);
        table.decimal('confidence_level', 3, 2).defaultTo(0.95);
        table.enum('traffic_allocation_type', ['RANDOM', 'USER_ID', 'SESSION', 'COOKIE']).notNullable();
        table.string('traffic_allocation_salt');
        table.timestamp('created_at').defaultTo(db.fn.now());
        table.timestamp('updated_at').defaultTo(db.fn.now());
        
        table.index(['tenant_id', 'shop_id']);
        table.index(['status']);
        table.index(['target_metric']);
      });
      logger.info('[ABTestService] Created cf_ab_tests table');
    }

    if (!(await db.schema.hasTable('cf_ab_test_variants'))) {
      await db.schema.createTable('cf_ab_test_variants', (table) => {
        table.string('id').primary();
        table.string('test_id').notNullable();
        table.string('name').notNullable();
        table.text('description');
        table.boolean('control').defaultTo(false);
        table.integer('traffic_percentage').notNullable();
        table.json('config').notNullable();
        
        table.foreign('test_id').references('cf_ab_tests.id');
        table.index(['test_id']);
      });
      logger.info('[ABTestService] Created cf_ab_test_variants table');
    }

    if (!(await db.schema.hasTable('cf_ab_test_assignments'))) {
      await db.schema.createTable('cf_ab_test_assignments', (table) => {
        table.string('id').primary();
        table.string('tenant_id').notNullable();
        table.string('shop_id').notNullable();
        table.string('task_id').notNullable();
        table.string('trace_id').notNullable();
        table.enum('business_type', ['TOC', 'TOB']).notNullable();
        table.string('test_id').notNullable();
        table.string('user_id').notNullable();
        table.string('session_id');
        table.string('variant_id').notNullable();
        table.timestamp('assigned_at').defaultTo(db.fn.now());
        
        table.foreign('test_id').references('cf_ab_tests.id');
        table.foreign('variant_id').references('cf_ab_test_variants.id');
        table.index(['test_id', 'user_id']);
        table.index(['test_id']);
        table.index(['user_id']);
      });
      logger.info('[ABTestService] Created cf_ab_test_assignments table');
    }

    if (!(await db.schema.hasTable('cf_ab_test_conversions'))) {
      await db.schema.createTable('cf_ab_test_conversions', (table) => {
        table.string('id').primary();
        table.string('assignment_id').notNullable();
        table.string('user_id').notNullable();
        table.decimal('revenue', 10, 2);
        table.json('metadata');
        table.timestamp('converted_at').defaultTo(db.fn.now());
        
        table.foreign('assignment_id').references('cf_ab_test_assignments.id');
        table.index(['assignment_id']);
        table.index(['converted_at']);
      });
      logger.info('[ABTestService] Created cf_ab_test_conversions table');
    }
  }
}
