/**
 * TenantSettingService - Tenant-level Configuration Management
 *
 * Manages custom settings per tenant, overriding system and plan defaults.
 * Hierarchy: system_settings < plan_settings < tenant_settings
 *
 * @module Settings
 */

import { Repository } from 'typeorm';
import { TenantSetting, SystemSetting, PlanSetting } from '../entities';

interface ServiceContext {
  tenantId: string;
  userId?: string;
}

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

export type InheritedFrom = 'system' | 'plan' | 'custom';

export interface CreateTenantSettingDto {
  key: string;
  value: any;
  inheritedFrom?: InheritedFrom;
  isOverridden?: boolean;
}

export interface UpdateTenantSettingDto {
  value: any;
  inheritedFrom?: InheritedFrom;
  isOverridden?: boolean;
}

export interface TenantSettingFilters {
  inheritedFrom?: InheritedFrom;
  isOverridden?: boolean;
  search?: string;
}

export interface EffectiveSettingResult {
  key: string;
  value: any;
  source: 'system' | 'plan' | 'tenant';
  isOverridden: boolean;
}

export class TenantSettingService {
  private repository: Repository<TenantSetting>;
  private systemSettingRepository: Repository<SystemSetting>;
  private planSettingRepository: Repository<PlanSetting>;

  constructor(
    repository: Repository<TenantSetting>,
    systemSettingRepository: Repository<SystemSetting>,
    planSettingRepository: Repository<PlanSetting>
  ) {
    this.repository = repository;
    this.systemSettingRepository = systemSettingRepository;
    this.planSettingRepository = planSettingRepository;
  }

  /**
   * Create a new tenant setting
   */
  async create(ctx: ServiceContext, data: CreateTenantSettingDto): Promise<TenantSetting> {
    const existing = await this.findByKey(ctx, data.key);
    if (existing) {
      throw new Error(`Tenant setting with key '${data.key}' already exists`);
    }

    const entity = this.repository.create({
      tenantId: ctx.tenantId,
      key: data.key,
      value: data.value,
      inheritedFrom: data.inheritedFrom ?? 'custom',
      isOverridden: data.isOverridden ?? true,
    });

    return this.repository.save(entity);
  }

  /**
   * Find tenant setting by ID
   */
  async findById(ctx: ServiceContext, id: string): Promise<TenantSetting | null> {
    return this.repository.findOne({
      where: { id, tenantId: ctx.tenantId },
    });
  }

  /**
   * Find tenant setting by key
   */
  async findByKey(ctx: ServiceContext, key: string): Promise<TenantSetting | null> {
    return this.repository.findOne({
      where: { tenantId: ctx.tenantId, key },
    });
  }

  /**
   * Get value for a tenant setting
   */
  async getValue(ctx: ServiceContext, key: string): Promise<any> {
    const setting = await this.findByKey(ctx, key);
    return setting?.value ?? null;
  }

  /**
   * Get effective value with inheritance hierarchy
   * Order: tenant > plan > system
   */
  async getEffectiveValue(ctx: ServiceContext, key: string, planId?: string): Promise<any> {
    // First check tenant setting
    const tenantSetting = await this.findByKey(ctx, key);
    if (tenantSetting && tenantSetting.isOverridden) {
      return tenantSetting.value;
    }

    // Then check plan setting
    if (planId) {
      const planSetting = await this.planSettingRepository.findOne({
        where: { planId, key },
      });
      if (planSetting) {
        return planSetting.value;
      }
    }

    // Finally check system setting
    const systemSetting = await this.systemSettingRepository.findOne({
      where: { key },
    });
    if (systemSetting) {
      return systemSetting.value;
    }

    return null;
  }

  /**
   * Get all effective settings with source information
   */
  async getEffectiveSettings(ctx: ServiceContext, planId?: string): Promise<EffectiveSettingResult[]> {
    const results: Map<string, EffectiveSettingResult> = new Map();

    // Start with system settings (base layer)
    const systemSettings = await this.systemSettingRepository.find();
    for (const setting of systemSettings) {
      results.set(setting.key, {
        key: setting.key,
        value: setting.value,
        source: 'system',
        isOverridden: false,
      });
    }

    // Override with plan settings
    if (planId) {
      const planSettings = await this.planSettingRepository.find({
        where: { planId },
      });
      for (const setting of planSettings) {
        results.set(setting.key, {
          key: setting.key,
          value: setting.value,
          source: 'plan',
          isOverridden: results.has(setting.key),
        });
      }
    }

    // Override with tenant settings
    const tenantSettings = await this.repository.find({
      where: { tenantId: ctx.tenantId },
    });
    for (const setting of tenantSettings) {
      if (setting.isOverridden) {
        results.set(setting.key, {
          key: setting.key,
          value: setting.value,
          source: 'tenant',
          isOverridden: results.has(setting.key),
        });
      }
    }

    return Array.from(results.values());
  }

  /**
   * Get all tenant settings
   */
  async findAll(ctx: ServiceContext): Promise<TenantSetting[]> {
    return this.repository.find({
      where: { tenantId: ctx.tenantId },
      order: { key: 'ASC' },
    });
  }

  /**
   * Get all tenant settings as key-value map
   */
  async getTenantSettingsMap(ctx: ServiceContext): Promise<Record<string, any>> {
    const settings = await this.findAll(ctx);
    const result: Record<string, any> = {};
    for (const setting of settings) {
      result[setting.key] = setting.value;
    }
    return result;
  }

  /**
   * Update tenant setting
   */
  async update(ctx: ServiceContext, id: string, data: UpdateTenantSettingDto): Promise<TenantSetting | null> {
    const entity = await this.findById(ctx, id);
    if (!entity) {
      return null;
    }

    Object.assign(entity, data);
    return this.repository.save(entity);
  }

  /**
   * Update or create tenant setting by key (upsert)
   */
  async upsert(ctx: ServiceContext, key: string, value: any): Promise<TenantSetting> {
    const existing = await this.findByKey(ctx, key);

    if (existing) {
      existing.value = value;
      existing.isOverridden = true;
      return this.repository.save(existing);
    }

    return this.create(ctx, {
      key,
      value,
      inheritedFrom: 'custom',
      isOverridden: true,
    });
  }

  /**
   * Reset setting to inherited value (remove override)
   */
  async resetToInherited(ctx: ServiceContext, key: string): Promise<TenantSetting | null> {
    const setting = await this.findByKey(ctx, key);
    if (!setting) {
      return null;
    }

    setting.isOverridden = false;
    return this.repository.save(setting);
  }

  /**
   * Find settings with filters
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: TenantSettingFilters,
    page = 1,
    limit = 50
  ): Promise<PaginatedResult<TenantSetting>> {
    const qb = this.repository
      .createQueryBuilder('ts')
      .where('ts.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.inheritedFrom) {
      qb.andWhere('ts.inherited_from = :inheritedFrom', { inheritedFrom: filters.inheritedFrom });
    }
    if (filters.isOverridden !== undefined) {
      qb.andWhere('ts.is_overridden = :isOverridden', { isOverridden: filters.isOverridden });
    }
    if (filters.search) {
      qb.andWhere('ts.key ILIKE :search', { search: `%${filters.search}%` });
    }

    const skip = (page - 1) * limit;
    qb.orderBy('ts.key', 'ASC')
      .skip(skip)
      .take(limit);

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

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

  /**
   * Bulk update tenant settings
   */
  async updateMultiple(
    ctx: ServiceContext,
    settings: { key: string; value: any }[]
  ): Promise<{ updated: number; created: number; errors: string[] }> {
    let updated = 0;
    let created = 0;
    const errors: string[] = [];

    for (const { key, value } of settings) {
      try {
        const existing = await this.findByKey(ctx, key);
        if (existing) {
          existing.value = value;
          existing.isOverridden = true;
          await this.repository.save(existing);
          updated++;
        } else {
          await this.create(ctx, { key, value });
          created++;
        }
      } catch (error) {
        errors.push(`${key}: ${error instanceof Error ? error.message : 'Unknown error'}`);
      }
    }

    return { updated, created, errors };
  }

  /**
   * Delete tenant setting (hard delete)
   */
  async hardDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const result = await this.repository.delete({ id, tenantId: ctx.tenantId });
    return (result.affected || 0) > 0;
  }

  /**
   * Initialize tenant settings from plan defaults
   */
  async initializeFromPlan(ctx: ServiceContext, planId: string): Promise<number> {
    const planSettings = await this.planSettingRepository.find({
      where: { planId },
    });

    let initialized = 0;
    for (const planSetting of planSettings) {
      const existing = await this.findByKey(ctx, planSetting.key);
      if (!existing) {
        await this.create(ctx, {
          key: planSetting.key,
          value: planSetting.value,
          inheritedFrom: 'plan',
          isOverridden: false,
        });
        initialized++;
      }
    }

    return initialized;
  }

  /**
   * Get overridden settings only
   */
  async getOverriddenSettings(ctx: ServiceContext): Promise<TenantSetting[]> {
    return this.repository.find({
      where: { tenantId: ctx.tenantId, isOverridden: true },
      order: { key: 'ASC' },
    });
  }
}
