/**
 * A/B Testing Framework
 *
 * Manages feature flags and variant assignment for experiments.
 * Uses consistent hashing to ensure players always get the same variant.
 *
 * Ported from Python ws/experiments/ab_testing.py
 *
 * NOTE: All experiments are DISABLED by default. Enable specific experiments
 * by setting their `enabled` flag to true when ready to run tests.
 */

import crypto from 'crypto';

// ============================================================
// Types
// ============================================================

export type VariantName = 'control' | 'treatment' | 'treatment_a' | 'treatment_b';

export interface FeatureFlag {
  enabled: boolean;
  variants: Record<string, number>;
  description: string;
}

export type FeatureFlags = Record<string, FeatureFlag>;

// ============================================================
// Feature Flags Configuration
// ============================================================

/**
 * All feature flags for A/B testing.
 * NOTE: All experiments are DISABLED by default for safety.
 * Enable specific experiments when ready to run tests.
 */
export const FEATURE_FLAGS: FeatureFlags = {
  // Tutorial experiments
  tutorial_flow: {
    enabled: false, // DISABLED - enable when ready to test
    variants: {
      control: 0.5,      // 50% get original tutorial
      treatment: 0.5,    // 50% get new tutorial flow
    },
    description: 'Test new tutorial flow effectiveness',
  },

  // Monetization experiments
  premium_pricing: {
    enabled: false, // DISABLED - enable when ready to test
    variants: {
      control: 0.33,      // $4.99
      treatment_a: 0.33,  // $2.99
      treatment_b: 0.34,  // $6.99
    },
    description: 'Test different premium subscription prices',
  },

  // Dating system experiments
  dating_match_algorithm: {
    enabled: false, // DISABLED
    variants: {
      control: 0.5,      // Original compatibility algorithm
      treatment: 0.5,    // Enhanced AI-based matching
    },
    description: 'Test enhanced dating match algorithm',
  },

  // Retention experiments
  daily_reward_amount: {
    enabled: false, // DISABLED - enable when ready to test
    variants: {
      control: 0.5,      // Standard rewards
      treatment: 0.5,    // Increased rewards
    },
    description: 'Test impact of increased daily rewards on retention',
  },

  // Engagement experiments
  notification_frequency: {
    enabled: false, // DISABLED - enable when ready to test
    variants: {
      control: 0.33,      // Standard notifications
      treatment_a: 0.33,  // More frequent
      treatment_b: 0.34,  // Less frequent
    },
    description: 'Test optimal notification frequency',
  },

  // UI/UX experiments
  conversation_ui: {
    enabled: false, // DISABLED
    variants: {
      control: 0.5,      // Original UI
      treatment: 0.5,    // New conversation interface
    },
    description: 'Test new conversation UI design',
  },

  // Game mechanics experiments
  energy_regeneration: {
    enabled: false, // DISABLED - enable when ready to test
    variants: {
      control: 0.5,      // Standard regeneration rate
      treatment: 0.5,    // Faster regeneration
    },
    description: 'Test faster energy regeneration impact',
  },
};

// ============================================================
// A/B Testing Manager Class
// ============================================================

/**
 * Manages A/B test variant assignment and tracking.
 */
export class ABTestingManager {
  private featureFlags: FeatureFlags;
  private cache: Map<string, string>;

  constructor(featureFlags?: FeatureFlags) {
    this.featureFlags = featureFlags ?? { ...FEATURE_FLAGS };
    this.cache = new Map();
  }

  /**
   * Get the assigned variant for a player and feature flag.
   * Uses consistent hashing to ensure same player always gets same variant.
   */
  getVariant(playerId: string, flagName: string): string {
    // Check cache first
    const cacheKey = `${playerId}:${flagName}`;
    const cached = this.cache.get(cacheKey);
    if (cached) {
      return cached;
    }

    // Check if flag exists and is enabled
    if (!(flagName in this.featureFlags)) {
      console.warn(`Feature flag '${flagName}' not found, defaulting to control`);
      return 'control';
    }

    const flag = this.featureFlags[flagName];
    if (!flag.enabled) {
      // Flag is disabled, return control
      return 'control';
    }

    // Use consistent hashing to assign variant
    const variant = this.assignVariant(playerId, flagName, flag.variants);

    // Cache the assignment
    this.cache.set(cacheKey, variant);

    console.debug(`Player ${playerId} assigned to '${variant}' for flag '${flagName}'`);
    return variant;
  }

  /**
   * Assign a variant using consistent hashing.
   */
  private assignVariant(
    playerId: string,
    flagName: string,
    variants: Record<string, number>
  ): string {
    // Create hash from playerId and flagName
    const hashInput = `${playerId}:${flagName}`;
    const hash = crypto.createHash('md5').update(hashInput).digest('hex');
    const hashValue = parseInt(hash.substring(0, 8), 16);

    // Normalize hash to 0-1 range
    const normalized = (hashValue % 10000) / 10000;

    // Assign variant based on cumulative weights
    let cumulative = 0;
    for (const [variantName, weight] of Object.entries(variants)) {
      cumulative += weight;
      if (normalized <= cumulative) {
        return variantName;
      }
    }

    // Fallback to last variant (should not happen if weights sum to 1.0)
    return Object.keys(variants).pop() ?? 'control';
  }

  /**
   * Check if a feature flag is enabled.
   */
  isFeatureEnabled(flagName: string): boolean {
    if (!(flagName in this.featureFlags)) {
      return false;
    }
    return this.featureFlags[flagName].enabled;
  }

  /**
   * Enable a feature flag.
   */
  enableFeature(flagName: string): void {
    if (flagName in this.featureFlags) {
      this.featureFlags[flagName].enabled = true;
      console.log(`Enabled feature flag '${flagName}'`);
    }
  }

  /**
   * Disable a feature flag.
   */
  disableFeature(flagName: string): void {
    if (flagName in this.featureFlags) {
      this.featureFlags[flagName].enabled = false;
      // Clear cache for this flag
      for (const key of this.cache.keys()) {
        if (key.endsWith(`:${flagName}`)) {
          this.cache.delete(key);
        }
      }
      console.log(`Disabled feature flag '${flagName}'`);
    }
  }

  /**
   * Get all feature flags configuration.
   */
  getAllFlags(): FeatureFlags {
    return this.featureFlags;
  }

  /**
   * Get all enabled feature flags.
   */
  getEnabledFlags(): string[] {
    return Object.entries(this.featureFlags)
      .filter(([_, flag]) => flag.enabled)
      .map(([name]) => name);
  }

  /**
   * Get all variant assignments for a player.
   */
  getPlayerVariants(playerId: string): Record<string, string> {
    const assignments: Record<string, string> = {};
    for (const flagName of Object.keys(this.featureFlags)) {
      assignments[flagName] = this.getVariant(playerId, flagName);
    }
    return assignments;
  }

  /**
   * Get variant assignments for enabled flags only.
   */
  getPlayerEnabledVariants(playerId: string): Record<string, string> {
    const assignments: Record<string, string> = {};
    for (const [flagName, flag] of Object.entries(this.featureFlags)) {
      if (flag.enabled) {
        assignments[flagName] = this.getVariant(playerId, flagName);
      }
    }
    return assignments;
  }

  /**
   * Clear cached variant assignments.
   */
  clearCache(playerId?: string): void {
    if (playerId) {
      // Clear only for specific player
      for (const key of this.cache.keys()) {
        if (key.startsWith(`${playerId}:`)) {
          this.cache.delete(key);
        }
      }
    } else {
      // Clear all cache
      this.cache.clear();
    }
  }

  /**
   * Add a new feature flag dynamically.
   */
  addFlag(name: string, flag: FeatureFlag): void {
    this.featureFlags[name] = flag;
    console.log(`Added feature flag '${name}'`);
  }

  /**
   * Remove a feature flag.
   */
  removeFlag(name: string): void {
    if (name in this.featureFlags) {
      delete this.featureFlags[name];
      // Clear cache for this flag
      for (const key of this.cache.keys()) {
        if (key.endsWith(`:${name}`)) {
          this.cache.delete(key);
        }
      }
      console.log(`Removed feature flag '${name}'`);
    }
  }

  /**
   * Get cache statistics.
   */
  getCacheStats(): { size: number; keys: string[] } {
    return {
      size: this.cache.size,
      keys: Array.from(this.cache.keys()),
    };
  }
}

// ============================================================
// Global Instance and Convenience Functions
// ============================================================

let _abManager: ABTestingManager | null = null;

/**
 * Get or create the global A/B testing manager instance.
 */
export function getABManager(): ABTestingManager {
  if (_abManager === null) {
    _abManager = new ABTestingManager();
  }
  return _abManager;
}

/**
 * Get variant assignment for a player (convenience function).
 */
export function getVariant(playerId: string, flagName: string): string {
  return getABManager().getVariant(playerId, flagName);
}

/**
 * Check if a player is in treatment variant.
 */
export function isInTreatment(playerId: string, flagName: string): boolean {
  const variant = getVariant(playerId, flagName);
  return variant.startsWith('treatment');
}

/**
 * Check if a feature is enabled.
 */
export function isFeatureEnabled(flagName: string): boolean {
  return getABManager().isFeatureEnabled(flagName);
}

/**
 * Enable a feature flag globally.
 */
export function enableFeature(flagName: string): void {
  getABManager().enableFeature(flagName);
}

/**
 * Disable a feature flag globally.
 */
export function disableFeature(flagName: string): void {
  getABManager().disableFeature(flagName);
}

// ============================================================
// Default Export
// ============================================================

export const abTesting = {
  manager: getABManager,
  getVariant,
  isInTreatment,
  isFeatureEnabled,
  enableFeature,
  disableFeature,
  FEATURE_FLAGS,
};
