/**
 * SystemSettingService - Gestión de Configuración del Sistema
 *
 * Administra configuraciones del sistema por tenant.
 *
 * @module Admin
 */

import { Repository } from 'typeorm';
import { SystemSetting, SettingCategory, SettingDataType } from '../entities/system-setting.entity';

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

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

export interface CreateSettingDto {
  key: string;
  name: string;
  description?: string;
  category?: SettingCategory;
  dataType?: SettingDataType;
  value: string;
  defaultValue?: string;
  validation?: Record<string, any>;
  options?: Record<string, any>[];
  isPublic?: boolean;
  isEncrypted?: boolean;
  requiresRestart?: boolean;
  allowedRoles?: string[];
}

export interface UpdateSettingDto {
  name?: string;
  description?: string;
  value?: string;
  validation?: Record<string, any>;
  options?: Record<string, any>[];
  isPublic?: boolean;
  allowedRoles?: string[];
  sortOrder?: number;
}

export interface SettingFilters {
  category?: SettingCategory;
  isPublic?: boolean;
  search?: string;
}

export class SystemSettingService {
  private repository: Repository<SystemSetting>;

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

  async create(ctx: ServiceContext, data: Partial<SystemSetting>): Promise<SystemSetting> {
    const entity = this.repository.create({
      ...data,
      tenantId: ctx.tenantId,
    });
    return this.repository.save(entity);
  }

  async findById(ctx: ServiceContext, id: string): Promise<SystemSetting | null> {
    return this.repository.findOne({
      where: { id, tenantId: ctx.tenantId } as any,
    });
  }

  async update(ctx: ServiceContext, id: string, data: Partial<SystemSetting>): Promise<SystemSetting | null> {
    const entity = await this.findById(ctx, id);
    if (!entity) return null;
    Object.assign(entity, data);
    return this.repository.save(entity);
  }

  async hardDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const result = await this.repository.delete({ id, tenantId: ctx.tenantId } as any);
    return (result.affected || 0) > 0;
  }

  /**
   * Obtener valor de configuración
   */
  async getValue(ctx: ServiceContext, key: string): Promise<any> {
    const setting = await this.findByKey(ctx, key);
    if (!setting) {
      return null;
    }

    return this.parseValue(setting.value, setting.dataType);
  }

  /**
   * Obtener valor con default
   */
  async getValueOrDefault(ctx: ServiceContext, key: string, defaultValue: any): Promise<any> {
    const value = await this.getValue(ctx, key);
    return value !== null ? value : defaultValue;
  }

  /**
   * Buscar por key
   */
  async findByKey(ctx: ServiceContext, key: string): Promise<SystemSetting | null> {
    return this.repository.findOne({
      where: {
        tenantId: ctx.tenantId,
        key,
      } as any,
    });
  }

  /**
   * Buscar configuraciones con filtros
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: SettingFilters,
    page = 1,
    limit = 50
  ): Promise<PaginatedResult<SystemSetting>> {
    const qb = this.repository
      .createQueryBuilder('ss')
      .where('ss.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.category) {
      qb.andWhere('ss.category = :category', { category: filters.category });
    }
    if (filters.isPublic !== undefined) {
      qb.andWhere('ss.is_public = :isPublic', { isPublic: filters.isPublic });
    }
    if (filters.search) {
      qb.andWhere(
        '(ss.key ILIKE :search OR ss.name ILIKE :search OR ss.description ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

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

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

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

  /**
   * Obtener por categoría
   */
  async findByCategory(ctx: ServiceContext, category: SettingCategory): Promise<SystemSetting[]> {
    return this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        category,
      } as any,
      order: { sortOrder: 'ASC', key: 'ASC' },
    });
  }

  /**
   * Obtener configuraciones públicas
   */
  async getPublicSettings(ctx: ServiceContext): Promise<Record<string, any>> {
    const settings = await this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        isPublic: true,
      } as any,
    });

    const result: Record<string, any> = {};
    for (const setting of settings) {
      result[setting.key] = this.parseValue(setting.value, setting.dataType);
    }

    return result;
  }

  /**
   * Crear configuración
   */
  async createSetting(ctx: ServiceContext, data: CreateSettingDto): Promise<SystemSetting> {
    const existing = await this.findByKey(ctx, data.key);
    if (existing) {
      throw new Error(`Setting with key ${data.key} already exists`);
    }

    // Validate value against type and validation rules
    this.validateValue(data.value, data.dataType || 'string', data.validation);

    return this.create(ctx, {
      ...data,
      isSystem: false,
    });
  }

  /**
   * Actualizar valor
   */
  async updateValue(ctx: ServiceContext, key: string, value: string): Promise<SystemSetting | null> {
    const setting = await this.findByKey(ctx, key);
    if (!setting) {
      return null;
    }

    // Validate value against type and validation rules
    this.validateValue(value, setting.dataType, setting.validation);

    return this.update(ctx, setting.id, { value });
  }

  /**
   * Restablecer a valor por defecto
   */
  async resetToDefault(ctx: ServiceContext, key: string): Promise<SystemSetting | null> {
    const setting = await this.findByKey(ctx, key);
    if (!setting || !setting.defaultValue) {
      return null;
    }

    return this.update(ctx, setting.id, { value: setting.defaultValue });
  }

  /**
   * Actualizar múltiples configuraciones
   */
  async updateMultiple(
    ctx: ServiceContext,
    settings: { key: string; value: string }[]
  ): Promise<{ updated: number; errors: string[] }> {
    let updated = 0;
    const errors: string[] = [];

    for (const { key, value } of settings) {
      try {
        const result = await this.updateValue(ctx, key, value);
        if (result) {
          updated++;
        } else {
          errors.push(`Setting ${key} not found`);
        }
      } catch (error) {
        errors.push(`${key}: ${error instanceof Error ? error.message : 'Unknown error'}`);
      }
    }

    return { updated, errors };
  }

  /**
   * Parsear valor según tipo
   */
  private parseValue(value: string, dataType: SettingDataType): any {
    switch (dataType) {
      case 'number':
        return parseFloat(value);
      case 'boolean':
        return value === 'true' || value === '1';
      case 'json':
      case 'array':
        try {
          return JSON.parse(value);
        } catch {
          return null;
        }
      default:
        return value;
    }
  }

  /**
   * Validar valor
   */
  private validateValue(
    value: string,
    dataType: SettingDataType,
    validation?: Record<string, any> | null
  ): void {
    // Basic type validation
    switch (dataType) {
      case 'number':
        if (isNaN(parseFloat(value))) {
          throw new Error('Value must be a valid number');
        }
        break;
      case 'boolean':
        if (!['true', 'false', '1', '0'].includes(value)) {
          throw new Error('Value must be true/false or 1/0');
        }
        break;
      case 'json':
      case 'array':
        try {
          JSON.parse(value);
        } catch {
          throw new Error('Value must be valid JSON');
        }
        break;
    }

    // Custom validation rules
    if (validation) {
      if (validation.min !== undefined && parseFloat(value) < validation.min) {
        throw new Error(`Value must be at least ${validation.min}`);
      }
      if (validation.max !== undefined && parseFloat(value) > validation.max) {
        throw new Error(`Value must be at most ${validation.max}`);
      }
      if (validation.pattern && !new RegExp(validation.pattern).test(value)) {
        throw new Error(`Value does not match required pattern`);
      }
      if (validation.options && !validation.options.includes(value)) {
        throw new Error(`Value must be one of: ${validation.options.join(', ')}`);
      }
    }
  }

  /**
   * Obtener estadísticas
   */
  async getStats(ctx: ServiceContext): Promise<SettingStats> {
    const all = await this.repository.find({
      where: { tenantId: ctx.tenantId } as any,
    });

    const byCategory = new Map<SettingCategory, number>();
    let publicCount = 0;
    let systemCount = 0;
    let encryptedCount = 0;

    for (const setting of all) {
      byCategory.set(setting.category, (byCategory.get(setting.category) || 0) + 1);
      if (setting.isPublic) publicCount++;
      if (setting.isSystem) systemCount++;
      if (setting.isEncrypted) encryptedCount++;
    }

    return {
      total: all.length,
      publicCount,
      systemCount,
      encryptedCount,
      byCategory: Array.from(byCategory.entries()).map(([category, count]) => ({ category, count })),
    };
  }
}

export interface SettingStats {
  total: number;
  publicCount: number;
  systemCount: number;
  encryptedCount: number;
  byCategory: { category: SettingCategory; count: number }[];
}
