/**
 * PreferencesService - Gestión de Preferencias de Usuario
 *
 * Almacena y recupera preferencias personalizadas por usuario.
 * Categorías: UI, notificaciones, privacidad, accesibilidad.
 *
 * @module Profiles
 */

import { DataSource } from 'typeorm';

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

/**
 * User preferences structure
 */
export interface UserPreferences {
  // UI Preferences
  ui: {
    theme: 'light' | 'dark' | 'system';
    sidebarCollapsed: boolean;
    compactMode: boolean;
    fontSize: 'small' | 'medium' | 'large';
    language: string;
    timezone: string;
    dateFormat: string;
    timeFormat: '12h' | '24h';
    numberFormat: {
      decimal: string;
      thousands: string;
      currency: string;
    };
  };

  // Notification Preferences
  notifications: {
    email: {
      enabled: boolean;
      digest: 'realtime' | 'daily' | 'weekly' | 'never';
      types: string[];
    };
    push: {
      enabled: boolean;
      types: string[];
    };
    inApp: {
      enabled: boolean;
      sound: boolean;
      desktop: boolean;
    };
  };

  // Privacy Preferences
  privacy: {
    showEmail: boolean;
    showPhone: boolean;
    showOnlineStatus: boolean;
    allowDirectMessages: boolean;
    profileVisibility: 'public' | 'team' | 'private';
  };

  // Accessibility Preferences
  accessibility: {
    reduceMotion: boolean;
    highContrast: boolean;
    screenReaderOptimized: boolean;
    keyboardNavigation: boolean;
  };

  // Dashboard Preferences
  dashboard: {
    defaultView: string;
    widgets: string[];
    refreshInterval: number;
  };

  // Custom/Extension Preferences
  custom: Record<string, any>;
}

const DEFAULT_PREFERENCES: UserPreferences = {
  ui: {
    theme: 'system',
    sidebarCollapsed: false,
    compactMode: false,
    fontSize: 'medium',
    language: 'es-MX',
    timezone: 'America/Mexico_City',
    dateFormat: 'DD/MM/YYYY',
    timeFormat: '24h',
    numberFormat: {
      decimal: '.',
      thousands: ',',
      currency: 'MXN',
    },
  },
  notifications: {
    email: {
      enabled: true,
      digest: 'daily',
      types: ['mentions', 'assignments', 'updates'],
    },
    push: {
      enabled: true,
      types: ['mentions', 'assignments'],
    },
    inApp: {
      enabled: true,
      sound: false,
      desktop: true,
    },
  },
  privacy: {
    showEmail: true,
    showPhone: false,
    showOnlineStatus: true,
    allowDirectMessages: true,
    profileVisibility: 'team',
  },
  accessibility: {
    reduceMotion: false,
    highContrast: false,
    screenReaderOptimized: false,
    keyboardNavigation: true,
  },
  dashboard: {
    defaultView: 'overview',
    widgets: ['tasks', 'notifications', 'calendar'],
    refreshInterval: 60,
  },
  custom: {},
};

// In-memory cache for preferences (in production, use Redis)
const preferencesCache = new Map<string, { data: UserPreferences; cachedAt: Date }>();
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes

export class PreferencesService {
  private dataSource: DataSource;

  constructor(dataSource: DataSource) {
    this.dataSource = dataSource;
  }

  /**
   * Get all preferences for a user
   */
  async getPreferences(ctx: ServiceContext, userId: string): Promise<UserPreferences> {
    // Check cache first
    const cacheKey = this.getCacheKey(ctx.tenantId, userId);
    const cached = preferencesCache.get(cacheKey);

    if (cached && Date.now() - cached.cachedAt.getTime() < CACHE_TTL_MS) {
      return cached.data;
    }

    // Load from database (stored in user or separate table)
    // For now, return defaults merged with any stored preferences
    const stored = await this.loadFromDatabase(ctx, userId);
    const preferences = this.mergePreferences(DEFAULT_PREFERENCES, stored);

    // Update cache
    preferencesCache.set(cacheKey, { data: preferences, cachedAt: new Date() });

    return preferences;
  }

  /**
   * Update user preferences (partial update)
   */
  async updatePreferences(
    ctx: ServiceContext,
    userId: string,
    updates: DeepPartial<UserPreferences>
  ): Promise<UserPreferences> {
    const current = await this.getPreferences(ctx, userId);
    const updated = this.mergePreferences(current, updates as any);

    // Save to database
    await this.saveToDatabase(ctx, userId, updated);

    // Update cache
    const cacheKey = this.getCacheKey(ctx.tenantId, userId);
    preferencesCache.set(cacheKey, { data: updated, cachedAt: new Date() });

    return updated;
  }

  /**
   * Reset preferences to defaults
   */
  async resetPreferences(ctx: ServiceContext, userId: string): Promise<UserPreferences> {
    await this.saveToDatabase(ctx, userId, DEFAULT_PREFERENCES);

    // Clear cache
    const cacheKey = this.getCacheKey(ctx.tenantId, userId);
    preferencesCache.delete(cacheKey);

    return DEFAULT_PREFERENCES;
  }

  /**
   * Get a specific preference value
   */
  async getPreference<T>(ctx: ServiceContext, userId: string, path: string): Promise<T | undefined> {
    const preferences = await this.getPreferences(ctx, userId);
    return this.getNestedValue(preferences, path) as T | undefined;
  }

  /**
   * Set a specific preference value
   */
  async setPreference(
    ctx: ServiceContext,
    userId: string,
    path: string,
    value: any
  ): Promise<UserPreferences> {
    const current = await this.getPreferences(ctx, userId);
    const updated = this.setNestedValue({ ...current }, path, value);

    await this.saveToDatabase(ctx, userId, updated);

    // Update cache
    const cacheKey = this.getCacheKey(ctx.tenantId, userId);
    preferencesCache.set(cacheKey, { data: updated, cachedAt: new Date() });

    return updated;
  }

  /**
   * Get UI preferences
   */
  async getUIPreferences(ctx: ServiceContext, userId: string): Promise<UserPreferences['ui']> {
    const prefs = await this.getPreferences(ctx, userId);
    return prefs.ui;
  }

  /**
   * Update UI preferences
   */
  async updateUIPreferences(
    ctx: ServiceContext,
    userId: string,
    updates: Partial<UserPreferences['ui']>
  ): Promise<UserPreferences['ui']> {
    const result = await this.updatePreferences(ctx, userId, { ui: updates as any });
    return result.ui;
  }

  /**
   * Get notification preferences
   */
  async getNotificationPreferences(
    ctx: ServiceContext,
    userId: string
  ): Promise<UserPreferences['notifications']> {
    const prefs = await this.getPreferences(ctx, userId);
    return prefs.notifications;
  }

  /**
   * Update notification preferences
   */
  async updateNotificationPreferences(
    ctx: ServiceContext,
    userId: string,
    updates: DeepPartial<UserPreferences['notifications']>
  ): Promise<UserPreferences['notifications']> {
    const result = await this.updatePreferences(ctx, userId, { notifications: updates as any });
    return result.notifications;
  }

  /**
   * Get privacy preferences
   */
  async getPrivacyPreferences(ctx: ServiceContext, userId: string): Promise<UserPreferences['privacy']> {
    const prefs = await this.getPreferences(ctx, userId);
    return prefs.privacy;
  }

  /**
   * Update privacy preferences
   */
  async updatePrivacyPreferences(
    ctx: ServiceContext,
    userId: string,
    updates: Partial<UserPreferences['privacy']>
  ): Promise<UserPreferences['privacy']> {
    const result = await this.updatePreferences(ctx, userId, { privacy: updates as any });
    return result.privacy;
  }

  /**
   * Get accessibility preferences
   */
  async getAccessibilityPreferences(
    ctx: ServiceContext,
    userId: string
  ): Promise<UserPreferences['accessibility']> {
    const prefs = await this.getPreferences(ctx, userId);
    return prefs.accessibility;
  }

  /**
   * Update accessibility preferences
   */
  async updateAccessibilityPreferences(
    ctx: ServiceContext,
    userId: string,
    updates: Partial<UserPreferences['accessibility']>
  ): Promise<UserPreferences['accessibility']> {
    const result = await this.updatePreferences(ctx, userId, { accessibility: updates as any });
    return result.accessibility;
  }

  /**
   * Set custom preference
   */
  async setCustomPreference(
    ctx: ServiceContext,
    userId: string,
    key: string,
    value: any
  ): Promise<void> {
    const current = await this.getPreferences(ctx, userId);
    current.custom[key] = value;
    await this.updatePreferences(ctx, userId, { custom: current.custom });
  }

  /**
   * Get custom preference
   */
  async getCustomPreference<T>(ctx: ServiceContext, userId: string, key: string): Promise<T | undefined> {
    const prefs = await this.getPreferences(ctx, userId);
    return prefs.custom[key] as T | undefined;
  }

  /**
   * Clear preference cache for user
   */
  clearCache(tenantId: string, userId: string): void {
    const cacheKey = this.getCacheKey(tenantId, userId);
    preferencesCache.delete(cacheKey);
  }

  /**
   * Clear all preference caches
   */
  clearAllCaches(): void {
    preferencesCache.clear();
  }

  // ============ Private Helper Methods ============

  private getCacheKey(tenantId: string, userId: string): string {
    return `${tenantId}:${userId}`;
  }

  private async loadFromDatabase(ctx: ServiceContext, userId: string): Promise<Partial<UserPreferences>> {
    // In a real implementation, load from a preferences table or user record
    // For now, return empty object (will merge with defaults)
    try {
      const result = await this.dataSource.query(
        `SELECT preferences FROM auth.user_preferences WHERE user_id = $1 AND tenant_id = $2`,
        [userId, ctx.tenantId]
      );
      return result[0]?.preferences || {};
    } catch {
      // Table might not exist, return empty
      return {};
    }
  }

  private async saveToDatabase(ctx: ServiceContext, userId: string, preferences: UserPreferences): Promise<void> {
    // In a real implementation, save to a preferences table
    try {
      await this.dataSource.query(
        `INSERT INTO auth.user_preferences (user_id, tenant_id, preferences, updated_at)
         VALUES ($1, $2, $3, NOW())
         ON CONFLICT (user_id, tenant_id) DO UPDATE SET preferences = $3, updated_at = NOW()`,
        [userId, ctx.tenantId, JSON.stringify(preferences)]
      );
    } catch {
      // Table might not exist, silently fail (preferences will be in-memory only)
    }
  }

  private mergePreferences(base: UserPreferences, updates: Partial<UserPreferences>): UserPreferences {
    return {
      ui: { ...base.ui, ...(updates.ui || {}) },
      notifications: this.mergeNotificationPrefs(base.notifications, updates.notifications),
      privacy: { ...base.privacy, ...(updates.privacy || {}) },
      accessibility: { ...base.accessibility, ...(updates.accessibility || {}) },
      dashboard: { ...base.dashboard, ...(updates.dashboard || {}) },
      custom: { ...base.custom, ...(updates.custom || {}) },
    };
  }

  private mergeNotificationPrefs(
    base: UserPreferences['notifications'],
    updates?: Partial<UserPreferences['notifications']>
  ): UserPreferences['notifications'] {
    if (!updates) return base;
    return {
      email: { ...base.email, ...(updates.email || {}) },
      push: { ...base.push, ...(updates.push || {}) },
      inApp: { ...base.inApp, ...(updates.inApp || {}) },
    };
  }

  private getNestedValue(obj: any, path: string): any {
    return path.split('.').reduce((curr, key) => curr?.[key], obj);
  }

  private setNestedValue(obj: any, path: string, value: any): any {
    const keys = path.split('.');
    let curr = obj;
    for (let i = 0; i < keys.length - 1; i++) {
      if (!(keys[i] in curr)) {
        curr[keys[i]] = {};
      }
      curr = curr[keys[i]];
    }
    curr[keys[keys.length - 1]] = value;
    return obj;
  }
}

/**
 * Deep partial type for nested objects
 */
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
