/**
 * PlanSettingService - Plan-level Configuration Management
 *
 * Manages default settings per subscription plan.
 * Hierarchy: system_settings < plan_settings < tenant_settings
 *
 * @module Settings
 */

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

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

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

export interface CreatePlanSettingDto {
  planId: string;
  key: string;
  value: any;
}

export interface UpdatePlanSettingDto {
  value: any;
}

export interface PlanSettingFilters {
  planId?: string;
  search?: string;
}

export class PlanSettingService {
  private repository: Repository<PlanSetting>;

  constructor(repository: Repository<PlanSetting>) {
    this.repository = repository;
  }

  /**
   * Create a new plan setting
   */
  async create(_ctx: ServiceContext, data: CreatePlanSettingDto): Promise<PlanSetting> {
    const existing = await this.findByPlanAndKey(data.planId, data.key);
    if (existing) {
      throw new Error(`Plan setting with key '${data.key}' already exists for this plan`);
    }

    const entity = this.repository.create({
      planId: data.planId,
      key: data.key,
      value: data.value,
    });

    return this.repository.save(entity);
  }

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

  /**
   * Find plan setting by plan ID and key
   */
  async findByPlanAndKey(planId: string, key: string): Promise<PlanSetting | null> {
    return this.repository.findOne({
      where: { planId, key },
    });
  }

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

  /**
   * Get all settings for a plan
   */
  async findByPlan(planId: string): Promise<PlanSetting[]> {
    return this.repository.find({
      where: { planId },
      order: { key: 'ASC' },
    });
  }

  /**
   * Get all settings for a plan as key-value map
   */
  async getPlanSettingsMap(planId: string): Promise<Record<string, any>> {
    const settings = await this.findByPlan(planId);
    const result: Record<string, any> = {};
    for (const setting of settings) {
      result[setting.key] = setting.value;
    }
    return result;
  }

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

    entity.value = data.value;
    return this.repository.save(entity);
  }

  /**
   * Update or create plan setting by plan and key (upsert)
   */
  async upsert(_ctx: ServiceContext, planId: string, key: string, value: any): Promise<PlanSetting> {
    const existing = await this.findByPlanAndKey(planId, key);

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

    const entity = this.repository.create({
      planId,
      key,
      value,
    });

    return this.repository.save(entity);
  }

  /**
   * Find settings with filters
   */
  async findWithFilters(
    filters: PlanSettingFilters,
    page = 1,
    limit = 50
  ): Promise<PaginatedResult<PlanSetting>> {
    const qb = this.repository.createQueryBuilder('ps');

    if (filters.planId) {
      qb.andWhere('ps.plan_id = :planId', { planId: filters.planId });
    }
    if (filters.search) {
      qb.andWhere('ps.key ILIKE :search', { search: `%${filters.search}%` });
    }

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

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

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

  /**
   * Bulk update plan settings
   */
  async updateMultiple(
    ctx: ServiceContext,
    planId: string,
    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.findByPlanAndKey(planId, key);
        if (existing) {
          existing.value = value;
          await this.repository.save(existing);
          updated++;
        } else {
          await this.create(ctx, { planId, key, value });
          created++;
        }
      } catch (error) {
        errors.push(`${key}: ${error instanceof Error ? error.message : 'Unknown error'}`);
      }
    }

    return { updated, created, errors };
  }

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

  /**
   * Delete all settings for a plan
   */
  async deleteByPlan(planId: string): Promise<number> {
    const result = await this.repository.delete({ planId });
    return result.affected || 0;
  }

  /**
   * Copy settings from one plan to another
   */
  async copyFromPlan(
    ctx: ServiceContext,
    sourcePlanId: string,
    targetPlanId: string,
    overwrite = false
  ): Promise<{ copied: number; skipped: number }> {
    const sourceSettings = await this.findByPlan(sourcePlanId);
    let copied = 0;
    let skipped = 0;

    for (const setting of sourceSettings) {
      const existing = await this.findByPlanAndKey(targetPlanId, setting.key);

      if (existing && !overwrite) {
        skipped++;
        continue;
      }

      if (existing) {
        existing.value = setting.value;
        await this.repository.save(existing);
      } else {
        await this.create(ctx, {
          planId: targetPlanId,
          key: setting.key,
          value: setting.value,
        });
      }
      copied++;
    }

    return { copied, skipped };
  }
}
