/**
 * Flag Service
 * ERP Construccion - Feature Flags Module
 *
 * Business logic for managing feature flags.
 * Supports multi-tenancy via ServiceContext pattern.
 *
 * @module FeatureFlags
 */

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

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

export interface CreateFlagDto {
  code: string;
  name: string;
  description?: string;
  enabled?: boolean;
  rolloutPercentage?: number;
  tags?: string[];
}

export interface UpdateFlagDto {
  name?: string;
  description?: string;
  enabled?: boolean;
  rolloutPercentage?: number;
  tags?: string[];
  isActive?: boolean;
}

export interface FlagFilters {
  enabled?: boolean;
  isActive?: boolean;
  tags?: string[];
  search?: string;
}

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

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

export interface FlagWithOverride extends Flag {
  tenantOverride?: TenantOverride | null;
  effectiveEnabled?: boolean;
}

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

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

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

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

  /**
   * Create a new feature flag
   */
  async create(dto: CreateFlagDto, userId?: string): Promise<Flag> {
    // Check code uniqueness
    const existing = await this.flagRepository.findOne({
      where: { code: dto.code },
    });

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

    // Validate code format (alphanumeric with underscores, uppercase)
    const codePattern = /^[A-Z][A-Z0-9_]*$/;
    if (!codePattern.test(dto.code)) {
      throw new Error(
        'Flag code must start with uppercase letter and contain only uppercase letters, numbers, and underscores'
      );
    }

    // Validate rollout percentage
    if (dto.rolloutPercentage !== undefined) {
      if (dto.rolloutPercentage < 0 || dto.rolloutPercentage > 100) {
        throw new Error('Rollout percentage must be between 0 and 100');
      }
    }

    const flag = this.flagRepository.create({
      ...dto,
      enabled: dto.enabled ?? false,
      rolloutPercentage: dto.rolloutPercentage ?? 100,
      isActive: true,
      createdBy: userId,
    });

    return this.flagRepository.save(flag);
  }

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

  /**
   * Find flag by code
   */
  async findByCode(code: string): Promise<Flag | null> {
    return this.flagRepository.findOne({
      where: { code },
      relations: ['overrides'],
    });
  }

  /**
   * Find multiple flags by codes
   */
  async findByCodes(codes: string[]): Promise<Flag[]> {
    if (codes.length === 0) return [];

    return this.flagRepository.find({
      where: { code: In(codes), isActive: true },
    });
  }

  /**
   * List all flags with filters and pagination
   */
  async findAll(
    filters: FlagFilters = {},
    pagination: PaginationOptions = { page: 1, limit: 20 }
  ): Promise<PaginatedResult<Flag>> {
    const queryBuilder = this.flagRepository
      .createQueryBuilder('flag')
      .where('1 = 1');

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

    if (filters.isActive !== undefined) {
      queryBuilder.andWhere('flag.is_active = :isActive', { isActive: filters.isActive });
    }

    if (filters.search) {
      queryBuilder.andWhere(
        '(flag.code ILIKE :search OR flag.name ILIKE :search OR flag.description ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

    if (filters.tags && filters.tags.length > 0) {
      queryBuilder.andWhere('flag.tags && :tags', { tags: filters.tags });
    }

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

    const [data, total] = await queryBuilder
      .orderBy('flag.code', 'ASC')
      .skip(skip)
      .take(pagination.limit)
      .getManyAndCount();

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

  /**
   * Get all active flags (for bulk evaluation)
   */
  async findAllActive(): Promise<Flag[]> {
    return this.flagRepository.find({
      where: { isActive: true },
      order: { code: 'ASC' },
    });
  }

  /**
   * Update a flag
   */
  async update(id: string, dto: UpdateFlagDto, userId?: string): Promise<Flag | null> {
    const flag = await this.findById(id);
    if (!flag) return null;

    // Validate rollout percentage
    if (dto.rolloutPercentage !== undefined) {
      if (dto.rolloutPercentage < 0 || dto.rolloutPercentage > 100) {
        throw new Error('Rollout percentage must be between 0 and 100');
      }
    }

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

  /**
   * Toggle flag enabled status
   */
  async toggle(id: string, userId?: string): Promise<Flag | null> {
    const flag = await this.findById(id);
    if (!flag) return null;

    flag.enabled = !flag.enabled;
    flag.updatedBy = userId || null;

    return this.flagRepository.save(flag);
  }

  /**
   * Update rollout percentage
   */
  async updateRollout(id: string, percentage: number, userId?: string): Promise<Flag | null> {
    if (percentage < 0 || percentage > 100) {
      throw new Error('Rollout percentage must be between 0 and 100');
    }

    return this.update(id, { rolloutPercentage: percentage }, userId);
  }

  /**
   * Soft delete a flag (set isActive = false)
   */
  async delete(id: string, userId?: string): Promise<boolean> {
    const result = await this.flagRepository.update(
      { id },
      { isActive: false, updatedBy: userId }
    );
    return (result.affected ?? 0) > 0;
  }

  /**
   * Hard delete a flag (permanently remove)
   */
  async hardDelete(id: string): Promise<boolean> {
    const result = await this.flagRepository.delete({ id });
    return (result.affected ?? 0) > 0;
  }

  // ============================================
  // TENANT-AWARE OPERATIONS
  // ============================================

  /**
   * Get flag with tenant-specific override
   */
  async findByIdForTenant(id: string, tenantId: string): Promise<FlagWithOverride | null> {
    const flag = await this.findById(id);
    if (!flag) return null;

    const override = await this.overrideRepository.findOne({
      where: { flagId: id, tenantId },
    });

    const result: FlagWithOverride = {
      ...flag,
      tenantOverride: override,
      effectiveEnabled: this.calculateEffectiveEnabled(flag, override),
    };

    return result;
  }

  /**
   * Get flag by code with tenant-specific override
   */
  async findByCodeForTenant(code: string, tenantId: string): Promise<FlagWithOverride | null> {
    const flag = await this.findByCode(code);
    if (!flag) return null;

    const override = await this.overrideRepository.findOne({
      where: { flagId: flag.id, tenantId },
    });

    return {
      ...flag,
      tenantOverride: override,
      effectiveEnabled: this.calculateEffectiveEnabled(flag, override),
    };
  }

  /**
   * Get all flags with tenant-specific overrides
   */
  async findAllForTenant(
    tenantId: string,
    filters: FlagFilters = {},
    pagination: PaginationOptions = { page: 1, limit: 20 }
  ): Promise<PaginatedResult<FlagWithOverride>> {
    const baseResult = await this.findAll(filters, pagination);

    if (baseResult.data.length === 0) {
      return { ...baseResult, data: [] };
    }

    const flagIds = baseResult.data.map((f) => f.id);
    const overrides = await this.overrideRepository.find({
      where: { flagId: In(flagIds), tenantId },
    });

    const overrideMap = new Map(overrides.map((o) => [o.flagId, o]));

    const dataWithOverrides: FlagWithOverride[] = baseResult.data.map((flag) => {
      const override = overrideMap.get(flag.id) || null;
      return {
        ...flag,
        tenantOverride: override,
        effectiveEnabled: this.calculateEffectiveEnabled(flag, override),
      };
    });

    return {
      ...baseResult,
      data: dataWithOverrides,
    };
  }

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

  /**
   * Calculate effective enabled status considering override
   */
  private calculateEffectiveEnabled(flag: Flag, override: TenantOverride | null): boolean {
    if (!flag.isActive) return false;

    if (override) {
      // Check if override has expired
      if (override.expiresAt && override.expiresAt < new Date()) {
        return flag.enabled;
      }
      return override.enabled;
    }

    return flag.enabled;
  }

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

  /**
   * Get flag statistics
   */
  async getStatistics(): Promise<{
    total: number;
    enabled: number;
    disabled: number;
    active: number;
    inactive: number;
    byTag: Record<string, number>;
  }> {
    const [total, enabled, active, tagsRaw] = await Promise.all([
      this.flagRepository.count(),

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

      this.flagRepository.count({ where: { isActive: true } }),

      this.flagRepository
        .createQueryBuilder('flag')
        .select('UNNEST(flag.tags)', 'tag')
        .addSelect('COUNT(*)', 'count')
        .where('flag.tags IS NOT NULL')
        .andWhere('flag.is_active = true')
        .groupBy('tag')
        .getRawMany(),
    ]);

    const byTag: Record<string, number> = {};
    tagsRaw.forEach((row: { tag: string; count: string }) => {
      byTag[row.tag] = parseInt(row.count, 10);
    });

    return {
      total,
      enabled,
      disabled: total - enabled,
      active,
      inactive: total - active,
      byTag,
    };
  }

  /**
   * Search flags for autocomplete
   */
  async search(query: string, limit = 10): Promise<Flag[]> {
    return this.flagRepository
      .createQueryBuilder('flag')
      .where('flag.is_active = true')
      .andWhere('(flag.code ILIKE :query OR flag.name ILIKE :query)', {
        query: `%${query}%`,
      })
      .orderBy('flag.code', 'ASC')
      .take(limit)
      .getMany();
  }
}
