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";

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

interface RetryFailedCampaignUseCaseResponse {
  retried: number;
  succeeded: number;
  failed: number;
}

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

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

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

    const failedLogs = await this.campaignLogsRepository.findFailedByCampaignId(id);
    const mailProvider = this.mailProviderFactory(campaign.sender);

    const worker = async (log: (typeof failedLogs)[number]) => {
      await this.campaignLogsRepository.incrementRetryCount(log.id);

      if (await this.suppressedEmailsRepository.isSuppressed(log.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, log.contact.id, trackingToken);
      const result = await mailProvider.send({
        sender: campaign.sender,
        to: log.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 { successCount, failureCount } =
      campaign.sender.provider === "SES"
        ? await runSequentialWithRateLimit(failedLogs, env.sesSendRatePerSecond, worker)
        : await runInBatches(failedLogs, MAX_CONCURRENT_SENDS, worker);

    const remainingFailed = await this.campaignLogsRepository.countFailedByCampaignId(id);

    await this.campaignsRepository.updateStatus(
      id,
      remainingFailed > 0 ? CampaignStatus.COMPLETED_WITH_ERRORS : CampaignStatus.SENT
    );

    return { retried: failedLogs.length, succeeded: successCount, failed: failureCount };
  }
}
