/**
 * ConfigChange Service
 * System configuration change auditing.
 *
 * @module Audit
 */

import { Repository } from 'typeorm';
import { ConfigChange, ConfigType } from '../entities/config-change.entity';
import { ServiceContext, PaginatedResult } from './audit-log.service';

export interface CreateConfigChangeDto {
  changedBy: string;
  configType: ConfigType;
  configKey: string;
  configPath?: string;
  oldValue?: Record<string, any>;
  newValue?: Record<string, any>;
  reason?: string;
  ticketId?: string;
}

export interface ConfigChangeFilters {
  changedBy?: string;
  configType?: ConfigType;
  configKey?: string;
  dateFrom?: Date;
  dateTo?: Date;
  search?: string;
}

export interface ConfigChangeStats {
  period: { days: number; from: Date; to: Date };
  totalChanges: number;
  byConfigType: { configType: string; count: number }[];
  topChangedKeys: { configKey: string; count: number }[];
  topChangers: { userId: string; count: number }[];
}

export class ConfigChangeService {
  constructor(private readonly repository: Repository<ConfigChange>) {}

  /**
   * Record a configuration change
   */
  async log(
    ctx: ServiceContext,
    dto: CreateConfigChangeDto,
  ): Promise<ConfigChange> {
    const change = this.repository.create({
      tenantId: ctx.tenantId,
      changedBy: dto.changedBy,
      configType: dto.configType,
      configKey: dto.configKey,
      configPath: dto.configPath,
      oldValue: dto.oldValue,
      newValue: dto.newValue,
      reason: dto.reason,
      ticketId: dto.ticketId,
    });

    return this.repository.save(change);
  }

  /**
   * Find changes with filters
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: ConfigChangeFilters,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<ConfigChange>> {
    const qb = this.repository
      .createQueryBuilder('cc')
      .where('cc.tenant_id = :tenantId OR cc.tenant_id IS NULL', { tenantId: ctx.tenantId });

    if (filters.changedBy) {
      qb.andWhere('cc.changed_by = :changedBy', { changedBy: filters.changedBy });
    }
    if (filters.configType) {
      qb.andWhere('cc.config_type = :configType', { configType: filters.configType });
    }
    if (filters.configKey) {
      qb.andWhere('cc.config_key = :configKey', { configKey: filters.configKey });
    }
    if (filters.dateFrom) {
      qb.andWhere('cc.changed_at >= :dateFrom', { dateFrom: filters.dateFrom });
    }
    if (filters.dateTo) {
      qb.andWhere('cc.changed_at <= :dateTo', { dateTo: filters.dateTo });
    }
    if (filters.search) {
      qb.andWhere(
        '(cc.config_key ILIKE :search OR cc.config_path ILIKE :search OR cc.reason ILIKE :search)',
        { search: `%${filters.search}%` },
      );
    }

    const skip = (page - 1) * limit;
    qb.orderBy('cc.changed_at', 'DESC').skip(skip).take(limit);

    const [data, total] = await qb.getManyAndCount();

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

  /**
   * Get changes for a specific config key
   */
  async findByConfigKey(
    ctx: ServiceContext,
    configKey: string,
    page = 1,
    limit = 20,
  ): Promise<PaginatedResult<ConfigChange>> {
    return this.findWithFilters(ctx, { configKey }, page, limit);
  }

  /**
   * Get changes by config type
   */
  async findByConfigType(
    ctx: ServiceContext,
    configType: ConfigType,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<ConfigChange>> {
    return this.findWithFilters(ctx, { configType }, page, limit);
  }

  /**
   * Get changes made by a user
   */
  async findByChanger(
    ctx: ServiceContext,
    changedBy: string,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<ConfigChange>> {
    return this.findWithFilters(ctx, { changedBy }, page, limit);
  }

  /**
   * Get recent configuration changes
   */
  async getRecentChanges(
    ctx: ServiceContext,
    days = 7,
    limit = 100,
  ): Promise<ConfigChange[]> {
    const dateFrom = new Date();
    dateFrom.setDate(dateFrom.getDate() - days);

    return this.repository
      .createQueryBuilder('cc')
      .where('cc.tenant_id = :tenantId OR cc.tenant_id IS NULL', { tenantId: ctx.tenantId })
      .andWhere('cc.changed_at >= :dateFrom', { dateFrom })
      .orderBy('cc.changed_at', 'DESC')
      .take(limit)
      .getMany();
  }

  /**
   * Get feature flag changes
   */
  async getFeatureFlagChanges(
    ctx: ServiceContext,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<ConfigChange>> {
    return this.findWithFilters(ctx, { configType: 'feature_flags' }, page, limit);
  }

  /**
   * Get tenant settings changes
   */
  async getTenantSettingsChanges(
    ctx: ServiceContext,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<ConfigChange>> {
    return this.findWithFilters(ctx, { configType: 'tenant_settings' }, page, limit);
  }

  /**
   * Get system settings changes (admin only)
   */
  async getSystemSettingsChanges(
    ctx: ServiceContext,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<ConfigChange>> {
    return this.findWithFilters(ctx, { configType: 'system_settings' }, page, limit);
  }

  /**
   * Get history for a specific configuration
   */
  async getConfigHistory(
    ctx: ServiceContext,
    configKey: string,
    limit = 20,
  ): Promise<ConfigChange[]> {
    return this.repository
      .createQueryBuilder('cc')
      .where('cc.tenant_id = :tenantId OR cc.tenant_id IS NULL', { tenantId: ctx.tenantId })
      .andWhere('cc.config_key = :configKey', { configKey })
      .orderBy('cc.changed_at', 'DESC')
      .take(limit)
      .getMany();
  }

  /**
   * Get statistics
   */
  async getStats(ctx: ServiceContext, days = 30): Promise<ConfigChangeStats> {
    const dateFrom = new Date();
    dateFrom.setDate(dateFrom.getDate() - days);

    const [
      totalChanges,
      byConfigType,
      topChangedKeys,
      topChangers,
    ] = await Promise.all([
      this.repository
        .createQueryBuilder('cc')
        .where('cc.tenant_id = :tenantId OR cc.tenant_id IS NULL', { tenantId: ctx.tenantId })
        .andWhere('cc.changed_at >= :dateFrom', { dateFrom })
        .getCount(),
      this.repository
        .createQueryBuilder('cc')
        .select('cc.config_type', 'configType')
        .addSelect('COUNT(*)', 'count')
        .where('cc.tenant_id = :tenantId OR cc.tenant_id IS NULL', { tenantId: ctx.tenantId })
        .andWhere('cc.changed_at >= :dateFrom', { dateFrom })
        .groupBy('cc.config_type')
        .getRawMany(),
      this.repository
        .createQueryBuilder('cc')
        .select('cc.config_key', 'configKey')
        .addSelect('COUNT(*)', 'count')
        .where('cc.tenant_id = :tenantId OR cc.tenant_id IS NULL', { tenantId: ctx.tenantId })
        .andWhere('cc.changed_at >= :dateFrom', { dateFrom })
        .groupBy('cc.config_key')
        .orderBy('count', 'DESC')
        .limit(10)
        .getRawMany(),
      this.repository
        .createQueryBuilder('cc')
        .select('cc.changed_by', 'userId')
        .addSelect('COUNT(*)', 'count')
        .where('cc.tenant_id = :tenantId OR cc.tenant_id IS NULL', { tenantId: ctx.tenantId })
        .andWhere('cc.changed_at >= :dateFrom', { dateFrom })
        .groupBy('cc.changed_by')
        .orderBy('count', 'DESC')
        .limit(10)
        .getRawMany(),
    ]);

    return {
      period: { days, from: dateFrom, to: new Date() },
      totalChanges,
      byConfigType: byConfigType.map((r) => ({ configType: r.configType, count: parseInt(r.count) })),
      topChangedKeys: topChangedKeys.map((r) => ({ configKey: r.configKey, count: parseInt(r.count) })),
      topChangers: topChangers.map((r) => ({ userId: r.userId, count: parseInt(r.count) })),
    };
  }

  /**
   * Compare old and new values
   */
  getValueDifferences(
    oldValue: Record<string, any> | null,
    newValue: Record<string, any> | null,
  ): { field: string; oldValue: any; newValue: any }[] {
    const differences: { field: string; oldValue: any; newValue: any }[] = [];
    const allKeys = new Set([
      ...Object.keys(oldValue || {}),
      ...Object.keys(newValue || {}),
    ]);

    for (const key of allKeys) {
      const oldVal = oldValue?.[key];
      const newVal = newValue?.[key];

      if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
        differences.push({
          field: key,
          oldValue: oldVal,
          newValue: newVal,
        });
      }
    }

    return differences;
  }
}
