/**
 * KPI Config Service
 * ERP Construccion - Modulo Reports (MAI-006)
 *
 * Logica de negocio para configuracion y calculo de KPIs dinamicos.
 * @gap GAP-001
 */

import { Repository, DataSource } from 'typeorm';
import { KpiConfig, KpiCategory, FormulaType, CalculationFrequency } from '../entities/kpi-config.entity';
import { KpiValue, PeriodType, KpiStatus, TrendDirection } from '../entities/kpi-value.entity';

// DTOs
export interface CreateKpiConfigDto {
  code: string;
  name: string;
  description?: string;
  category: KpiCategory;
  module: string;
  formula: string;
  formulaType?: FormulaType;
  queryFunction?: string;
  parametersSchema?: Record<string, any>;
  unit?: string;
  decimalPlaces?: number;
  formatPattern?: string;
  targetValue?: number;
  thresholdGreen?: number;
  thresholdYellow?: number;
  invertColors?: boolean;
  calculationFrequency?: CalculationFrequency;
  displayOrder?: number;
  icon?: string;
  color?: string;
  isSystem?: boolean;
}

export interface UpdateKpiConfigDto extends Partial<CreateKpiConfigDto> {
  isActive?: boolean;
}

export interface KpiConfigFilters {
  category?: KpiCategory;
  module?: string;
  isActive?: boolean;
  search?: string;
}

export interface CreateKpiValueDto {
  kpiId: string;
  periodStart: Date;
  periodEnd: Date;
  periodType?: PeriodType;
  projectId?: string;
  departmentId?: string;
  value: number;
  previousValue?: number;
  targetValue?: number;
  breakdown?: Record<string, any>;
}

export interface KpiValueFilters {
  kpiId?: string;
  projectId?: string;
  periodType?: PeriodType;
  fromDate?: Date;
  toDate?: Date;
}

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

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

export class KpiConfigService {
  private configRepository: Repository<KpiConfig>;
  private valueRepository: Repository<KpiValue>;

  constructor(dataSource: DataSource) {
    this.configRepository = dataSource.getRepository(KpiConfig);
    this.valueRepository = dataSource.getRepository(KpiValue);
  }

  // ============================================
  // KPI CONFIG
  // ============================================

  async create(tenantId: string, dto: CreateKpiConfigDto, userId?: string): Promise<KpiConfig> {
    const existing = await this.configRepository.findOne({
      where: { tenantId, code: dto.code },
    });

    if (existing) {
      throw new Error(`KPI with code ${dto.code} already exists`);
    }

    const kpi = this.configRepository.create({
      tenantId,
      ...dto,
      formulaType: dto.formulaType || 'sql',
      calculationFrequency: dto.calculationFrequency || 'daily',
      decimalPlaces: dto.decimalPlaces ?? 2,
      invertColors: dto.invertColors ?? false,
      isActive: true,
      isSystem: dto.isSystem ?? false,
      createdBy: userId,
    });

    return this.configRepository.save(kpi);
  }

  async findById(tenantId: string, id: string): Promise<KpiConfig | null> {
    return this.configRepository.findOne({
      where: { id, tenantId, deletedAt: undefined },
    });
  }

  async findByCode(tenantId: string, code: string): Promise<KpiConfig | null> {
    return this.configRepository.findOne({
      where: { tenantId, code, deletedAt: undefined },
    });
  }

  async findAll(
    tenantId: string,
    filters: KpiConfigFilters = {},
    pagination: PaginationOptions = { page: 1, limit: 20 }
  ): Promise<PaginatedResult<KpiConfig>> {
    const queryBuilder = this.configRepository.createQueryBuilder('kpi')
      .where('kpi.tenant_id = :tenantId', { tenantId })
      .andWhere('kpi.deleted_at IS NULL');

    if (filters.category) {
      queryBuilder.andWhere('kpi.category = :category', { category: filters.category });
    }
    if (filters.module) {
      queryBuilder.andWhere('kpi.module = :module', { module: filters.module });
    }
    if (filters.isActive !== undefined) {
      queryBuilder.andWhere('kpi.is_active = :isActive', { isActive: filters.isActive });
    }
    if (filters.search) {
      queryBuilder.andWhere(
        '(kpi.code ILIKE :search OR kpi.name ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

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

    const [data, total] = await queryBuilder
      .orderBy('kpi.display_order', 'ASC')
      .addOrderBy('kpi.name', 'ASC')
      .skip(skip)
      .take(pagination.limit)
      .getManyAndCount();

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

  async update(tenantId: string, id: string, dto: UpdateKpiConfigDto, userId?: string): Promise<KpiConfig | null> {
    const kpi = await this.findById(tenantId, id);
    if (!kpi) return null;

    if (kpi.isSystem && !dto.isActive) {
      throw new Error('Cannot modify system KPIs');
    }

    Object.assign(kpi, dto, { updatedBy: userId });
    return this.configRepository.save(kpi);
  }

  async delete(tenantId: string, id: string, userId?: string): Promise<boolean> {
    const kpi = await this.findById(tenantId, id);
    if (!kpi) return false;

    if (kpi.isSystem) {
      throw new Error('Cannot delete system KPIs');
    }

    const result = await this.configRepository.update(
      { id, tenantId },
      { deletedAt: new Date(), isActive: false, updatedBy: userId }
    );
    return (result.affected ?? 0) > 0;
  }

  async getByCategory(tenantId: string, category: KpiCategory): Promise<KpiConfig[]> {
    return this.configRepository.find({
      where: { tenantId, category, isActive: true, deletedAt: undefined },
      order: { displayOrder: 'ASC', name: 'ASC' },
    });
  }

  async getByModule(tenantId: string, module: string): Promise<KpiConfig[]> {
    return this.configRepository.find({
      where: { tenantId, module, isActive: true, deletedAt: undefined },
      order: { displayOrder: 'ASC', name: 'ASC' },
    });
  }

  // ============================================
  // KPI VALUES
  // ============================================

  async recordValue(tenantId: string, dto: CreateKpiValueDto, _userId?: string): Promise<KpiValue> {
    const kpi = await this.findById(tenantId, dto.kpiId);
    if (!kpi) {
      throw new Error('KPI configuration not found');
    }

    // Calculate variance if target is provided
    let varianceValue: number | undefined;
    let variancePercentage: number | undefined;
    let status: KpiStatus | undefined;
    let isOnTarget: boolean | undefined;

    const targetValue = dto.targetValue ?? kpi.targetValue;
    if (targetValue !== undefined && targetValue !== null) {
      varianceValue = dto.value - targetValue;
      variancePercentage = targetValue !== 0 ? ((dto.value - targetValue) / targetValue) * 100 : 0;

      // Calculate status based on thresholds
      if (kpi.thresholdGreen !== undefined && kpi.thresholdYellow !== undefined) {
        if (kpi.invertColors) {
          status = dto.value <= kpi.thresholdGreen ? 'green' : dto.value <= kpi.thresholdYellow ? 'yellow' : 'red';
        } else {
          status = dto.value >= kpi.thresholdGreen ? 'green' : dto.value >= kpi.thresholdYellow ? 'yellow' : 'red';
        }
        isOnTarget = status === 'green';
      }
    }

    // Calculate trend
    let trendDirection: TrendDirection | undefined;
    let changePercentage: number | undefined;
    if (dto.previousValue !== undefined && dto.previousValue !== null) {
      changePercentage = dto.previousValue !== 0 ? ((dto.value - dto.previousValue) / dto.previousValue) * 100 : 0;
      trendDirection = dto.value > dto.previousValue ? 'up' : dto.value < dto.previousValue ? 'down' : 'stable';
    }

    const value = this.valueRepository.create({
      tenantId,
      kpiId: dto.kpiId,
      periodStart: dto.periodStart,
      periodEnd: dto.periodEnd,
      periodType: dto.periodType || 'daily',
      projectId: dto.projectId,
      departmentId: dto.departmentId,
      value: dto.value,
      previousValue: dto.previousValue,
      targetValue,
      varianceValue,
      variancePercentage,
      status,
      isOnTarget,
      trendDirection,
      changePercentage,
      breakdown: dto.breakdown,
      calculatedAt: new Date(),
    });

    return this.valueRepository.save(value);
  }

  async getValues(
    tenantId: string,
    filters: KpiValueFilters = {},
    pagination: PaginationOptions = { page: 1, limit: 50 }
  ): Promise<PaginatedResult<KpiValue>> {
    const queryBuilder = this.valueRepository.createQueryBuilder('val')
      .where('val.tenant_id = :tenantId', { tenantId });

    if (filters.kpiId) {
      queryBuilder.andWhere('val.kpi_id = :kpiId', { kpiId: filters.kpiId });
    }
    if (filters.projectId) {
      queryBuilder.andWhere('val.project_id = :projectId', { projectId: filters.projectId });
    }
    if (filters.periodType) {
      queryBuilder.andWhere('val.period_type = :periodType', { periodType: filters.periodType });
    }
    if (filters.fromDate) {
      queryBuilder.andWhere('val.period_start >= :fromDate', { fromDate: filters.fromDate });
    }
    if (filters.toDate) {
      queryBuilder.andWhere('val.period_end <= :toDate', { toDate: filters.toDate });
    }

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

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

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

  async getLatestValue(tenantId: string, kpiId: string, projectId?: string): Promise<KpiValue | null> {
    const queryBuilder = this.valueRepository.createQueryBuilder('val')
      .where('val.tenant_id = :tenantId', { tenantId })
      .andWhere('val.kpi_id = :kpiId', { kpiId });

    if (projectId) {
      queryBuilder.andWhere('val.project_id = :projectId', { projectId });
    }

    return queryBuilder
      .orderBy('val.period_start', 'DESC')
      .getOne();
  }

  async getKpiDashboard(tenantId: string, projectId?: string): Promise<Array<{
    config: KpiConfig;
    latestValue: KpiValue | null;
  }>> {
    const kpis = await this.configRepository.find({
      where: { tenantId, isActive: true, deletedAt: undefined },
      order: { displayOrder: 'ASC', name: 'ASC' },
    });

    return Promise.all(
      kpis.map(async (config) => ({
        config,
        latestValue: await this.getLatestValue(tenantId, config.id, projectId),
      }))
    );
  }
}
