/**
 * FlagEvaluation Service
 * ERP Construccion - Feature Flags Module
 *
 * Business logic for evaluating feature flags with user targeting,
 * rollout percentages, and A/B testing support.
 *
 * @module FeatureFlags
 */

import { Repository, DataSource, In } from 'typeorm';
import { createHash } from 'crypto';
import { Flag } from '../entities/flag.entity';
import { FlagEvaluation } from '../entities/flag-evaluation.entity';
import { TenantOverride } from '../entities/tenant-override.entity';

// ============================================
// DTOs & INTERFACES
// ============================================

export interface EvaluationContext {
  userId?: string;
  userEmail?: string;
  userRole?: string;
  userAttributes?: Record<string, any>;
  sessionId?: string;
  requestId?: string;
  environment?: string;
  platform?: string;
  version?: string;
  [key: string]: any;
}

export interface EvaluationResult {
  flagCode: string;
  enabled: boolean;
  variant?: string;
  reason: EvaluationReason;
  flagId: string;
  rolloutPercentage?: number;
}

export type EvaluationReason =
  | 'FLAG_NOT_FOUND'
  | 'FLAG_INACTIVE'
  | 'FLAG_DISABLED'
  | 'TENANT_OVERRIDE'
  | 'ROLLOUT_EXCLUDED'
  | 'ROLLOUT_INCLUDED'
  | 'DEFAULT_ENABLED'
  | 'USER_TARGETING';

export interface BulkEvaluationResult {
  evaluations: Record<string, EvaluationResult>;
  evaluatedAt: Date;
  tenantId: string;
  context: EvaluationContext;
}

export interface EvaluationFilters {
  flagId?: string;
  tenantId?: string;
  userId?: string;
  result?: boolean;
  startDate?: Date;
  endDate?: Date;
}

export interface PaginationOptions {
  page: number;
  limit: number;
}

export interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

export interface EvaluationStats {
  total: number;
  enabled: number;
  disabled: number;
  enabledPercentage: number;
  byReason: Record<string, number>;
  byVariant: Record<string, number>;
}

// ============================================
// SERVICE
// ============================================

export class FlagEvaluationService {
  private flagRepository: Repository<Flag>;
  private evaluationRepository: Repository<FlagEvaluation>;
  private overrideRepository: Repository<TenantOverride>;

  constructor(dataSource: DataSource) {
    this.flagRepository = dataSource.getRepository(Flag);
    this.evaluationRepository = dataSource.getRepository(FlagEvaluation);
    this.overrideRepository = dataSource.getRepository(TenantOverride);
  }

  // ============================================
  // CORE EVALUATION
  // ============================================

  /**
   * Evaluate a single flag for a tenant/user
   * @param flagCode - The flag code to evaluate
   * @param tenantId - The tenant ID
   * @param context - Additional context for evaluation
   * @param track - Whether to track the evaluation (default: true)
   */
  async evaluate(
    flagCode: string,
    tenantId: string,
    context: EvaluationContext = {},
    track = true
  ): Promise<EvaluationResult> {
    // Find the flag
    const flag = await this.flagRepository.findOne({
      where: { code: flagCode },
    });

    if (!flag) {
      return this.createResult(flagCode, '', false, 'FLAG_NOT_FOUND');
    }

    if (!flag.isActive) {
      return this.createResult(flagCode, flag.id, false, 'FLAG_INACTIVE');
    }

    // Check for tenant override
    const override = await this.getValidOverride(flag.id, tenantId);
    if (override !== null) {
      const result = this.createResult(
        flagCode,
        flag.id,
        override.enabled,
        'TENANT_OVERRIDE',
        flag.rolloutPercentage
      );

      if (track) {
        await this.trackEvaluation(flag.id, tenantId, context.userId || null, result, context);
      }

      return result;
    }

    // If flag is globally disabled
    if (!flag.enabled) {
      const result = this.createResult(flagCode, flag.id, false, 'FLAG_DISABLED', flag.rolloutPercentage);

      if (track) {
        await this.trackEvaluation(flag.id, tenantId, context.userId || null, result, context);
      }

      return result;
    }

    // Apply rollout percentage
    if (flag.rolloutPercentage < 100) {
      const isIncluded = this.isInRollout(flag.code, tenantId, context.userId, flag.rolloutPercentage);

      const result = this.createResult(
        flagCode,
        flag.id,
        isIncluded,
        isIncluded ? 'ROLLOUT_INCLUDED' : 'ROLLOUT_EXCLUDED',
        flag.rolloutPercentage
      );

      if (track) {
        await this.trackEvaluation(flag.id, tenantId, context.userId || null, result, context);
      }

      return result;
    }

    // Flag is enabled with 100% rollout
    const result = this.createResult(flagCode, flag.id, true, 'DEFAULT_ENABLED', flag.rolloutPercentage);

    if (track) {
      await this.trackEvaluation(flag.id, tenantId, context.userId || null, result, context);
    }

    return result;
  }

  /**
   * Evaluate multiple flags at once
   */
  async evaluateBulk(
    flagCodes: string[],
    tenantId: string,
    context: EvaluationContext = {},
    track = true
  ): Promise<BulkEvaluationResult> {
    const evaluations: Record<string, EvaluationResult> = {};

    // Fetch all requested flags
    const flags = await this.flagRepository.find({
      where: { code: In(flagCodes) },
    });

    const flagMap = new Map(flags.map((f) => [f.code, f]));

    // Get all tenant overrides for these flags
    const flagIds = flags.map((f) => f.id);
    const overrides = await this.getValidOverridesForFlags(flagIds, tenantId);
    const overrideMap = new Map(overrides.map((o) => [o.flagId, o]));

    // Evaluate each flag
    for (const code of flagCodes) {
      const flag = flagMap.get(code);

      if (!flag) {
        evaluations[code] = this.createResult(code, '', false, 'FLAG_NOT_FOUND');
        continue;
      }

      if (!flag.isActive) {
        evaluations[code] = this.createResult(code, flag.id, false, 'FLAG_INACTIVE');
        continue;
      }

      const override = overrideMap.get(flag.id);
      if (override) {
        evaluations[code] = this.createResult(code, flag.id, override.enabled, 'TENANT_OVERRIDE', flag.rolloutPercentage);
        continue;
      }

      if (!flag.enabled) {
        evaluations[code] = this.createResult(code, flag.id, false, 'FLAG_DISABLED', flag.rolloutPercentage);
        continue;
      }

      if (flag.rolloutPercentage < 100) {
        const isIncluded = this.isInRollout(flag.code, tenantId, context.userId, flag.rolloutPercentage);
        evaluations[code] = this.createResult(
          code,
          flag.id,
          isIncluded,
          isIncluded ? 'ROLLOUT_INCLUDED' : 'ROLLOUT_EXCLUDED',
          flag.rolloutPercentage
        );
        continue;
      }

      evaluations[code] = this.createResult(code, flag.id, true, 'DEFAULT_ENABLED', flag.rolloutPercentage);
    }

    // Track all evaluations if requested
    if (track) {
      await this.trackBulkEvaluations(evaluations, tenantId, context);
    }

    return {
      evaluations,
      evaluatedAt: new Date(),
      tenantId,
      context,
    };
  }

  /**
   * Evaluate all active flags for a tenant
   */
  async evaluateAll(
    tenantId: string,
    context: EvaluationContext = {},
    track = true
  ): Promise<BulkEvaluationResult> {
    const activeFlags = await this.flagRepository.find({
      where: { isActive: true },
    });

    const flagCodes = activeFlags.map((f) => f.code);
    return this.evaluateBulk(flagCodes, tenantId, context, track);
  }

  /**
   * Quick check if flag is enabled (no tracking)
   */
  async isEnabled(flagCode: string, tenantId: string, userId?: string): Promise<boolean> {
    const result = await this.evaluate(flagCode, tenantId, { userId }, false);
    return result.enabled;
  }

  // ============================================
  // ROLLOUT LOGIC
  // ============================================

  /**
   * Determine if a user/tenant is in the rollout group
   * Uses consistent hashing to ensure same result for same inputs
   */
  private isInRollout(
    flagCode: string,
    tenantId: string,
    userId: string | undefined,
    percentage: number
  ): boolean {
    if (percentage === 0) return false;
    if (percentage >= 100) return true;

    // Create a consistent hash key
    const hashKey = userId ? `${flagCode}:${tenantId}:${userId}` : `${flagCode}:${tenantId}`;

    const hash = createHash('md5').update(hashKey).digest('hex');
    const hashValue = parseInt(hash.substring(0, 8), 16);
    const bucket = hashValue % 100;

    return bucket < percentage;
  }

  /**
   * Get the rollout bucket for a specific user (useful for debugging)
   */
  getRolloutBucket(flagCode: string, tenantId: string, userId?: string): number {
    const hashKey = userId ? `${flagCode}:${tenantId}:${userId}` : `${flagCode}:${tenantId}`;

    const hash = createHash('md5').update(hashKey).digest('hex');
    const hashValue = parseInt(hash.substring(0, 8), 16);

    return hashValue % 100;
  }

  // ============================================
  // OVERRIDE HELPERS
  // ============================================

  /**
   * Get valid (non-expired) override for a flag/tenant
   */
  private async getValidOverride(flagId: string, tenantId: string): Promise<TenantOverride | null> {
    const override = await this.overrideRepository.findOne({
      where: { flagId, tenantId },
    });

    if (!override) return null;

    // Check expiration
    if (override.expiresAt && override.expiresAt < new Date()) {
      return null;
    }

    return override;
  }

  /**
   * Get valid overrides for multiple flags
   */
  private async getValidOverridesForFlags(flagIds: string[], tenantId: string): Promise<TenantOverride[]> {
    if (flagIds.length === 0) return [];

    const overrides = await this.overrideRepository.find({
      where: { flagId: In(flagIds), tenantId },
    });

    const now = new Date();
    return overrides.filter((o) => !o.expiresAt || o.expiresAt > now);
  }

  // ============================================
  // TRACKING
  // ============================================

  /**
   * Track a single evaluation
   */
  private async trackEvaluation(
    flagId: string,
    tenantId: string,
    userId: string | null,
    result: EvaluationResult,
    context: EvaluationContext
  ): Promise<void> {
    try {
      const evaluation = this.evaluationRepository.create({
        flagId,
        tenantId,
        userId,
        result: result.enabled,
        variant: result.variant || null,
        evaluationContext: context,
        evaluationReason: result.reason,
        evaluatedAt: new Date(),
      });

      await this.evaluationRepository.save(evaluation);
    } catch (error) {
      // Don't throw - tracking failure shouldn't affect flag evaluation
      console.error('Failed to track flag evaluation:', error);
    }
  }

  /**
   * Track multiple evaluations
   */
  private async trackBulkEvaluations(
    evaluations: Record<string, EvaluationResult>,
    tenantId: string,
    context: EvaluationContext
  ): Promise<void> {
    try {
      const records = Object.values(evaluations)
        .filter((e) => e.flagId) // Skip NOT_FOUND results
        .map((result) =>
          this.evaluationRepository.create({
            flagId: result.flagId,
            tenantId,
            userId: context.userId || null,
            result: result.enabled,
            variant: result.variant || null,
            evaluationContext: context,
            evaluationReason: result.reason,
            evaluatedAt: new Date(),
          })
        );

      if (records.length > 0) {
        await this.evaluationRepository.save(records);
      }
    } catch (error) {
      console.error('Failed to track bulk evaluations:', error);
    }
  }

  // ============================================
  // EVALUATION HISTORY
  // ============================================

  /**
   * Get evaluation history with filters
   */
  async getEvaluationHistory(
    filters: EvaluationFilters = {},
    pagination: PaginationOptions = { page: 1, limit: 50 }
  ): Promise<PaginatedResult<FlagEvaluation>> {
    const queryBuilder = this.evaluationRepository
      .createQueryBuilder('eval')
      .leftJoinAndSelect('eval.flag', 'flag')
      .where('1 = 1');

    if (filters.flagId) {
      queryBuilder.andWhere('eval.flag_id = :flagId', { flagId: filters.flagId });
    }

    if (filters.tenantId) {
      queryBuilder.andWhere('eval.tenant_id = :tenantId', { tenantId: filters.tenantId });
    }

    if (filters.userId) {
      queryBuilder.andWhere('eval.user_id = :userId', { userId: filters.userId });
    }

    if (filters.result !== undefined) {
      queryBuilder.andWhere('eval.result = :result', { result: filters.result });
    }

    if (filters.startDate) {
      queryBuilder.andWhere('eval.evaluated_at >= :startDate', { startDate: filters.startDate });
    }

    if (filters.endDate) {
      queryBuilder.andWhere('eval.evaluated_at <= :endDate', { endDate: filters.endDate });
    }

    const skip = (pagination.page - 1) * pagination.limit;

    const [data, total] = await queryBuilder
      .orderBy('eval.evaluated_at', 'DESC')
      .skip(skip)
      .take(pagination.limit)
      .getManyAndCount();

    return {
      data,
      total,
      page: pagination.page,
      limit: pagination.limit,
      totalPages: Math.ceil(total / pagination.limit),
    };
  }

  /**
   * Get evaluation statistics for a flag
   */
  async getEvaluationStats(flagId: string, tenantId?: string, days = 7): Promise<EvaluationStats> {
    const startDate = new Date();
    startDate.setDate(startDate.getDate() - days);

    const queryBuilder = this.evaluationRepository
      .createQueryBuilder('eval')
      .where('eval.flag_id = :flagId', { flagId })
      .andWhere('eval.evaluated_at >= :startDate', { startDate });

    if (tenantId) {
      queryBuilder.andWhere('eval.tenant_id = :tenantId', { tenantId });
    }

    const [total, enabled, byReasonRaw, byVariantRaw] = await Promise.all([
      queryBuilder.clone().getCount(),

      queryBuilder.clone().andWhere('eval.result = true').getCount(),

      queryBuilder
        .clone()
        .select('eval.evaluation_reason', 'reason')
        .addSelect('COUNT(*)', 'count')
        .groupBy('eval.evaluation_reason')
        .getRawMany(),

      queryBuilder
        .clone()
        .select('eval.variant', 'variant')
        .addSelect('COUNT(*)', 'count')
        .where('eval.variant IS NOT NULL')
        .groupBy('eval.variant')
        .getRawMany(),
    ]);

    const byReason: Record<string, number> = {};
    byReasonRaw.forEach((row: { reason: string; count: string }) => {
      byReason[row.reason] = parseInt(row.count, 10);
    });

    const byVariant: Record<string, number> = {};
    byVariantRaw.forEach((row: { variant: string; count: string }) => {
      if (row.variant) {
        byVariant[row.variant] = parseInt(row.count, 10);
      }
    });

    return {
      total,
      enabled,
      disabled: total - enabled,
      enabledPercentage: total > 0 ? Math.round((enabled / total) * 100) : 0,
      byReason,
      byVariant,
    };
  }

  /**
   * Clean up old evaluation records
   */
  async cleanupOldEvaluations(daysToKeep = 30): Promise<number> {
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);

    const result = await this.evaluationRepository
      .createQueryBuilder()
      .delete()
      .where('evaluated_at < :cutoffDate', { cutoffDate })
      .execute();

    return result.affected ?? 0;
  }

  // ============================================
  // HELPER METHODS
  // ============================================

  /**
   * Create a standard evaluation result
   */
  private createResult(
    flagCode: string,
    flagId: string,
    enabled: boolean,
    reason: EvaluationReason,
    rolloutPercentage?: number,
    variant?: string
  ): EvaluationResult {
    return {
      flagCode,
      flagId,
      enabled,
      reason,
      rolloutPercentage,
      variant,
    };
  }
}
