/**
 * UserPreferenceService - User Preferences Management
 *
 * Manages personal preferences per user (theme, language, notifications, etc.)
 * These are user-specific and don't follow the setting hierarchy.
 *
 * @module Settings
 */

import { Repository, In } from 'typeorm';
import { UserPreference } from '../entities';

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

export interface CreateUserPreferenceDto {
  userId: string;
  key: string;
  value: any;
}

export interface UpdateUserPreferenceDto {
  value: any;
}

export interface UserPreferenceFilters {
  userId?: string;
  search?: string;
}

export interface DefaultPreferences {
  theme: 'light' | 'dark' | 'system';
  language: string;
  timezone: string;
  dateFormat: string;
  timeFormat: '12h' | '24h';
  pageSize: number;
  notifications: {
    email: boolean;
    push: boolean;
    inApp: boolean;
  };
}

export const DEFAULT_PREFERENCES: DefaultPreferences = {
  theme: 'system',
  language: 'es',
  timezone: 'America/Mexico_City',
  dateFormat: 'DD/MM/YYYY',
  timeFormat: '24h',
  pageSize: 25,
  notifications: {
    email: true,
    push: true,
    inApp: true,
  },
};

export class UserPreferenceService {
  private repository: Repository<UserPreference>;

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

  /**
   * Create a new user preference
   */
  async create(data: CreateUserPreferenceDto): Promise<UserPreference> {
    const existing = await this.findByUserAndKey(data.userId, data.key);
    if (existing) {
      throw new Error(`User preference with key '${data.key}' already exists for this user`);
    }

    const entity = this.repository.create({
      userId: data.userId,
      key: data.key,
      value: data.value,
      syncedAt: new Date(),
    });

    return this.repository.save(entity);
  }

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

  /**
   * Find user preference by user ID and key
   */
  async findByUserAndKey(userId: string, key: string): Promise<UserPreference | null> {
    return this.repository.findOne({
      where: { userId, key },
    });
  }

  /**
   * Get value for a user preference
   */
  async getValue(userId: string, key: string): Promise<any> {
    const preference = await this.findByUserAndKey(userId, key);
    return preference?.value ?? null;
  }

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

  /**
   * Get all preferences for a user
   */
  async findByUser(userId: string): Promise<UserPreference[]> {
    return this.repository.find({
      where: { userId },
      order: { key: 'ASC' },
    });
  }

  /**
   * Get all preferences for a user as key-value map
   */
  async getUserPreferencesMap(userId: string): Promise<Record<string, any>> {
    const preferences = await this.findByUser(userId);
    const result: Record<string, any> = {};
    for (const pref of preferences) {
      result[pref.key] = pref.value;
    }
    return result;
  }

  /**
   * Get all preferences for a user with defaults filled in
   */
  async getUserPreferencesWithDefaults(userId: string): Promise<DefaultPreferences & Record<string, any>> {
    const userPrefs = await this.getUserPreferencesMap(userId);
    return {
      ...DEFAULT_PREFERENCES,
      ...userPrefs,
    };
  }

  /**
   * Update user preference
   */
  async update(id: string, data: UpdateUserPreferenceDto): Promise<UserPreference | null> {
    const entity = await this.findById(id);
    if (!entity) {
      return null;
    }

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

  /**
   * Update or create user preference by user and key (upsert)
   */
  async upsert(userId: string, key: string, value: any): Promise<UserPreference> {
    const existing = await this.findByUserAndKey(userId, key);

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

    return this.create({ userId, key, value });
  }

  /**
   * Find preferences with filters
   */
  async findWithFilters(
    filters: UserPreferenceFilters,
    page = 1,
    limit = 50
  ): Promise<PaginatedResult<UserPreference>> {
    const qb = this.repository.createQueryBuilder('up');

    if (filters.userId) {
      qb.andWhere('up.user_id = :userId', { userId: filters.userId });
    }
    if (filters.search) {
      qb.andWhere('up.key ILIKE :search', { search: `%${filters.search}%` });
    }

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

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

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

  /**
   * Bulk update user preferences
   */
  async updateMultiple(
    userId: string,
    preferences: { 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 preferences) {
      try {
        const existing = await this.findByUserAndKey(userId, key);
        if (existing) {
          existing.value = value;
          existing.syncedAt = new Date();
          await this.repository.save(existing);
          updated++;
        } else {
          await this.create({ userId, key, value });
          created++;
        }
      } catch (error) {
        errors.push(`${key}: ${error instanceof Error ? error.message : 'Unknown error'}`);
      }
    }

    return { updated, created, errors };
  }

  /**
   * Reset preference to default
   */
  async resetToDefault(userId: string, key: string): Promise<UserPreference | null> {
    const defaultValue = (DEFAULT_PREFERENCES as Record<string, any>)[key];
    if (defaultValue === undefined) {
      return null;
    }

    return this.upsert(userId, key, defaultValue);
  }

  /**
   * Reset all preferences to defaults
   */
  async resetAllToDefaults(userId: string): Promise<number> {
    // Delete all existing preferences
    await this.repository.delete({ userId });

    // Create default preferences
    let count = 0;
    for (const [key, value] of Object.entries(DEFAULT_PREFERENCES)) {
      await this.create({ userId, key, value });
      count++;
    }

    return count;
  }

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

  /**
   * Delete all preferences for a user
   */
  async deleteByUser(userId: string): Promise<number> {
    const result = await this.repository.delete({ userId });
    return result.affected || 0;
  }

  /**
   * Copy preferences from one user to another
   */
  async copyFromUser(
    sourceUserId: string,
    targetUserId: string,
    overwrite = false
  ): Promise<{ copied: number; skipped: number }> {
    const sourcePreferences = await this.findByUser(sourceUserId);
    let copied = 0;
    let skipped = 0;

    for (const pref of sourcePreferences) {
      const existing = await this.findByUserAndKey(targetUserId, pref.key);

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

      if (existing) {
        existing.value = pref.value;
        existing.syncedAt = new Date();
        await this.repository.save(existing);
      } else {
        await this.create({
          userId: targetUserId,
          key: pref.key,
          value: pref.value,
        });
      }
      copied++;
    }

    return { copied, skipped };
  }

  /**
   * Get preferences for multiple users
   */
  async getPreferencesForUsers(userIds: string[], key: string): Promise<Map<string, any>> {
    const preferences = await this.repository.find({
      where: {
        userId: In(userIds),
        key,
      },
    });

    const result = new Map<string, any>();
    for (const pref of preferences) {
      result.set(pref.userId, pref.value);
    }

    return result;
  }

  /**
   * Get specific preference keys for a user
   */
  async getPreferencesByKeys(userId: string, keys: string[]): Promise<Record<string, any>> {
    const preferences = await this.repository.find({
      where: {
        userId,
        key: In(keys),
      },
    });

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

    return result;
  }

  /**
   * Sync preferences (update syncedAt timestamp)
   */
  async syncPreferences(userId: string): Promise<void> {
    await this.repository.update(
      { userId },
      { syncedAt: new Date() }
    );
  }

  /**
   * Get preferences modified since a specific date
   */
  async getModifiedSince(userId: string, since: Date): Promise<UserPreference[]> {
    return this.repository
      .createQueryBuilder('up')
      .where('up.user_id = :userId', { userId })
      .andWhere('up.updated_at > :since', { since })
      .orderBy('up.updated_at', 'DESC')
      .getMany();
  }
}
