import { CampaignStatus, Sender } from "@prisma/client";
import { AppError } from "@/core/errors/app-error";
import { env } from "@/config/env";
import { SuppressedEmailsRepository } from "@/modules/suppression/repositories/suppressed-emails-repository";
import { buildEmailContent } from "../entities/build-email-content";
import { runInBatches, MAX_CONCURRENT_SENDS } from "../providers/run-in-batches";
import { runSequentialWithRateLimit } from "../providers/rate-limiter";
import { MailProvider } from "../providers/mail-provider";
import { CampaignsRepository } from "../repositories/campaigns-repository";
import { CampaignLogsRepository } from "../repositories/campaign-logs-repository";
import { ContactsRepository } from "@/modules/contacts/repositories/contacts-repository";

interface DispatchCampaignUseCaseRequest {
  id: string;
  userId: string;
}

interface DispatchCampaignUseCaseResponse {
  total: number;
  failed: number;
  status: string;
}

export class DispatchCampaignUseCase {
  constructor(
    private campaignsRepository: CampaignsRepository,
    private contactsRepository: ContactsRepository,
    private campaignLogsRepository: CampaignLogsRepository,
    private suppressedEmailsRepository: SuppressedEmailsRepository,
    private mailProviderFactory: (sender: Sender) => MailProvider
  ) {}

  async execute({ id, userId }: DispatchCampaignUseCaseRequest): Promise<DispatchCampaignUseCaseResponse> {
    const campaign = await this.campaignsRepository.findByIdWithSender(id);

    if (campaign?.createdById !== userId) {
      throw AppError.notFound("Campanha não encontrada");
    }

    if (!campaign.contactListId) {
      throw AppError.badRequest(
        "Selecione uma lista de contatos antes de disparar a campanha."
      );
    }

    await this.campaignsRepository.updateStatus(id, CampaignStatus.SENDING);
    const contacts = await this.contactsRepository.findActiveByListId(campaign.contactListId);
    const mailProvider = this.mailProviderFactory(campaign.sender);

    const worker = async (contact: (typeof contacts)[number]) => {
      const log = await this.campaignLogsRepository.upsertPending(id, contact.id);

      if (await this.suppressedEmailsRepository.isSuppressed(contact.email)) {
        await this.campaignLogsRepository.markFailed(
          log.id,
          "E-mail suprimido (bounce ou reclamação anterior)"
        );
        return false;
      }

      const trackingToken = campaign.sender.provider === "SES" ? undefined : log.id;
      const content = buildEmailContent(campaign.htmlBody, contact.id, trackingToken);
      const result = await mailProvider.send({
        sender: campaign.sender,
        to: contact.email,
        subject: campaign.subject,
        html: content.html,
        text: content.text,
        headers: content.headers,
      });

      if (result.success) {
        await this.campaignLogsRepository.markSuccess(log.id);
        if (result.messageId) {
          await this.campaignLogsRepository.updateMessageId(log.id, result.messageId);
        }
        return true;
      }

      await this.campaignLogsRepository.markFailed(
        log.id,
        result.errorMessage ?? "Erro desconhecido ao enviar e-mail"
      );
      return false;
    };

    const { failureCount } =
      campaign.sender.provider === "SES"
        ? await runSequentialWithRateLimit(contacts, env.sesSendRatePerSecond, worker)
        : await runInBatches(contacts, MAX_CONCURRENT_SENDS, worker);

    const finalStatus =
      failureCount > 0 ? CampaignStatus.COMPLETED_WITH_ERRORS : CampaignStatus.SENT;
    await this.campaignsRepository.updateStatus(id, finalStatus);

    return { total: contacts.length, failed: failureCount, status: finalStatus };
  }
}
