/**
 * KPI Snapshot Service
 * Manages KPI snapshots for historical tracking and analysis
 *
 * @module Reports
 */

import { Repository, FindOptionsWhere, Between, LessThanOrEqual, MoreThanOrEqual } from 'typeorm';
import {
  KpiSnapshot,
  KpiCategory,
  KpiPeriodType,
  TrendDirection,
} from '../entities/kpi-snapshot.entity';

export interface KpiSearchParams {
  tenantId: string;
  kpiCode?: string;
  category?: KpiCategory;
  periodType?: KpiPeriodType;
  fraccionamientoId?: string;
  dateFrom?: Date;
  dateTo?: Date;
  limit?: number;
  offset?: number;
}

export interface CreateKpiSnapshotDto {
  kpiCode: string;
  kpiName: string;
  category: KpiCategory;
  snapshotDate: Date;
  periodType?: KpiPeriodType;
  periodStart?: Date | null;
  periodEnd?: Date | null;
  fraccionamientoId?: string | null;
  value: number;
  previousValue?: number | null;
  targetValue?: number | null;
  minValue?: number | null;
  maxValue?: number | null;
  unit?: string | null;
  breakdown?: Record<string, any> | null;
  metadata?: Record<string, any> | null;
}

export interface KpiTrend {
  kpiCode: string;
  kpiName: string;
  category: KpiCategory;
  currentValue: number;
  previousValue: number | null;
  changePercentage: number | null;
  trendDirection: TrendDirection;
  isOnTarget: boolean | null;
  statusColor: string | null;
  snapshots: KpiSnapshot[];
}

export interface KpiSummary {
  category: KpiCategory;
  totalKpis: number;
  onTarget: number;
  belowTarget: number;
  aboveTarget: number;
  averagePerformance: number;
}

export class KpiSnapshotService {
  constructor(private readonly kpiSnapshotRepository: Repository<KpiSnapshot>) {}

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

  async findAll(params: KpiSearchParams): Promise<{ data: KpiSnapshot[]; total: number }> {
    const {
      tenantId,
      kpiCode,
      category,
      periodType,
      fraccionamientoId,
      dateFrom,
      dateTo,
      limit = 50,
      offset = 0,
    } = params;

    const where: FindOptionsWhere<KpiSnapshot> = { tenantId };

    if (kpiCode) {
      where.kpiCode = kpiCode;
    }

    if (category) {
      where.category = category;
    }

    if (periodType) {
      where.periodType = periodType;
    }

    if (fraccionamientoId) {
      where.fraccionamientoId = fraccionamientoId;
    }

    if (dateFrom && dateTo) {
      where.snapshotDate = Between(dateFrom, dateTo);
    } else if (dateFrom) {
      where.snapshotDate = MoreThanOrEqual(dateFrom);
    } else if (dateTo) {
      where.snapshotDate = LessThanOrEqual(dateTo);
    }

    const [data, total] = await this.kpiSnapshotRepository.findAndCount({
      where,
      take: limit,
      skip: offset,
      order: { snapshotDate: 'DESC', kpiCode: 'ASC' },
    });

    return { data, total };
  }

  async findOne(id: string, tenantId: string): Promise<KpiSnapshot | null> {
    return this.kpiSnapshotRepository.findOne({
      where: { id, tenantId },
    });
  }

  async findByKpiCode(
    kpiCode: string,
    tenantId: string,
    options?: {
      limit?: number;
      dateFrom?: Date;
      dateTo?: Date;
    }
  ): Promise<KpiSnapshot[]> {
    const where: FindOptionsWhere<KpiSnapshot> = { kpiCode, tenantId };

    if (options?.dateFrom && options?.dateTo) {
      where.snapshotDate = Between(options.dateFrom, options.dateTo);
    } else if (options?.dateFrom) {
      where.snapshotDate = MoreThanOrEqual(options.dateFrom);
    } else if (options?.dateTo) {
      where.snapshotDate = LessThanOrEqual(options.dateTo);
    }

    return this.kpiSnapshotRepository.find({
      where,
      order: { snapshotDate: 'DESC' },
      take: options?.limit || 30,
    });
  }

  async create(tenantId: string, dto: CreateKpiSnapshotDto): Promise<KpiSnapshot> {
    // Calculate derived fields
    const changePercentage = dto.previousValue
      ? ((dto.value - dto.previousValue) / Math.abs(dto.previousValue)) * 100
      : null;

    const trendDirection = this.calculateTrendDirection(dto.value, dto.previousValue || null);
    const isOnTarget = this.calculateIsOnTarget(dto.value, dto.targetValue || null);
    const statusColor = this.calculateStatusColor(dto.value, dto.targetValue, dto.minValue, dto.maxValue);

    const snapshot = this.kpiSnapshotRepository.create({
      ...dto,
      tenantId,
      changePercentage,
      trendDirection,
      isOnTarget,
      statusColor,
      calculatedAt: new Date(),
    });

    return this.kpiSnapshotRepository.save(snapshot);
  }

  async createBatch(
    tenantId: string,
    snapshots: CreateKpiSnapshotDto[]
  ): Promise<KpiSnapshot[]> {
    const kpiSnapshots = snapshots.map((dto) => {
      const changePercentage = dto.previousValue
        ? ((dto.value - dto.previousValue) / Math.abs(dto.previousValue)) * 100
        : null;

      const trendDirection = this.calculateTrendDirection(dto.value, dto.previousValue || null);
      const isOnTarget = this.calculateIsOnTarget(dto.value, dto.targetValue || null);
      const statusColor = this.calculateStatusColor(dto.value, dto.targetValue, dto.minValue, dto.maxValue);

      return this.kpiSnapshotRepository.create({
        ...dto,
        tenantId,
        changePercentage,
        trendDirection,
        isOnTarget,
        statusColor,
        calculatedAt: new Date(),
      });
    });

    return this.kpiSnapshotRepository.save(kpiSnapshots);
  }

  async delete(id: string, tenantId: string): Promise<boolean> {
    const result = await this.kpiSnapshotRepository.delete({ id, tenantId });
    return (result.affected ?? 0) > 0;
  }

  // ==================== KPI Analysis ====================

  async getLatestSnapshot(
    kpiCode: string,
    tenantId: string,
    fraccionamientoId?: string
  ): Promise<KpiSnapshot | null> {
    const where: FindOptionsWhere<KpiSnapshot> = { kpiCode, tenantId };

    if (fraccionamientoId) {
      where.fraccionamientoId = fraccionamientoId;
    }

    return this.kpiSnapshotRepository.findOne({
      where,
      order: { snapshotDate: 'DESC' },
    });
  }

  async getLatestSnapshots(
    tenantId: string,
    kpiCodes?: string[],
    fraccionamientoId?: string
  ): Promise<KpiSnapshot[]> {
    const queryBuilder = this.kpiSnapshotRepository
      .createQueryBuilder('k')
      .where('k.tenant_id = :tenantId', { tenantId })
      .andWhere(
        `k.snapshot_date = (
          SELECT MAX(k2.snapshot_date)
          FROM reports.kpi_snapshots k2
          WHERE k2.kpi_code = k.kpi_code
            AND k2.tenant_id = k.tenant_id
            ${fraccionamientoId ? 'AND k2.fraccionamiento_id = :fraccionamientoId' : ''}
        )`,
        fraccionamientoId ? { fraccionamientoId } : {}
      );

    if (kpiCodes && kpiCodes.length > 0) {
      queryBuilder.andWhere('k.kpi_code IN (:...kpiCodes)', { kpiCodes });
    }

    if (fraccionamientoId) {
      queryBuilder.andWhere('k.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId });
    }

    return queryBuilder.orderBy('k.category', 'ASC').addOrderBy('k.kpi_code', 'ASC').getMany();
  }

  async getKpiTrend(
    kpiCode: string,
    tenantId: string,
    periods = 12,
    periodType: KpiPeriodType = 'monthly',
    fraccionamientoId?: string
  ): Promise<KpiTrend | null> {
    const where: FindOptionsWhere<KpiSnapshot> = {
      kpiCode,
      tenantId,
      periodType,
    };

    if (fraccionamientoId) {
      where.fraccionamientoId = fraccionamientoId;
    }

    const snapshots = await this.kpiSnapshotRepository.find({
      where,
      order: { snapshotDate: 'DESC' },
      take: periods,
    });

    if (snapshots.length === 0) return null;

    const latest = snapshots[0];
    const previous = snapshots.length > 1 ? snapshots[1] : null;

    return {
      kpiCode: latest.kpiCode,
      kpiName: latest.kpiName,
      category: latest.category,
      currentValue: Number(latest.value),
      previousValue: previous ? Number(previous.value) : null,
      changePercentage: latest.changePercentage ? Number(latest.changePercentage) : null,
      trendDirection: latest.trendDirection || 'stable',
      isOnTarget: latest.isOnTarget,
      statusColor: latest.statusColor,
      snapshots: snapshots.reverse(),
    };
  }

  async getKpiSummaryByCategory(
    tenantId: string,
    fraccionamientoId?: string
  ): Promise<KpiSummary[]> {
    const latestSnapshots = await this.getLatestSnapshots(tenantId, undefined, fraccionamientoId);

    const summaryMap = new Map<KpiCategory, KpiSummary>();

    for (const snapshot of latestSnapshots) {
      const existing = summaryMap.get(snapshot.category) || {
        category: snapshot.category,
        totalKpis: 0,
        onTarget: 0,
        belowTarget: 0,
        aboveTarget: 0,
        averagePerformance: 0,
      };

      existing.totalKpis++;

      if (snapshot.isOnTarget === true) {
        existing.onTarget++;
      } else if (snapshot.isOnTarget === false) {
        // Determine if below or above target
        if (snapshot.targetValue !== null) {
          if (Number(snapshot.value) < Number(snapshot.targetValue)) {
            existing.belowTarget++;
          } else {
            existing.aboveTarget++;
          }
        }
      }

      summaryMap.set(snapshot.category, existing);
    }

    // Calculate average performance for each category
    for (const [category, summary] of summaryMap) {
      summary.averagePerformance = summary.totalKpis > 0
        ? (summary.onTarget / summary.totalKpis) * 100
        : 0;
    }

    return Array.from(summaryMap.values());
  }

  async compareKpisByPeriod(
    tenantId: string,
    kpiCode: string,
    period1Start: Date,
    period1End: Date,
    period2Start: Date,
    period2End: Date,
    fraccionamientoId?: string
  ): Promise<{
    period1: KpiSnapshot[];
    period2: KpiSnapshot[];
    comparison: {
      period1Average: number;
      period2Average: number;
      changePercentage: number;
      trendDirection: TrendDirection;
    };
  }> {
    const baseFindOptions = {
      kpiCode,
      tenantId,
      ...(fraccionamientoId && { fraccionamientoId }),
    };

    const [period1, period2] = await Promise.all([
      this.kpiSnapshotRepository.find({
        where: {
          ...baseFindOptions,
          snapshotDate: Between(period1Start, period1End),
        },
        order: { snapshotDate: 'ASC' },
      }),
      this.kpiSnapshotRepository.find({
        where: {
          ...baseFindOptions,
          snapshotDate: Between(period2Start, period2End),
        },
        order: { snapshotDate: 'ASC' },
      }),
    ]);

    const period1Average = period1.length > 0
      ? period1.reduce((sum, s) => sum + Number(s.value), 0) / period1.length
      : 0;

    const period2Average = period2.length > 0
      ? period2.reduce((sum, s) => sum + Number(s.value), 0) / period2.length
      : 0;

    const changePercentage = period1Average !== 0
      ? ((period2Average - period1Average) / Math.abs(period1Average)) * 100
      : 0;

    return {
      period1,
      period2,
      comparison: {
        period1Average,
        period2Average,
        changePercentage,
        trendDirection: this.calculateTrendDirection(period2Average, period1Average),
      },
    };
  }

  // ==================== History Operations ====================

  async getHistory(
    tenantId: string,
    options?: {
      kpiCode?: string;
      category?: KpiCategory;
      fraccionamientoId?: string;
      limit?: number;
      daysBack?: number;
    }
  ): Promise<KpiSnapshot[]> {
    const where: FindOptionsWhere<KpiSnapshot> = { tenantId };

    if (options?.kpiCode) {
      where.kpiCode = options.kpiCode;
    }

    if (options?.category) {
      where.category = options.category;
    }

    if (options?.fraccionamientoId) {
      where.fraccionamientoId = options.fraccionamientoId;
    }

    if (options?.daysBack) {
      const dateFrom = new Date();
      dateFrom.setDate(dateFrom.getDate() - options.daysBack);
      where.snapshotDate = MoreThanOrEqual(dateFrom);
    }

    return this.kpiSnapshotRepository.find({
      where,
      order: { snapshotDate: 'DESC' },
      take: options?.limit || 100,
    });
  }

  async cleanupOldSnapshots(
    tenantId: string,
    daysToKeep = 365
  ): Promise<number> {
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);

    const result = await this.kpiSnapshotRepository.delete({
      tenantId,
      snapshotDate: LessThanOrEqual(cutoffDate),
    });

    return result.affected || 0;
  }

  // ==================== Export Operations ====================

  async export(
    tenantId: string,
    format: 'pdf' | 'excel' | 'csv',
    options?: {
      kpiCodes?: string[];
      category?: KpiCategory;
      dateFrom?: Date;
      dateTo?: Date;
    }
  ): Promise<{ filePath: string; mimeType: string }> {
    // This would integrate with actual export logic
    const mimeTypes: Record<string, string> = {
      pdf: 'application/pdf',
      excel: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      csv: 'text/csv',
    };

    return {
      filePath: `/exports/kpi_snapshots_${Date.now()}.${format}`,
      mimeType: mimeTypes[format],
    };
  }

  // ==================== Private Methods ====================

  private calculateTrendDirection(
    currentValue: number,
    previousValue: number | null
  ): TrendDirection {
    if (previousValue === null) return 'stable';

    const threshold = 0.01; // 1% threshold for stability
    const change = (currentValue - previousValue) / Math.abs(previousValue);

    if (change > threshold) return 'up';
    if (change < -threshold) return 'down';
    return 'stable';
  }

  private calculateIsOnTarget(
    value: number,
    targetValue: number | null
  ): boolean | null {
    if (targetValue === null) return null;

    // Consider a 5% tolerance
    const tolerance = 0.05;
    const lowerBound = targetValue * (1 - tolerance);

    return value >= lowerBound;
  }

  private calculateStatusColor(
    value: number,
    targetValue?: number | null,
    minValue?: number | null,
    maxValue?: number | null
  ): string | null {
    if (targetValue === null || targetValue === undefined) return null;

    // Calculate thresholds
    const greenThreshold = targetValue * 0.95; // Within 5% of target
    const yellowThreshold = targetValue * 0.80; // Within 20% of target

    if (value >= greenThreshold) return 'green';
    if (value >= yellowThreshold) return 'yellow';
    return 'red';
  }
}
