/**
 * SystemSettingService - Global System Configuration Management
 *
 * Manages global system-wide settings across all tenants.
 * These are the base defaults that can be overridden by plan/tenant settings.
 *
 * @module Settings
 */

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

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

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

export type SettingDataType = 'string' | 'number' | 'boolean' | 'json' | 'array' | 'secret';

export interface CreateSystemSettingDto {
  key: string;
  value: any;
  dataType?: SettingDataType;
  category: string;
  description?: string;
  isPublic?: boolean;
  isEditable?: boolean;
  defaultValue?: any;
  validationRules?: Record<string, any>;
}

export interface UpdateSystemSettingDto {
  value?: any;
  description?: string;
  isPublic?: boolean;
  isEditable?: boolean;
  defaultValue?: any;
  validationRules?: Record<string, any>;
}

export interface SystemSettingFilters {
  category?: string;
  isPublic?: boolean;
  isEditable?: boolean;
  search?: string;
}

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

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

  /**
   * Create a new system setting
   */
  async create(_ctx: ServiceContext, data: CreateSystemSettingDto): Promise<SystemSetting> {
    const existing = await this.findByKey(data.key);
    if (existing) {
      throw new Error(`System setting with key '${data.key}' already exists`);
    }

    this.validateValue(data.value, data.dataType || 'string', data.validationRules);

    const entity = this.repository.create({
      key: data.key,
      value: data.value,
      dataType: data.dataType || 'string',
      category: data.category,
      description: data.description || null,
      isPublic: data.isPublic ?? false,
      isEditable: data.isEditable ?? true,
      defaultValue: data.defaultValue ?? null,
      validationRules: data.validationRules || {},
    });

    return this.repository.save(entity);
  }

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

  /**
   * Find system setting by key
   */
  async findByKey(key: string): Promise<SystemSetting | null> {
    return this.repository.findOne({
      where: { key },
    });
  }

  /**
   * Get value by key
   */
  async getValue(key: string): Promise<any> {
    const setting = await this.findByKey(key);
    if (!setting) {
      return null;
    }
    return setting.value;
  }

  /**
   * Get value with default fallback
   */
  async getValueOrDefault(key: string, defaultValue: any): Promise<any> {
    const value = await this.getValue(key);
    return value !== null ? value : defaultValue;
  }

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

    if (!entity.isEditable) {
      throw new Error('This setting is not editable');
    }

    if (data.value !== undefined) {
      this.validateValue(data.value, entity.dataType, data.validationRules ?? entity.validationRules);
    }

    Object.assign(entity, {
      ...data,
      updatedBy: ctx.userId || null,
    });

    return this.repository.save(entity);
  }

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

    if (!setting.isEditable) {
      throw new Error('This setting is not editable');
    }

    this.validateValue(value, setting.dataType, setting.validationRules);
    setting.value = value;
    setting.updatedBy = ctx.userId || null;

    return this.repository.save(setting);
  }

  /**
   * Reset setting to default value
   */
  async resetToDefault(ctx: ServiceContext, key: string): Promise<SystemSetting | null> {
    const setting = await this.findByKey(key);
    if (!setting || setting.defaultValue === null) {
      return null;
    }

    setting.value = setting.defaultValue;
    setting.updatedBy = ctx.userId || null;

    return this.repository.save(setting);
  }

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

    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.isEditable !== undefined) {
      qb.andWhere('ss.is_editable = :isEditable', { isEditable: filters.isEditable });
    }
    if (filters.search) {
      qb.andWhere(
        '(ss.key ILIKE :search OR ss.description ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

    const skip = (page - 1) * limit;
    qb.orderBy('ss.category', '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),
    };
  }

  /**
   * Find settings by category
   */
  async findByCategory(category: string): Promise<SystemSetting[]> {
    return this.repository.find({
      where: { category },
      order: { key: 'ASC' },
    });
  }

  /**
   * Get all public settings
   */
  async getPublicSettings(): Promise<Record<string, any>> {
    const settings = await this.repository.find({
      where: { isPublic: true },
    });

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

    return result;
  }

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

    for (const { key, value } of settings) {
      try {
        const result = await this.updateValueByKey(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 };
  }

  /**
   * Delete system setting (hard delete)
   */
  async hardDelete(id: string): Promise<boolean> {
    const entity = await this.findById(id);
    if (!entity) {
      return false;
    }

    if (!entity.isEditable) {
      throw new Error('This setting cannot be deleted');
    }

    const result = await this.repository.delete({ id });
    return (result.affected || 0) > 0;
  }

  /**
   * Get all categories
   */
  async getCategories(): Promise<string[]> {
    const result = await this.repository
      .createQueryBuilder('ss')
      .select('DISTINCT ss.category', 'category')
      .orderBy('ss.category', 'ASC')
      .getRawMany();

    return result.map((r) => r.category);
  }

  /**
   * Validate value against type and rules
   */
  private validateValue(
    value: any,
    dataType: SettingDataType,
    validationRules?: Record<string, any> | null
  ): void {
    switch (dataType) {
      case 'number':
        if (typeof value !== 'number' || isNaN(value)) {
          throw new Error('Value must be a valid number');
        }
        break;
      case 'boolean':
        if (typeof value !== 'boolean') {
          throw new Error('Value must be a boolean');
        }
        break;
      case 'json':
        if (typeof value !== 'object' || value === null) {
          throw new Error('Value must be a valid JSON object');
        }
        break;
      case 'array':
        if (!Array.isArray(value)) {
          throw new Error('Value must be an array');
        }
        break;
      case 'secret':
      case 'string':
        if (typeof value !== 'string') {
          throw new Error('Value must be a string');
        }
        break;
    }

    if (validationRules) {
      if (validationRules.min !== undefined && typeof value === 'number' && value < validationRules.min) {
        throw new Error(`Value must be at least ${validationRules.min}`);
      }
      if (validationRules.max !== undefined && typeof value === 'number' && value > validationRules.max) {
        throw new Error(`Value must be at most ${validationRules.max}`);
      }
      if (validationRules.pattern && typeof value === 'string' && !new RegExp(validationRules.pattern).test(value)) {
        throw new Error('Value does not match required pattern');
      }
      if (validationRules.options && !validationRules.options.includes(value)) {
        throw new Error(`Value must be one of: ${validationRules.options.join(', ')}`);
      }
      if (validationRules.minLength !== undefined && typeof value === 'string' && value.length < validationRules.minLength) {
        throw new Error(`Value must be at least ${validationRules.minLength} characters`);
      }
      if (validationRules.maxLength !== undefined && typeof value === 'string' && value.length > validationRules.maxLength) {
        throw new Error(`Value must be at most ${validationRules.maxLength} characters`);
      }
    }
  }
}
