import { Repository, FindOptionsWhere, ILike } from 'typeorm';
import { UserPreference } from '../entities';
import {
  UpdateUserPreferenceDto,
  BulkUpdateUserPreferencesDto,
  SetUserPreferencesDto,
  UserPreferencesFiltersDto,
} from '../dto';
import { logger } from '../../../shared/utils/logger.js';

/**
 * Default user preferences
 */
const DEFAULT_PREFERENCES: Record<string, any> = {
  'ui.theme': 'system',
  'ui.language': 'es-MX',
  'ui.dateFormat': 'DD/MM/YYYY',
  'ui.numberFormat': 'es-MX',
  'notifications.email': true,
  'notifications.push': true,
  'notifications.inApp': true,
};

/**
 * UserPreferencesService
 *
 * Manages personal preferences for individual users.
 *
 * Features:
 * - CRUD operations for user preferences
 * - Bulk update support
 * - Default values for new users
 * - Sync timestamp tracking
 */
export class UserPreferencesService {
  constructor(private readonly preferenceRepository: Repository<UserPreference>) {}

  /**
   * Get all preferences for a user
   */
  async getAll(
    userId: string,
    filters: UserPreferencesFiltersDto = {}
  ): Promise<UserPreference[]> {
    const { category, search } = filters;

    const where: FindOptionsWhere<UserPreference>[] = [];
    const baseWhere: FindOptionsWhere<UserPreference> = { userId };

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

    const preferences = await this.preferenceRepository.find({
      where,
      order: { key: 'ASC' },
    });

    // If filtering by category, filter in memory (keys start with category.)
    if (category) {
      const prefix = `${category}.`;
      return preferences.filter((p) => p.key.startsWith(prefix));
    }

    return preferences;
  }

  /**
   * Get all preferences as a key-value map
   */
  async getAllAsMap(userId: string): Promise<Record<string, any>> {
    const preferences = await this.preferenceRepository.find({
      where: { userId },
    });

    const map: Record<string, any> = {};
    for (const pref of preferences) {
      map[pref.key] = pref.value;
    }

    // Fill in defaults for missing keys
    for (const [key, defaultValue] of Object.entries(DEFAULT_PREFERENCES)) {
      if (map[key] === undefined) {
        map[key] = defaultValue;
      }
    }

    return map;
  }

  /**
   * Get a single preference by key
   */
  async get(userId: string, key: string): Promise<UserPreference | null> {
    return this.preferenceRepository.findOne({
      where: { userId, key },
    });
  }

  /**
   * Get preference value with default fallback
   */
  async getValue(userId: string, key: string): Promise<any> {
    const preference = await this.get(userId, key);
    if (preference) {
      return preference.value;
    }
    return DEFAULT_PREFERENCES[key] ?? null;
  }

  /**
   * Update or create a single preference
   */
  async update(
    userId: string,
    key: string,
    dto: UpdateUserPreferenceDto
  ): Promise<UserPreference> {
    let preference = await this.preferenceRepository.findOne({
      where: { userId, key },
    });

    if (preference) {
      preference.value = dto.value;
      preference.syncedAt = new Date();
      const updated = await this.preferenceRepository.save(preference);
      logger.debug('User preference updated', { userId, key });
      return updated;
    }

    // Create new preference
    preference = this.preferenceRepository.create({
      userId,
      key,
      value: dto.value,
      syncedAt: new Date(),
    });

    const created = await this.preferenceRepository.save(preference);
    logger.debug('User preference created', { userId, key });
    return created;
  }

  /**
   * Bulk update multiple preferences
   */
  async bulkUpdate(
    userId: string,
    dto: BulkUpdateUserPreferencesDto
  ): Promise<UserPreference[]> {
    const results: UserPreference[] = [];

    for (const { key, value } of dto.preferences) {
      const updated = await this.update(userId, key, { value });
      results.push(updated);
    }

    logger.info('User preferences bulk updated', {
      userId,
      count: dto.preferences.length,
    });

    return results;
  }

  /**
   * Set all standard preferences at once
   */
  async setPreferences(
    userId: string,
    dto: SetUserPreferencesDto
  ): Promise<UserPreference[]> {
    const preferences: Array<{ key: string; value: any }> = [];

    if (dto.theme !== undefined) {
      preferences.push({ key: 'ui.theme', value: dto.theme });
    }
    if (dto.language !== undefined) {
      preferences.push({ key: 'ui.language', value: dto.language });
    }
    if (dto.dateFormat !== undefined) {
      preferences.push({ key: 'ui.dateFormat', value: dto.dateFormat });
    }
    if (dto.numberFormat !== undefined) {
      preferences.push({ key: 'ui.numberFormat', value: dto.numberFormat });
    }
    if (dto.notificationsEmail !== undefined) {
      preferences.push({ key: 'notifications.email', value: dto.notificationsEmail });
    }
    if (dto.notificationsPush !== undefined) {
      preferences.push({ key: 'notifications.push', value: dto.notificationsPush });
    }
    if (dto.notificationsInApp !== undefined) {
      preferences.push({ key: 'notifications.inApp', value: dto.notificationsInApp });
    }

    return this.bulkUpdate(userId, { preferences });
  }

  /**
   * Reset a preference to default value
   */
  async reset(userId: string, key: string): Promise<UserPreference | null> {
    const defaultValue = DEFAULT_PREFERENCES[key];

    if (defaultValue === undefined) {
      // No default, delete the preference
      const result = await this.preferenceRepository.delete({ userId, key });
      if ((result.affected ?? 0) > 0) {
        logger.info('User preference deleted (no default)', { userId, key });
      }
      return null;
    }

    // Reset to default value
    return this.update(userId, key, { value: defaultValue });
  }

  /**
   * Reset all preferences to defaults
   */
  async resetAll(userId: string): Promise<UserPreference[]> {
    // Delete all existing preferences
    await this.preferenceRepository.delete({ userId });

    // Create with defaults
    const results: UserPreference[] = [];
    for (const [key, value] of Object.entries(DEFAULT_PREFERENCES)) {
      const preference = this.preferenceRepository.create({
        userId,
        key,
        value,
        syncedAt: new Date(),
      });
      results.push(await this.preferenceRepository.save(preference));
    }

    logger.info('User preferences reset to defaults', { userId });
    return results;
  }

  /**
   * Delete a specific preference
   */
  async delete(userId: string, key: string): Promise<boolean> {
    const result = await this.preferenceRepository.delete({ userId, key });
    const deleted = (result.affected ?? 0) > 0;

    if (deleted) {
      logger.debug('User preference deleted', { userId, key });
    }

    return deleted;
  }

  /**
   * Initialize default preferences for a new user
   */
  async initializeDefaults(userId: string): Promise<UserPreference[]> {
    const existing = await this.preferenceRepository.find({
      where: { userId },
    });

    if (existing.length > 0) {
      logger.debug('User already has preferences', { userId, count: existing.length });
      return existing;
    }

    const results: UserPreference[] = [];
    for (const [key, value] of Object.entries(DEFAULT_PREFERENCES)) {
      const preference = this.preferenceRepository.create({
        userId,
        key,
        value,
        syncedAt: new Date(),
      });
      results.push(await this.preferenceRepository.save(preference));
    }

    logger.info('User preferences initialized with defaults', { userId });
    return results;
  }

  /**
   * Get preferences grouped by category
   */
  async getGroupedByCategory(userId: string): Promise<Record<string, Record<string, any>>> {
    const preferences = await this.getAllAsMap(userId);
    const grouped: Record<string, Record<string, any>> = {};

    for (const [key, value] of Object.entries(preferences)) {
      const [category, ...rest] = key.split('.');
      const subKey = rest.join('.');

      if (!grouped[category]) {
        grouped[category] = {};
      }
      grouped[category][subKey] = value;
    }

    return grouped;
  }
}
