/**
 * WhatsApp Broadcast Service
 * Service for managing broadcast campaigns
 *
 * @module WhatsApp
 */

import { Repository, FindOptionsWhere } from 'typeorm';
import { AppDataSource } from '../../../shared/database/typeorm.config';
import { Broadcast, BroadcastStatus, AudienceType, BroadcastRecipient, RecipientStatus } from '../entities';
import { ServiceContext } from './account.service';

export interface CreateBroadcastDto {
  accountId: string;
  name: string;
  description?: string;
  templateId: string;
  audienceType: AudienceType;
  audienceFilter?: Record<string, any>;
  scheduledAt?: Date;
  timezone?: string;
}

export interface UpdateBroadcastDto {
  name?: string;
  description?: string;
  templateId?: string;
  audienceType?: AudienceType;
  audienceFilter?: Record<string, any>;
  scheduledAt?: Date;
  timezone?: string;
}

export interface AddRecipientDto {
  contactId: string;
  templateVariables?: any[];
}

export interface BroadcastFilters {
  accountId?: string;
  status?: BroadcastStatus;
  audienceType?: AudienceType;
}

export class BroadcastService {
  private broadcastRepository: Repository<Broadcast>;
  private recipientRepository: Repository<BroadcastRecipient>;

  constructor() {
    this.broadcastRepository = AppDataSource.getRepository(Broadcast);
    this.recipientRepository = AppDataSource.getRepository(BroadcastRecipient);
  }

  async findAll(
    ctx: ServiceContext,
    filters?: BroadcastFilters,
    pagination?: { page?: number; limit?: number }
  ): Promise<{ data: Broadcast[]; total: number }> {
    const where: FindOptionsWhere<Broadcast> = {
      tenantId: ctx.tenantId,
    };

    if (filters?.accountId) {
      where.accountId = filters.accountId;
    }
    if (filters?.status) {
      where.status = filters.status;
    }
    if (filters?.audienceType) {
      where.audienceType = filters.audienceType;
    }

    const page = pagination?.page || 1;
    const limit = pagination?.limit || 50;
    const skip = (page - 1) * limit;

    const [data, total] = await this.broadcastRepository.findAndCount({
      where,
      relations: ['account', 'template'],
      order: { createdAt: 'DESC' },
      skip,
      take: limit,
    });

    return { data, total };
  }

  async findById(ctx: ServiceContext, id: string): Promise<Broadcast | null> {
    return this.broadcastRepository.findOne({
      where: { id, tenantId: ctx.tenantId },
      relations: ['account', 'template'],
    });
  }

  async findScheduled(ctx: ServiceContext): Promise<Broadcast[]> {
    return this.broadcastRepository.find({
      where: {
        tenantId: ctx.tenantId,
        status: 'scheduled',
      },
      relations: ['account', 'template'],
      order: { scheduledAt: 'ASC' },
    });
  }

  async findDue(): Promise<Broadcast[]> {
    const now = new Date();
    const broadcasts = await this.broadcastRepository.find({
      where: { status: 'scheduled' },
      relations: ['account', 'template'],
    });

    return broadcasts.filter(b => b.scheduledAt && b.scheduledAt <= now);
  }

  async create(ctx: ServiceContext, data: CreateBroadcastDto): Promise<Broadcast> {
    const broadcast = this.broadcastRepository.create({
      ...data,
      tenantId: ctx.tenantId,
      createdBy: ctx.userId,
      status: 'draft',
      timezone: data.timezone || 'America/Mexico_City',
    });

    return this.broadcastRepository.save(broadcast);
  }

  async update(ctx: ServiceContext, id: string, data: UpdateBroadcastDto): Promise<Broadcast | null> {
    const broadcast = await this.findById(ctx, id);
    if (!broadcast) {
      return null;
    }

    if (broadcast.status !== 'draft') {
      throw new Error('Cannot update broadcast that is not in draft status');
    }

    Object.assign(broadcast, data);
    return this.broadcastRepository.save(broadcast);
  }

  async delete(ctx: ServiceContext, id: string): Promise<boolean> {
    const broadcast = await this.findById(ctx, id);
    if (!broadcast) {
      return false;
    }

    if (!['draft', 'cancelled'].includes(broadcast.status)) {
      throw new Error('Cannot delete broadcast that is not in draft or cancelled status');
    }

    const result = await this.broadcastRepository.delete({ id, tenantId: ctx.tenantId });
    return result.affected ? result.affected > 0 : false;
  }

  async addRecipient(
    ctx: ServiceContext,
    broadcastId: string,
    data: AddRecipientDto
  ): Promise<BroadcastRecipient> {
    const recipient = this.recipientRepository.create({
      broadcastId,
      contactId: data.contactId,
      templateVariables: data.templateVariables || [],
      status: 'pending',
    });

    const saved = await this.recipientRepository.save(recipient);

    await this.broadcastRepository.increment(
      { id: broadcastId, tenantId: ctx.tenantId },
      'recipientCount',
      1
    );

    return saved;
  }

  async addRecipients(
    ctx: ServiceContext,
    broadcastId: string,
    recipients: AddRecipientDto[]
  ): Promise<number> {
    const entities = recipients.map(r =>
      this.recipientRepository.create({
        broadcastId,
        contactId: r.contactId,
        templateVariables: r.templateVariables || [],
        status: 'pending',
      })
    );

    await this.recipientRepository.save(entities);

    await this.broadcastRepository.update(
      { id: broadcastId, tenantId: ctx.tenantId },
      { recipientCount: entities.length }
    );

    return entities.length;
  }

  async removeRecipient(ctx: ServiceContext, broadcastId: string, recipientId: string): Promise<boolean> {
    const result = await this.recipientRepository.delete({ id: recipientId, broadcastId });

    if (result.affected && result.affected > 0) {
      await this.broadcastRepository.decrement(
        { id: broadcastId, tenantId: ctx.tenantId },
        'recipientCount',
        1
      );
      return true;
    }

    return false;
  }

  async getRecipients(
    broadcastId: string,
    status?: RecipientStatus,
    pagination?: { page?: number; limit?: number }
  ): Promise<{ data: BroadcastRecipient[]; total: number }> {
    const where: FindOptionsWhere<BroadcastRecipient> = { broadcastId };
    if (status) {
      where.status = status;
    }

    const page = pagination?.page || 1;
    const limit = pagination?.limit || 50;
    const skip = (page - 1) * limit;

    const [data, total] = await this.recipientRepository.findAndCount({
      where,
      relations: ['contact'],
      order: { createdAt: 'ASC' },
      skip,
      take: limit,
    });

    return { data, total };
  }

  async schedule(ctx: ServiceContext, id: string, scheduledAt: Date): Promise<Broadcast | null> {
    const broadcast = await this.findById(ctx, id);
    if (!broadcast) {
      return null;
    }

    if (broadcast.status !== 'draft') {
      throw new Error('Cannot schedule broadcast that is not in draft status');
    }

    if (broadcast.recipientCount === 0) {
      throw new Error('Cannot schedule broadcast with no recipients');
    }

    broadcast.status = 'scheduled';
    broadcast.scheduledAt = scheduledAt;

    return this.broadcastRepository.save(broadcast);
  }

  async start(ctx: ServiceContext, id: string): Promise<Broadcast | null> {
    const broadcast = await this.findById(ctx, id);
    if (!broadcast) {
      return null;
    }

    if (!['draft', 'scheduled'].includes(broadcast.status)) {
      throw new Error('Cannot start broadcast that is not in draft or scheduled status');
    }

    if (broadcast.recipientCount === 0) {
      throw new Error('Cannot start broadcast with no recipients');
    }

    broadcast.status = 'sending';
    broadcast.startedAt = new Date();

    return this.broadcastRepository.save(broadcast);
  }

  async cancel(ctx: ServiceContext, id: string): Promise<Broadcast | null> {
    const broadcast = await this.findById(ctx, id);
    if (!broadcast) {
      return null;
    }

    if (!['draft', 'scheduled', 'sending'].includes(broadcast.status)) {
      throw new Error('Cannot cancel broadcast in current status');
    }

    broadcast.status = 'cancelled';
    return this.broadcastRepository.save(broadcast);
  }

  async complete(ctx: ServiceContext, id: string): Promise<Broadcast | null> {
    const broadcast = await this.findById(ctx, id);
    if (!broadcast) {
      return null;
    }

    broadcast.status = 'completed';
    broadcast.completedAt = new Date();

    return this.broadcastRepository.save(broadcast);
  }

  async updateRecipientStatus(
    recipientId: string,
    status: RecipientStatus,
    messageId?: string,
    error?: { code: string; message: string }
  ): Promise<BroadcastRecipient | null> {
    const recipient = await this.recipientRepository.findOne({
      where: { id: recipientId },
    });

    if (!recipient) {
      return null;
    }

    recipient.status = status;
    if (messageId) {
      recipient.messageId = messageId;
    }
    if (error) {
      recipient.errorCode = error.code;
      recipient.errorMessage = error.message;
    }

    switch (status) {
      case 'sent':
        recipient.sentAt = new Date();
        break;
      case 'delivered':
        recipient.deliveredAt = new Date();
        break;
      case 'read':
        recipient.readAt = new Date();
        break;
    }

    return this.recipientRepository.save(recipient);
  }

  async updateBroadcastCounts(ctx: ServiceContext, id: string): Promise<void> {
    const recipients = await this.recipientRepository.find({
      where: { broadcastId: id },
    });

    const counts = {
      sentCount: recipients.filter(r => ['sent', 'delivered', 'read'].includes(r.status)).length,
      deliveredCount: recipients.filter(r => ['delivered', 'read'].includes(r.status)).length,
      readCount: recipients.filter(r => r.status === 'read').length,
      failedCount: recipients.filter(r => r.status === 'failed').length,
    };

    await this.broadcastRepository.update(
      { id, tenantId: ctx.tenantId },
      counts
    );
  }

  async getStatistics(ctx: ServiceContext, accountId?: string): Promise<{
    total: number;
    draft: number;
    scheduled: number;
    sending: number;
    completed: number;
    cancelled: number;
    failed: number;
    totalRecipients: number;
    totalSent: number;
    totalDelivered: number;
    totalRead: number;
    avgDeliveryRate: number;
    avgReadRate: number;
  }> {
    const where: FindOptionsWhere<Broadcast> = { tenantId: ctx.tenantId };
    if (accountId) {
      where.accountId = accountId;
    }

    const broadcasts = await this.broadcastRepository.find({ where });

    const totalRecipients = broadcasts.reduce((sum, b) => sum + b.recipientCount, 0);
    const totalSent = broadcasts.reduce((sum, b) => sum + b.sentCount, 0);
    const totalDelivered = broadcasts.reduce((sum, b) => sum + b.deliveredCount, 0);
    const totalRead = broadcasts.reduce((sum, b) => sum + b.readCount, 0);

    return {
      total: broadcasts.length,
      draft: broadcasts.filter(b => b.status === 'draft').length,
      scheduled: broadcasts.filter(b => b.status === 'scheduled').length,
      sending: broadcasts.filter(b => b.status === 'sending').length,
      completed: broadcasts.filter(b => b.status === 'completed').length,
      cancelled: broadcasts.filter(b => b.status === 'cancelled').length,
      failed: broadcasts.filter(b => b.status === 'failed').length,
      totalRecipients,
      totalSent,
      totalDelivered,
      totalRead,
      avgDeliveryRate: totalSent > 0 ? (totalDelivered / totalSent) * 100 : 0,
      avgReadRate: totalDelivered > 0 ? (totalRead / totalDelivered) * 100 : 0,
    };
  }

  async duplicate(ctx: ServiceContext, id: string, newName: string): Promise<Broadcast | null> {
    const broadcast = await this.findById(ctx, id);
    if (!broadcast) {
      return null;
    }

    const newBroadcast = this.broadcastRepository.create({
      tenantId: ctx.tenantId,
      accountId: broadcast.accountId,
      name: newName,
      description: broadcast.description,
      templateId: broadcast.templateId,
      audienceType: broadcast.audienceType,
      audienceFilter: broadcast.audienceFilter,
      timezone: broadcast.timezone,
      status: 'draft',
      createdBy: ctx.userId,
    });

    return this.broadcastRepository.save(newBroadcast);
  }

  async estimateCost(ctx: ServiceContext, id: string, costPerMessage: number): Promise<number> {
    const broadcast = await this.findById(ctx, id);
    if (!broadcast) {
      return 0;
    }

    const cost = broadcast.recipientCount * costPerMessage;
    await this.broadcastRepository.update(
      { id, tenantId: ctx.tenantId },
      { estimatedCost: cost }
    );

    return cost;
  }
}
