/**
 * Channel Service
 * Notification channel management.
 *
 * @module Notifications
 */

import { Repository } from 'typeorm';
import { Channel, ChannelType } from '../entities/channel.entity';

export interface CreateChannelDto {
  code: string;
  name: string;
  description?: string;
  channelType: ChannelType;
  provider?: string;
  providerConfig?: Record<string, any>;
  rateLimitPerMinute?: number;
  rateLimitPerHour?: number;
  rateLimitPerDay?: number;
  isActive?: boolean;
  isDefault?: boolean;
  metadata?: Record<string, any>;
}

export interface UpdateChannelDto {
  name?: string;
  description?: string;
  provider?: string;
  providerConfig?: Record<string, any>;
  rateLimitPerMinute?: number | null;
  rateLimitPerHour?: number | null;
  rateLimitPerDay?: number | null;
  isActive?: boolean;
  isDefault?: boolean;
  metadata?: Record<string, any>;
}

export interface ChannelFilters {
  channelType?: ChannelType;
  isActive?: boolean;
  provider?: string;
  page?: number;
  limit?: number;
}

export interface ChannelStats {
  totalMessages: number;
  successRate: number;
  averageDeliveryTime: number;
}

export class ChannelService {
  constructor(
    private readonly channelRepository: Repository<Channel>,
  ) {}

  /**
   * Create a new channel
   */
  async create(dto: CreateChannelDto): Promise<Channel> {
    // Check for duplicate code
    const existing = await this.findByCode(dto.code);
    if (existing) {
      throw new Error(`Channel with code '${dto.code}' already exists`);
    }

    // If setting as default, clear other defaults for this type
    if (dto.isDefault) {
      await this.clearDefaultForType(dto.channelType);
    }

    const channel = this.channelRepository.create(dto);
    return this.channelRepository.save(channel);
  }

  /**
   * Find channel by ID
   */
  async findById(id: string): Promise<Channel | null> {
    return this.channelRepository.findOne({
      where: { id },
    });
  }

  /**
   * Find channel by code
   */
  async findByCode(code: string): Promise<Channel | null> {
    return this.channelRepository.findOne({
      where: { code },
    });
  }

  /**
   * Find all channels with filters
   */
  async findAll(
    filters: ChannelFilters,
  ): Promise<{ data: Channel[]; total: number; page: number; limit: number }> {
    const page = filters.page || 1;
    const limit = filters.limit || 20;
    const skip = (page - 1) * limit;

    const queryBuilder = this.channelRepository.createQueryBuilder('c');

    if (filters.channelType) {
      queryBuilder.andWhere('c.channel_type = :channelType', { channelType: filters.channelType });
    }

    if (filters.isActive !== undefined) {
      queryBuilder.andWhere('c.is_active = :isActive', { isActive: filters.isActive });
    }

    if (filters.provider) {
      queryBuilder.andWhere('c.provider = :provider', { provider: filters.provider });
    }

    const [data, total] = await queryBuilder
      .orderBy('c.channel_type', 'ASC')
      .addOrderBy('c.is_default', 'DESC')
      .addOrderBy('c.name', 'ASC')
      .skip(skip)
      .take(limit)
      .getManyAndCount();

    return { data, total, page, limit };
  }

  /**
   * Update a channel
   */
  async update(id: string, dto: UpdateChannelDto): Promise<Channel | null> {
    const channel = await this.findById(id);
    if (!channel) {
      return null;
    }

    // If setting as default, clear other defaults for this type
    if (dto.isDefault && !channel.isDefault) {
      await this.clearDefaultForType(channel.channelType);
    }

    Object.assign(channel, dto);
    return this.channelRepository.save(channel);
  }

  /**
   * Delete a channel
   */
  async delete(id: string): Promise<boolean> {
    const channel = await this.findById(id);
    if (!channel) {
      return false;
    }

    // Cannot delete default channel
    if (channel.isDefault) {
      throw new Error('Cannot delete the default channel. Set another channel as default first.');
    }

    const result = await this.channelRepository.delete(id);
    return (result.affected ?? 0) > 0;
  }

  /**
   * Activate/deactivate a channel
   */
  async setActive(id: string, isActive: boolean): Promise<Channel | null> {
    const channel = await this.findById(id);
    if (!channel) {
      return null;
    }

    // Cannot deactivate default channel
    if (!isActive && channel.isDefault) {
      throw new Error('Cannot deactivate the default channel. Set another channel as default first.');
    }

    channel.isActive = isActive;
    return this.channelRepository.save(channel);
  }

  /**
   * Set a channel as default for its type
   */
  async setDefault(id: string): Promise<Channel | null> {
    const channel = await this.findById(id);
    if (!channel) {
      return null;
    }

    if (!channel.isActive) {
      throw new Error('Cannot set an inactive channel as default');
    }

    // Clear other defaults for this type
    await this.clearDefaultForType(channel.channelType);

    channel.isDefault = true;
    return this.channelRepository.save(channel);
  }

  /**
   * Clear default flag for all channels of a type
   */
  private async clearDefaultForType(channelType: ChannelType): Promise<void> {
    await this.channelRepository.update(
      { channelType, isDefault: true },
      { isDefault: false },
    );
  }

  /**
   * Get the default channel for a type
   */
  async getDefault(channelType: ChannelType): Promise<Channel | null> {
    return this.channelRepository.findOne({
      where: { channelType, isDefault: true, isActive: true },
    });
  }

  /**
   * Get all active channels for a type
   */
  async getActiveByType(channelType: ChannelType): Promise<Channel[]> {
    return this.channelRepository.find({
      where: { channelType, isActive: true },
      order: { isDefault: 'DESC', name: 'ASC' },
    });
  }

  /**
   * Update provider configuration
   */
  async updateProviderConfig(
    id: string,
    providerConfig: Record<string, any>,
  ): Promise<Channel | null> {
    const channel = await this.findById(id);
    if (!channel) {
      return null;
    }

    channel.providerConfig = {
      ...channel.providerConfig,
      ...providerConfig,
    };

    return this.channelRepository.save(channel);
  }

  /**
   * Update rate limits
   */
  async updateRateLimits(
    id: string,
    limits: {
      perMinute?: number | null;
      perHour?: number | null;
      perDay?: number | null;
    },
  ): Promise<Channel | null> {
    const channel = await this.findById(id);
    if (!channel) {
      return null;
    }

    if (limits.perMinute !== undefined) {
      channel.rateLimitPerMinute = limits.perMinute as number;
    }
    if (limits.perHour !== undefined) {
      channel.rateLimitPerHour = limits.perHour as number;
    }
    if (limits.perDay !== undefined) {
      channel.rateLimitPerDay = limits.perDay as number;
    }

    return this.channelRepository.save(channel);
  }

  /**
   * Check if rate limit is exceeded for a channel
   */
  async checkRateLimit(
    id: string,
    sentCounts: { perMinute: number; perHour: number; perDay: number },
  ): Promise<{ allowed: boolean; reason?: string }> {
    const channel = await this.findById(id);
    if (!channel) {
      return { allowed: false, reason: 'Channel not found' };
    }

    if (!channel.isActive) {
      return { allowed: false, reason: 'Channel is inactive' };
    }

    if (channel.rateLimitPerMinute && sentCounts.perMinute >= channel.rateLimitPerMinute) {
      return { allowed: false, reason: 'Per-minute rate limit exceeded' };
    }

    if (channel.rateLimitPerHour && sentCounts.perHour >= channel.rateLimitPerHour) {
      return { allowed: false, reason: 'Per-hour rate limit exceeded' };
    }

    if (channel.rateLimitPerDay && sentCounts.perDay >= channel.rateLimitPerDay) {
      return { allowed: false, reason: 'Per-day rate limit exceeded' };
    }

    return { allowed: true };
  }

  /**
   * Get all channel types
   */
  getChannelTypes(): ChannelType[] {
    return ['email', 'sms', 'push', 'whatsapp', 'in_app', 'webhook'];
  }

  /**
   * Get available providers for a channel type
   */
  getProvidersForType(channelType: ChannelType): string[] {
    switch (channelType) {
      case 'email':
        return ['sendgrid', 'ses', 'mailgun', 'smtp'];
      case 'sms':
        return ['twilio', 'nexmo', 'aws_sns'];
      case 'push':
        return ['fcm', 'apns', 'onesignal'];
      case 'whatsapp':
        return ['twilio', 'meta_whatsapp_business'];
      case 'in_app':
        return ['internal'];
      case 'webhook':
        return ['http'];
      default:
        return [];
    }
  }

  /**
   * Validate provider configuration
   */
  validateProviderConfig(
    channelType: ChannelType,
    provider: string,
    config: Record<string, any>,
  ): { valid: boolean; errors: string[] } {
    const errors: string[] = [];

    const requiredFields = this.getRequiredConfigFields(channelType, provider);
    for (const field of requiredFields) {
      if (!config[field]) {
        errors.push(`Missing required field: ${field}`);
      }
    }

    return {
      valid: errors.length === 0,
      errors,
    };
  }

  /**
   * Get required configuration fields for a provider
   */
  private getRequiredConfigFields(channelType: ChannelType, provider: string): string[] {
    const configMap: Record<string, Record<string, string[]>> = {
      email: {
        sendgrid: ['apiKey'],
        ses: ['accessKeyId', 'secretAccessKey', 'region'],
        mailgun: ['apiKey', 'domain'],
        smtp: ['host', 'port', 'username', 'password'],
      },
      sms: {
        twilio: ['accountSid', 'authToken', 'fromNumber'],
        nexmo: ['apiKey', 'apiSecret', 'fromNumber'],
        aws_sns: ['accessKeyId', 'secretAccessKey', 'region'],
      },
      push: {
        fcm: ['serviceAccountKey'],
        apns: ['keyId', 'teamId', 'bundleId', 'privateKey'],
        onesignal: ['appId', 'apiKey'],
      },
      whatsapp: {
        twilio: ['accountSid', 'authToken', 'fromNumber'],
        meta_whatsapp_business: ['accessToken', 'phoneNumberId'],
      },
      in_app: {
        internal: [],
      },
      webhook: {
        http: ['url'],
      },
    };

    return configMap[channelType]?.[provider] || [];
  }

  /**
   * Test channel connectivity
   */
  async testChannel(id: string): Promise<{ success: boolean; message: string }> {
    const channel = await this.findById(id);
    if (!channel) {
      return { success: false, message: 'Channel not found' };
    }

    if (!channel.isActive) {
      return { success: false, message: 'Channel is inactive' };
    }

    // Validate configuration
    if (channel.provider) {
      const validation = this.validateProviderConfig(
        channel.channelType,
        channel.provider,
        channel.providerConfig,
      );

      if (!validation.valid) {
        return {
          success: false,
          message: `Invalid configuration: ${validation.errors.join(', ')}`,
        };
      }
    }

    // In production, this would actually test the provider connectivity
    return { success: true, message: 'Channel configuration is valid' };
  }
}
