import { describe, it, expect, beforeEach } from "vitest";
import { Contact, ContactStatus, SenderProvider } from "@prisma/client";
import { InMemoryCampaignLogsRepository } from "../repositories/in-memory-campaign-logs-repository";
import { InMemorySuppressedEmailsRepository } from "@/modules/suppression/repositories/in-memory-suppressed-emails-repository";
import { InMemoryMailProvider } from "../providers/in-memory-mail-provider";
import { ResendCampaignToContactUseCase } from "./resend-campaign-to-contact-use-case";
import { AppError } from "@/core/errors/app-error";

let campaignLogsRepository: InMemoryCampaignLogsRepository;
let suppressedEmailsRepository: InMemorySuppressedEmailsRepository;
let mailProvider: InMemoryMailProvider;
let sut: ResendCampaignToContactUseCase;

function makeContact(overrides: Partial<Contact> = {}): Contact {
  return {
    id: "contact-1",
    email: "contact@example.com",
    name: null,
    lastName: null,
    phone: null,
    bairro: null,
    cidade: null,
    uf: null,
    idioma: null,
    empresa: null,
    cep: null,
    codigoEstabelecimento: null,
    nomeEstabelecimento: null,
    cdate: null,
    tags: [],
    source: "MANUAL",
    status: ContactStatus.ACTIVE,
    createdAt: new Date(),
    updatedAt: new Date(),
    ...overrides,
  };
}

function makeCampaign(overrides: { createdById?: string } = {}) {
  return {
    id: "campaign-1",
    title: "Newsletter",
    subject: "Assunto",
    htmlBody: "<p>Corpo</p>",
    status: "SENT" as const,
    senderId: "sender-1",
    contactListId: null,
    createdById: overrides.createdById ?? "user-1",
    createdAt: new Date(),
    updatedAt: new Date(),
    sender: {
      id: "sender-1",
      name: "Remetente",
      fromEmail: "remetente@example.com",
      provider: SenderProvider.SMTP,
      smtpHost: "smtp.example.com",
      smtpPort: 587,
      smtpUser: "remetente@example.com",
      smtpPass: "encrypted-pass",
      domainId: null,
      domain: null,
      userId: overrides.createdById ?? "user-1",
      createdAt: new Date(),
      updatedAt: new Date(),
    },
    contactList: null,
  };
}

async function seedLog(contact: Contact, campaignOverrides: { createdById?: string } = {}) {
  const log = await campaignLogsRepository.upsertPending("campaign-1", contact.id);
  const stored = campaignLogsRepository.items.find((item) => item.id === log.id)!;
  stored.contact = contact;
  stored.campaign = makeCampaign(campaignOverrides);
  return log;
}

describe("Resend Campaign To Contact Use Case", () => {
  beforeEach(() => {
    campaignLogsRepository = new InMemoryCampaignLogsRepository();
    suppressedEmailsRepository = new InMemorySuppressedEmailsRepository();
    mailProvider = new InMemoryMailProvider();
    sut = new ResendCampaignToContactUseCase(
      campaignLogsRepository,
      suppressedEmailsRepository,
      () => mailProvider
    );
  });

  it("resends the campaign to the contact and marks the log as successful", async () => {
    const contact = makeContact();
    const log = await seedLog(contact);

    const result = await sut.execute({ logId: log.id, contactId: contact.id, userId: "user-1" });

    expect(result.success).toBe(true);
    expect(mailProvider.sentEmails).toHaveLength(1);
    expect(mailProvider.sentEmails[0].to).toBe(contact.email);
    const updated = campaignLogsRepository.items.find((item) => item.id === log.id)!;
    expect(updated.status).toBe("SUCCESS");
    expect(updated.retryCount).toBe(1);
  });

  it("rejects resending a campaign that belongs to another user", async () => {
    const contact = makeContact();
    const log = await seedLog(contact, { createdById: "user-2" });

    await expect(
      sut.execute({ logId: log.id, contactId: contact.id, userId: "user-1" })
    ).rejects.toBeInstanceOf(AppError);
  });

  it("rejects resending when the contact's e-mail is suppressed", async () => {
    const contact = makeContact();
    const log = await seedLog(contact);
    await suppressedEmailsRepository.add(contact.email, "BOUNCE", "test");

    await expect(
      sut.execute({ logId: log.id, contactId: contact.id, userId: "user-1" })
    ).rejects.toBeInstanceOf(AppError);
    expect(mailProvider.sentEmails).toHaveLength(0);
  });

  it("rejects resending to an inactive (unsubscribed/bounced) contact", async () => {
    const contact = makeContact({ status: ContactStatus.UNSUBSCRIBED });
    const log = await seedLog(contact);

    await expect(
      sut.execute({ logId: log.id, contactId: contact.id, userId: "user-1" })
    ).rejects.toBeInstanceOf(AppError);
    expect(mailProvider.sentEmails).toHaveLength(0);
  });

  it("marks the log as failed and propagates the error when the mail provider fails", async () => {
    const contact = makeContact();
    const log = await seedLog(contact);
    mailProvider.shouldFail = true;

    await expect(
      sut.execute({ logId: log.id, contactId: contact.id, userId: "user-1" })
    ).rejects.toBeInstanceOf(AppError);

    const updated = campaignLogsRepository.items.find((item) => item.id === log.id)!;
    expect(updated.status).toBe("FAILED");
  });
});
