import { Repository, FindOptionsWhere, ILike } from 'typeorm';
import { TenantSetting, SystemSetting, PlanSetting } from '../entities';
import { UpdateTenantSettingDto, CreateTenantSettingDto, TenantSettingsFiltersDto } from '../dto';
import { logger } from '../../../shared/utils/logger.js';

/**
 * Effective setting result combining value with source information
 */
export interface EffectiveSetting {
  key: string;
  value: any;
  source: 'system' | 'plan' | 'tenant';
  isOverridden: boolean;
  category?: string;
  description?: string;
}

/**
 * TenantSettingsService
 *
 * Manages tenant-specific settings with inheritance from system and plan defaults.
 *
 * Inheritance hierarchy:
 * 1. Tenant Settings (highest priority)
 * 2. Plan Settings (subscription plan defaults)
 * 3. System Settings (lowest priority, global defaults)
 *
 * Features:
 * - Get effective settings with inheritance resolution
 * - Override system/plan settings at tenant level
 * - Reset to inherited values
 */
export class TenantSettingsService {
  constructor(
    private readonly tenantSettingRepository: Repository<TenantSetting>,
    private readonly systemSettingRepository: Repository<SystemSetting>,
    private readonly planSettingRepository: Repository<PlanSetting>
  ) {}

  /**
   * Get the effective value for a setting key, applying inheritance
   */
  async getEffective(tenantId: string, key: string, planId?: string): Promise<EffectiveSetting | null> {
    // 1. Check tenant override first
    const tenantSetting = await this.tenantSettingRepository.findOne({
      where: { tenantId, key, isOverridden: true },
    });

    if (tenantSetting) {
      return {
        key,
        value: tenantSetting.value,
        source: 'tenant',
        isOverridden: true,
      };
    }

    // 2. Check plan settings if planId provided
    if (planId) {
      const planSetting = await this.planSettingRepository.findOne({
        where: { planId, key },
      });

      if (planSetting) {
        return {
          key,
          value: planSetting.value,
          source: 'plan',
          isOverridden: false,
        };
      }
    }

    // 3. Fall back to system setting
    const systemSetting = await this.systemSettingRepository.findOne({
      where: { key },
    });

    if (systemSetting) {
      return {
        key,
        value: systemSetting.value ?? systemSetting.defaultValue,
        source: 'system',
        isOverridden: false,
        category: systemSetting.category,
        description: systemSetting.description ?? undefined,
      };
    }

    return null;
  }

  /**
   * Get all effective settings for a tenant, merging all sources
   */
  async getAllEffective(tenantId: string, planId?: string): Promise<EffectiveSetting[]> {
    const effectiveSettings: Map<string, EffectiveSetting> = new Map();

    // 1. Start with system settings (public ones)
    const systemSettings = await this.systemSettingRepository.find({
      where: { isPublic: true },
      order: { category: 'ASC', key: 'ASC' },
    });

    for (const setting of systemSettings) {
      effectiveSettings.set(setting.key, {
        key: setting.key,
        value: setting.value ?? setting.defaultValue,
        source: 'system',
        isOverridden: false,
        category: setting.category,
        description: setting.description ?? undefined,
      });
    }

    // 2. Apply plan settings (if planId provided)
    if (planId) {
      const planSettings = await this.planSettingRepository.find({
        where: { planId },
      });

      for (const setting of planSettings) {
        const existing = effectiveSettings.get(setting.key);
        effectiveSettings.set(setting.key, {
          key: setting.key,
          value: setting.value,
          source: 'plan',
          isOverridden: false,
          category: existing?.category,
          description: existing?.description,
        });
      }
    }

    // 3. Apply tenant overrides
    const tenantSettings = await this.tenantSettingRepository.find({
      where: { tenantId, isOverridden: true },
    });

    for (const setting of tenantSettings) {
      const existing = effectiveSettings.get(setting.key);
      effectiveSettings.set(setting.key, {
        key: setting.key,
        value: setting.value,
        source: 'tenant',
        isOverridden: true,
        category: existing?.category,
        description: existing?.description,
      });
    }

    return Array.from(effectiveSettings.values()).sort((a, b) => {
      const catCompare = (a.category || '').localeCompare(b.category || '');
      if (catCompare !== 0) return catCompare;
      return a.key.localeCompare(b.key);
    });
  }

  /**
   * Get tenant setting by key (direct, without inheritance)
   */
  async get(tenantId: string, key: string): Promise<TenantSetting | null> {
    return this.tenantSettingRepository.findOne({
      where: { tenantId, key },
    });
  }

  /**
   * Get all tenant settings with optional filtering
   */
  async getAll(
    tenantId: string,
    filters: TenantSettingsFiltersDto = {}
  ): Promise<TenantSetting[]> {
    const { inheritedFrom, isOverridden, search } = filters;

    const where: FindOptionsWhere<TenantSetting>[] = [];
    const baseWhere: FindOptionsWhere<TenantSetting> = { tenantId };

    if (inheritedFrom) {
      baseWhere.inheritedFrom = inheritedFrom;
    }

    if (isOverridden !== undefined) {
      baseWhere.isOverridden = isOverridden;
    }

    if (search) {
      where.push({ ...baseWhere, key: ILike(`%${search}%`) });
    } else {
      where.push(baseWhere);
    }

    return this.tenantSettingRepository.find({
      where,
      order: { key: 'ASC' },
    });
  }

  /**
   * Update or create a tenant setting (upsert)
   */
  async update(
    tenantId: string,
    key: string,
    dto: UpdateTenantSettingDto
  ): Promise<TenantSetting> {
    let setting = await this.tenantSettingRepository.findOne({
      where: { tenantId, key },
    });

    if (setting) {
      Object.assign(setting, dto);
      const updated = await this.tenantSettingRepository.save(setting);
      logger.info('Tenant setting updated', { tenantId, key });
      return updated;
    }

    // Create new setting
    setting = this.tenantSettingRepository.create({
      tenantId,
      key,
      value: dto.value,
      inheritedFrom: 'custom',
      isOverridden: dto.isOverridden ?? true,
    });

    const created = await this.tenantSettingRepository.save(setting);
    logger.info('Tenant setting created', { tenantId, key });
    return created;
  }

  /**
   * Create a new tenant setting
   */
  async create(tenantId: string, dto: CreateTenantSettingDto): Promise<TenantSetting> {
    const existing = await this.tenantSettingRepository.findOne({
      where: { tenantId, key: dto.key },
    });

    if (existing) {
      throw new Error(`Setting '${dto.key}' already exists for this tenant`);
    }

    const setting = this.tenantSettingRepository.create({
      ...dto,
      tenantId,
      inheritedFrom: dto.inheritedFrom ?? 'custom',
      isOverridden: dto.isOverridden ?? true,
    });

    const created = await this.tenantSettingRepository.save(setting);
    logger.info('Tenant setting created', { tenantId, key: dto.key });
    return created;
  }

  /**
   * Reset a tenant setting to inherited value
   */
  async reset(tenantId: string, key: string): Promise<boolean> {
    const result = await this.tenantSettingRepository.delete({ tenantId, key });
    const deleted = (result.affected ?? 0) > 0;

    if (deleted) {
      logger.info('Tenant setting reset to inherited', { tenantId, key });
    }

    return deleted;
  }

  /**
   * Bulk update tenant settings
   */
  async bulkUpdate(
    tenantId: string,
    settings: Array<{ key: string; value: any; isOverridden?: boolean }>
  ): Promise<TenantSetting[]> {
    const results: TenantSetting[] = [];

    for (const { key, value, isOverridden } of settings) {
      const updated = await this.update(tenantId, key, {
        value,
        isOverridden: isOverridden ?? true,
      });
      results.push(updated);
    }

    logger.info('Tenant settings bulk updated', {
      tenantId,
      count: settings.length,
    });

    return results;
  }

  /**
   * Delete a tenant setting override
   */
  async delete(tenantId: string, key: string): Promise<boolean> {
    const result = await this.tenantSettingRepository.delete({ tenantId, key });
    const deleted = (result.affected ?? 0) > 0;

    if (deleted) {
      logger.info('Tenant setting deleted', { tenantId, key });
    }

    return deleted;
  }
}
