/**
 * TenantOverride Service
 * ERP Construccion - Feature Flags Module
 *
 * Business logic for managing per-tenant feature flag overrides.
 * Allows enabling/disabling flags for specific tenants.
 *
 * @module FeatureFlags
 */

import { Repository, DataSource, In, LessThan, MoreThan } from 'typeorm';
import { TenantOverride } from '../entities/tenant-override.entity';
import { Flag } from '../entities/flag.entity';

// ============================================
// DTOs
// ============================================

export interface CreateOverrideDto {
  flagId: string;
  tenantId: string;
  enabled: boolean;
  reason?: string;
  expiresAt?: Date;
}

export interface UpdateOverrideDto {
  enabled?: boolean;
  reason?: string;
  expiresAt?: Date | null;
}

export interface OverrideFilters {
  tenantId?: string;
  flagId?: string;
  enabled?: boolean;
  expired?: boolean;
  expiresSoon?: boolean; // Expires within 7 days
}

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

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

export type OverrideWithFlag = TenantOverride & {
  flag?: Flag;
};

export interface BulkOverrideDto {
  tenantId: string;
  overrides: Array<{
    flagCode: string;
    enabled: boolean;
    reason?: string;
    expiresAt?: Date;
  }>;
}

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

export class TenantOverrideService {
  private overrideRepository: Repository<TenantOverride>;
  private flagRepository: Repository<Flag>;

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

  // ============================================
  // CRUD OPERATIONS
  // ============================================

  /**
   * Create or update a tenant override
   */
  async upsert(dto: CreateOverrideDto, userId?: string): Promise<TenantOverride> {
    // Verify flag exists
    const flag = await this.flagRepository.findOne({
      where: { id: dto.flagId },
    });

    if (!flag) {
      throw new Error(`Flag with id '${dto.flagId}' not found`);
    }

    // Check for existing override
    const existing = await this.overrideRepository.findOne({
      where: { flagId: dto.flagId, tenantId: dto.tenantId },
    });

    if (existing) {
      // Update existing
      existing.enabled = dto.enabled;
      existing.reason = dto.reason ?? existing.reason;
      existing.expiresAt = dto.expiresAt ?? existing.expiresAt;
      return this.overrideRepository.save(existing);
    }

    // Create new
    const override = this.overrideRepository.create({
      ...dto,
      createdBy: userId,
    });

    return this.overrideRepository.save(override);
  }

  /**
   * Create override by flag code
   */
  async createByFlagCode(
    flagCode: string,
    tenantId: string,
    enabled: boolean,
    reason?: string,
    expiresAt?: Date,
    userId?: string
  ): Promise<TenantOverride> {
    const flag = await this.flagRepository.findOne({
      where: { code: flagCode },
    });

    if (!flag) {
      throw new Error(`Flag with code '${flagCode}' not found`);
    }

    return this.upsert(
      { flagId: flag.id, tenantId, enabled, reason, expiresAt },
      userId
    );
  }

  /**
   * Find override by ID
   */
  async findById(id: string): Promise<OverrideWithFlag | null> {
    return this.overrideRepository.findOne({
      where: { id },
      relations: ['flag'],
    });
  }

  /**
   * Find override by flag and tenant
   */
  async findByFlagAndTenant(flagId: string, tenantId: string): Promise<TenantOverride | null> {
    return this.overrideRepository.findOne({
      where: { flagId, tenantId },
    });
  }

  /**
   * Find all overrides for a tenant
   */
  async findByTenant(tenantId: string): Promise<OverrideWithFlag[]> {
    return this.overrideRepository.find({
      where: { tenantId },
      relations: ['flag'],
      order: { createdAt: 'DESC' },
    });
  }

  /**
   * Find all overrides for a flag
   */
  async findByFlag(flagId: string): Promise<TenantOverride[]> {
    return this.overrideRepository.find({
      where: { flagId },
      order: { tenantId: 'ASC' },
    });
  }

  /**
   * List overrides with filters and pagination
   */
  async findAll(
    filters: OverrideFilters = {},
    pagination: PaginationOptions = { page: 1, limit: 20 }
  ): Promise<PaginatedResult<OverrideWithFlag>> {
    const queryBuilder = this.overrideRepository
      .createQueryBuilder('override')
      .leftJoinAndSelect('override.flag', 'flag')
      .where('1 = 1');

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

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

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

    if (filters.expired !== undefined) {
      if (filters.expired) {
        queryBuilder.andWhere('override.expires_at IS NOT NULL AND override.expires_at < :now', {
          now: new Date(),
        });
      } else {
        queryBuilder.andWhere(
          '(override.expires_at IS NULL OR override.expires_at >= :now)',
          { now: new Date() }
        );
      }
    }

    if (filters.expiresSoon) {
      const sevenDaysFromNow = new Date();
      sevenDaysFromNow.setDate(sevenDaysFromNow.getDate() + 7);

      queryBuilder.andWhere(
        'override.expires_at IS NOT NULL AND override.expires_at >= :now AND override.expires_at <= :sevenDays',
        { now: new Date(), sevenDays: sevenDaysFromNow }
      );
    }

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

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

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

  /**
   * Update an override
   */
  async update(id: string, dto: UpdateOverrideDto): Promise<TenantOverride | null> {
    const override = await this.overrideRepository.findOne({
      where: { id },
    });

    if (!override) return null;

    if (dto.enabled !== undefined) {
      override.enabled = dto.enabled;
    }

    if (dto.reason !== undefined) {
      override.reason = dto.reason;
    }

    if (dto.expiresAt !== undefined) {
      override.expiresAt = dto.expiresAt as Date;
    }

    return this.overrideRepository.save(override);
  }

  /**
   * Delete an override
   */
  async delete(id: string): Promise<boolean> {
    const result = await this.overrideRepository.delete({ id });
    return (result.affected ?? 0) > 0;
  }

  /**
   * Delete override by flag and tenant
   */
  async deleteByFlagAndTenant(flagId: string, tenantId: string): Promise<boolean> {
    const result = await this.overrideRepository.delete({ flagId, tenantId });
    return (result.affected ?? 0) > 0;
  }

  /**
   * Delete all overrides for a tenant
   */
  async deleteAllForTenant(tenantId: string): Promise<number> {
    const result = await this.overrideRepository.delete({ tenantId });
    return result.affected ?? 0;
  }

  /**
   * Delete all overrides for a flag
   */
  async deleteAllForFlag(flagId: string): Promise<number> {
    const result = await this.overrideRepository.delete({ flagId });
    return result.affected ?? 0;
  }

  // ============================================
  // BULK OPERATIONS
  // ============================================

  /**
   * Apply multiple overrides for a tenant
   */
  async bulkUpsert(dto: BulkOverrideDto, userId?: string): Promise<TenantOverride[]> {
    const results: TenantOverride[] = [];

    // Get all flags by code
    const flagCodes = dto.overrides.map((o) => o.flagCode);
    const flags = await this.flagRepository.find({
      where: { code: In(flagCodes) },
    });

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

    for (const override of dto.overrides) {
      const flag = flagMap.get(override.flagCode);
      if (!flag) {
        console.warn(`Flag with code '${override.flagCode}' not found, skipping`);
        continue;
      }

      const result = await this.upsert(
        {
          flagId: flag.id,
          tenantId: dto.tenantId,
          enabled: override.enabled,
          reason: override.reason,
          expiresAt: override.expiresAt,
        },
        userId
      );

      results.push(result);
    }

    return results;
  }

  /**
   * Copy overrides from one tenant to another
   */
  async copyOverrides(
    sourceTenantId: string,
    targetTenantId: string,
    userId?: string
  ): Promise<TenantOverride[]> {
    const sourceOverrides = await this.findByTenant(sourceTenantId);
    const results: TenantOverride[] = [];

    for (const source of sourceOverrides) {
      // Skip expired overrides
      if (source.expiresAt && source.expiresAt < new Date()) {
        continue;
      }

      const result = await this.upsert(
        {
          flagId: source.flagId,
          tenantId: targetTenantId,
          enabled: source.enabled,
          reason: `Copied from tenant ${sourceTenantId}: ${source.reason || 'No reason'}`,
          expiresAt: source.expiresAt,
        },
        userId
      );

      results.push(result);
    }

    return results;
  }

  // ============================================
  // EXPIRATION MANAGEMENT
  // ============================================

  /**
   * Get overrides expiring soon
   */
  async getExpiringSoon(days = 7): Promise<OverrideWithFlag[]> {
    const futureDate = new Date();
    futureDate.setDate(futureDate.getDate() + days);

    return this.overrideRepository.find({
      where: {
        expiresAt: MoreThan(new Date()),
      },
      relations: ['flag'],
      order: { expiresAt: 'ASC' },
    });
  }

  /**
   * Get expired overrides
   */
  async getExpired(): Promise<TenantOverride[]> {
    return this.overrideRepository.find({
      where: {
        expiresAt: LessThan(new Date()),
      },
      order: { expiresAt: 'DESC' },
    });
  }

  /**
   * Clean up expired overrides
   */
  async cleanupExpired(): Promise<number> {
    const result = await this.overrideRepository
      .createQueryBuilder()
      .delete()
      .where('expires_at IS NOT NULL AND expires_at < :now', { now: new Date() })
      .execute();

    return result.affected ?? 0;
  }

  /**
   * Extend expiration for an override
   */
  async extendExpiration(id: string, newExpiresAt: Date): Promise<TenantOverride | null> {
    return this.update(id, { expiresAt: newExpiresAt });
  }

  /**
   * Remove expiration (make permanent)
   */
  async removeExpiration(id: string): Promise<TenantOverride | null> {
    return this.update(id, { expiresAt: null });
  }

  // ============================================
  // STATISTICS
  // ============================================

  /**
   * Get override statistics
   */
  async getStatistics(): Promise<{
    total: number;
    enabled: number;
    disabled: number;
    expiringSoon: number;
    expired: number;
    byTenant: Record<string, number>;
  }> {
    const now = new Date();
    const sevenDaysFromNow = new Date();
    sevenDaysFromNow.setDate(sevenDaysFromNow.getDate() + 7);

    const [total, enabled, expiringSoon, expired, byTenantRaw] = await Promise.all([
      this.overrideRepository.count(),

      this.overrideRepository.count({ where: { enabled: true } }),

      this.overrideRepository
        .createQueryBuilder('o')
        .where('o.expires_at IS NOT NULL')
        .andWhere('o.expires_at >= :now', { now })
        .andWhere('o.expires_at <= :sevenDays', { sevenDays: sevenDaysFromNow })
        .getCount(),

      this.overrideRepository
        .createQueryBuilder('o')
        .where('o.expires_at IS NOT NULL')
        .andWhere('o.expires_at < :now', { now })
        .getCount(),

      this.overrideRepository
        .createQueryBuilder('o')
        .select('o.tenant_id', 'tenantId')
        .addSelect('COUNT(*)', 'count')
        .groupBy('o.tenant_id')
        .getRawMany(),
    ]);

    const byTenant: Record<string, number> = {};
    byTenantRaw.forEach((row: { tenantId: string; count: string }) => {
      byTenant[row.tenantId] = parseInt(row.count, 10);
    });

    return {
      total,
      enabled,
      disabled: total - enabled,
      expiringSoon,
      expired,
      byTenant,
    };
  }

  /**
   * Check if a tenant has any active overrides
   */
  async hasOverrides(tenantId: string): Promise<boolean> {
    const count = await this.overrideRepository.count({
      where: { tenantId },
    });
    return count > 0;
  }

  /**
   * Get override count for a tenant
   */
  async getOverrideCount(tenantId: string): Promise<{ total: number; enabled: number; disabled: number }> {
    const [total, enabled] = await Promise.all([
      this.overrideRepository.count({ where: { tenantId } }),
      this.overrideRepository.count({ where: { tenantId, enabled: true } }),
    ]);

    return {
      total,
      enabled,
      disabled: total - enabled,
    };
  }
}
