/**
 * Preference Service
 * User notification preferences management.
 *
 * @module Notifications
 */

import { Repository } from 'typeorm';
import { NotificationPreference, DigestFrequency } from '../entities/preference.entity';
import { ServiceContext } from './notification.service';

export interface CreatePreferenceDto {
  userId: string;
  globalEnabled?: boolean;
  quietHoursStart?: string;
  quietHoursEnd?: string;
  timezone?: string;
  emailEnabled?: boolean;
  smsEnabled?: boolean;
  pushEnabled?: boolean;
  whatsappEnabled?: boolean;
  inAppEnabled?: boolean;
  categoryPreferences?: Record<string, any>;
  digestFrequency?: DigestFrequency;
  digestDay?: number;
  digestHour?: number;
  metadata?: Record<string, any>;
}

export interface UpdatePreferenceDto {
  globalEnabled?: boolean;
  quietHoursStart?: string;
  quietHoursEnd?: string;
  timezone?: string;
  emailEnabled?: boolean;
  smsEnabled?: boolean;
  pushEnabled?: boolean;
  whatsappEnabled?: boolean;
  inAppEnabled?: boolean;
  categoryPreferences?: Record<string, any>;
  digestFrequency?: DigestFrequency;
  digestDay?: number;
  digestHour?: number;
  metadata?: Record<string, any>;
}

export interface ChannelPreferences {
  email: boolean;
  sms: boolean;
  push: boolean;
  whatsapp: boolean;
  inApp: boolean;
}

export class PreferenceService {
  constructor(
    private readonly preferenceRepository: Repository<NotificationPreference>,
  ) {}

  /**
   * Create user preferences
   */
  async create(
    ctx: ServiceContext,
    dto: CreatePreferenceDto,
  ): Promise<NotificationPreference> {
    // Check if preferences already exist
    const existing = await this.findByUserId(ctx, dto.userId);
    if (existing) {
      throw new Error('Preferences already exist for this user');
    }

    const preference = this.preferenceRepository.create({
      tenantId: ctx.tenantId,
      ...dto,
    });

    return this.preferenceRepository.save(preference);
  }

  /**
   * Get preferences by user ID
   */
  async findByUserId(
    ctx: ServiceContext,
    userId: string,
  ): Promise<NotificationPreference | null> {
    return this.preferenceRepository.findOne({
      where: { userId, tenantId: ctx.tenantId },
    });
  }

  /**
   * Get or create preferences for a user
   */
  async getOrCreate(
    ctx: ServiceContext,
    userId: string,
  ): Promise<NotificationPreference> {
    let preference = await this.findByUserId(ctx, userId);

    if (!preference) {
      preference = await this.create(ctx, { userId });
    }

    return preference;
  }

  /**
   * Update user preferences
   */
  async update(
    ctx: ServiceContext,
    userId: string,
    dto: UpdatePreferenceDto,
  ): Promise<NotificationPreference | null> {
    const preference = await this.findByUserId(ctx, userId);
    if (!preference) {
      return null;
    }

    Object.assign(preference, dto);
    return this.preferenceRepository.save(preference);
  }

  /**
   * Update or create preferences
   */
  async upsert(
    ctx: ServiceContext,
    userId: string,
    dto: UpdatePreferenceDto,
  ): Promise<NotificationPreference> {
    let preference = await this.findByUserId(ctx, userId);

    if (preference) {
      Object.assign(preference, dto);
    } else {
      preference = this.preferenceRepository.create({
        tenantId: ctx.tenantId,
        userId,
        ...dto,
      });
    }

    return this.preferenceRepository.save(preference);
  }

  /**
   * Delete user preferences
   */
  async delete(ctx: ServiceContext, userId: string): Promise<boolean> {
    const result = await this.preferenceRepository.delete({
      userId,
      tenantId: ctx.tenantId,
    });
    return (result.affected ?? 0) > 0;
  }

  /**
   * Enable all notifications for a user
   */
  async enableAll(ctx: ServiceContext, userId: string): Promise<NotificationPreference> {
    return this.upsert(ctx, userId, {
      globalEnabled: true,
      emailEnabled: true,
      smsEnabled: true,
      pushEnabled: true,
      whatsappEnabled: true,
      inAppEnabled: true,
    });
  }

  /**
   * Disable all notifications for a user
   */
  async disableAll(ctx: ServiceContext, userId: string): Promise<NotificationPreference> {
    return this.upsert(ctx, userId, {
      globalEnabled: false,
    });
  }

  /**
   * Update channel preferences
   */
  async updateChannelPreferences(
    ctx: ServiceContext,
    userId: string,
    channels: Partial<ChannelPreferences>,
  ): Promise<NotificationPreference> {
    const dto: UpdatePreferenceDto = {};

    if (channels.email !== undefined) dto.emailEnabled = channels.email;
    if (channels.sms !== undefined) dto.smsEnabled = channels.sms;
    if (channels.push !== undefined) dto.pushEnabled = channels.push;
    if (channels.whatsapp !== undefined) dto.whatsappEnabled = channels.whatsapp;
    if (channels.inApp !== undefined) dto.inAppEnabled = channels.inApp;

    return this.upsert(ctx, userId, dto);
  }

  /**
   * Get channel preferences
   */
  async getChannelPreferences(
    ctx: ServiceContext,
    userId: string,
  ): Promise<ChannelPreferences> {
    const preference = await this.getOrCreate(ctx, userId);

    return {
      email: preference.emailEnabled,
      sms: preference.smsEnabled,
      push: preference.pushEnabled,
      whatsapp: preference.whatsappEnabled,
      inApp: preference.inAppEnabled,
    };
  }

  /**
   * Set quiet hours
   */
  async setQuietHours(
    ctx: ServiceContext,
    userId: string,
    start: string | null,
    end: string | null,
  ): Promise<NotificationPreference> {
    return this.upsert(ctx, userId, {
      quietHoursStart: start || undefined,
      quietHoursEnd: end || undefined,
    });
  }

  /**
   * Clear quiet hours
   */
  async clearQuietHours(ctx: ServiceContext, userId: string): Promise<NotificationPreference> {
    return this.setQuietHours(ctx, userId, null, null);
  }

  /**
   * Set digest frequency
   */
  async setDigestFrequency(
    ctx: ServiceContext,
    userId: string,
    frequency: DigestFrequency,
    options?: { day?: number; hour?: number },
  ): Promise<NotificationPreference> {
    return this.upsert(ctx, userId, {
      digestFrequency: frequency,
      digestDay: options?.day,
      digestHour: options?.hour,
    });
  }

  /**
   * Update category preferences
   */
  async updateCategoryPreferences(
    ctx: ServiceContext,
    userId: string,
    categoryPreferences: Record<string, any>,
  ): Promise<NotificationPreference> {
    const preference = await this.getOrCreate(ctx, userId);

    preference.categoryPreferences = {
      ...preference.categoryPreferences,
      ...categoryPreferences,
    };

    return this.preferenceRepository.save(preference);
  }

  /**
   * Get category preference
   */
  async getCategoryPreference(
    ctx: ServiceContext,
    userId: string,
    category: string,
  ): Promise<Record<string, any> | null> {
    const preference = await this.findByUserId(ctx, userId);
    if (!preference) {
      return null;
    }

    return preference.categoryPreferences[category] || null;
  }

  /**
   * Check if a specific notification type is enabled for a user
   */
  async isNotificationEnabled(
    ctx: ServiceContext,
    userId: string,
    channelType: string,
    category?: string,
  ): Promise<boolean> {
    const preference = await this.findByUserId(ctx, userId);

    // Default to enabled if no preferences exist
    if (!preference) {
      return true;
    }

    // Check global enabled flag
    if (!preference.globalEnabled) {
      return false;
    }

    // Check channel-specific flag
    switch (channelType) {
      case 'email':
        if (!preference.emailEnabled) return false;
        break;
      case 'sms':
        if (!preference.smsEnabled) return false;
        break;
      case 'push':
        if (!preference.pushEnabled) return false;
        break;
      case 'whatsapp':
        if (!preference.whatsappEnabled) return false;
        break;
      case 'in_app':
        if (!preference.inAppEnabled) return false;
        break;
    }

    // Check category-specific preference if provided
    if (category && preference.categoryPreferences[category] !== undefined) {
      return preference.categoryPreferences[category].enabled !== false;
    }

    return true;
  }

  /**
   * Get users who prefer a specific digest frequency
   */
  async getUsersByDigestFrequency(
    ctx: ServiceContext,
    frequency: DigestFrequency,
    options?: { day?: number; hour?: number },
  ): Promise<NotificationPreference[]> {
    const queryBuilder = this.preferenceRepository
      .createQueryBuilder('p')
      .where('p.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('p.digest_frequency = :frequency', { frequency })
      .andWhere('p.global_enabled = true');

    if (options?.day !== undefined) {
      queryBuilder.andWhere('p.digest_day = :day', { day: options.day });
    }

    if (options?.hour !== undefined) {
      queryBuilder.andWhere('p.digest_hour = :hour', { hour: options.hour });
    }

    return queryBuilder.getMany();
  }

  /**
   * Set timezone for a user
   */
  async setTimezone(
    ctx: ServiceContext,
    userId: string,
    timezone: string,
  ): Promise<NotificationPreference> {
    return this.upsert(ctx, userId, { timezone });
  }

  /**
   * Get all preferences for a tenant (admin use)
   */
  async findAllByTenant(
    ctx: ServiceContext,
    options?: { page?: number; limit?: number },
  ): Promise<{ data: NotificationPreference[]; total: number }> {
    const page = options?.page || 1;
    const limit = options?.limit || 50;
    const skip = (page - 1) * limit;

    const [data, total] = await this.preferenceRepository.findAndCount({
      where: { tenantId: ctx.tenantId },
      skip,
      take: limit,
      order: { createdAt: 'DESC' },
    });

    return { data, total };
  }
}
