import { describe, it, expect, beforeEach } from "vitest";
import { InMemoryCampaignsRepository } from "../repositories/in-memory-campaigns-repository";
import { InMemoryCampaignLogsRepository } from "../repositories/in-memory-campaign-logs-repository";
import { InMemoryContactsRepository } from "@/modules/contacts/repositories/in-memory-contacts-repository";
import { InMemoryMailProvider } from "../providers/in-memory-mail-provider";
import { InMemorySuppressedEmailsRepository } from "@/modules/suppression/repositories/in-memory-suppressed-emails-repository";
import { SuppressionReason } from "@prisma/client";
import { DispatchCampaignUseCase } from "./dispatch-campaign-use-case";
import { AppError } from "@/core/errors/app-error";

let campaignsRepository: InMemoryCampaignsRepository;
let contactsRepository: InMemoryContactsRepository;
let campaignLogsRepository: InMemoryCampaignLogsRepository;
let suppressedEmailsRepository: InMemorySuppressedEmailsRepository;
let mailProvider: InMemoryMailProvider;
let sut: DispatchCampaignUseCase;

describe("Dispatch Campaign Use Case", () => {
  beforeEach(() => {
    campaignsRepository = new InMemoryCampaignsRepository();
    contactsRepository = new InMemoryContactsRepository();
    campaignLogsRepository = new InMemoryCampaignLogsRepository();
    suppressedEmailsRepository = new InMemorySuppressedEmailsRepository();
    mailProvider = new InMemoryMailProvider();
    sut = new DispatchCampaignUseCase(
      campaignsRepository,
      contactsRepository,
      campaignLogsRepository,
      suppressedEmailsRepository,
      () => mailProvider
    );
  });

  it("should be able to dispatch a campaign", async () => {
    const created = await campaignsRepository.create({
      title: "Test",
      subject: "Subject",
      htmlBody: "<p>Body</p>",
      contactListId: "list-1",
      createdById: "user-1",
    });

    await contactsRepository.createMany(["contact1@example.com", "contact2@example.com"]);
    contactsRepository.listMemberships.push(
      { contactId: "ccontact1", listId: "list-1", listName: "Lista Teste" },
      { contactId: "ccontact2", listId: "list-1", listName: "Lista Teste" }
    );

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

    expect(result.status).toEqual("SENT");
    expect(result.total).toEqual(2);
    expect(result.failed).toEqual(0);
    expect(mailProvider.sentEmails).toHaveLength(2);
  });

  it("should mark campaign as COMPLETED_WITH_ERRORS when a send fails", async () => {
    const created = await campaignsRepository.create({
      title: "Test",
      subject: "Subject",
      htmlBody: "<p>Body</p>",
      contactListId: "list-1",
      createdById: "user-1",
    });

    await contactsRepository.createMany(["contact1@example.com"]);
    contactsRepository.listMemberships.push({
      contactId: "ccontact1",
      listId: "list-1",
      listName: "Lista Teste",
    });
    mailProvider.shouldFail = true;

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

    expect(result.status).toEqual("COMPLETED_WITH_ERRORS");
    expect(result.failed).toEqual(1);
  });

  it("should skip suppressed contacts without sending", async () => {
    const created = await campaignsRepository.create({
      title: "Test",
      subject: "Subject",
      htmlBody: "<p>Body</p>",
      contactListId: "list-1",
      createdById: "user-1",
    });

    await contactsRepository.createMany(["contact1@example.com", "contact2@example.com"]);
    contactsRepository.listMemberships.push(
      { contactId: "ccontact1", listId: "list-1", listName: "Lista Teste" },
      { contactId: "ccontact2", listId: "list-1", listName: "Lista Teste" }
    );
    await suppressedEmailsRepository.add("contact1@example.com", SuppressionReason.BOUNCE, "test");

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

    expect(result.total).toEqual(2);
    expect(result.failed).toEqual(1);
    expect(mailProvider.sentEmails).toHaveLength(1);
    expect(mailProvider.sentEmails[0].to).toEqual("contact2@example.com");
  });

  it("should only send to contacts belonging to the selected list", async () => {
    const created = await campaignsRepository.create({
      title: "Test",
      subject: "Subject",
      htmlBody: "<p>Body</p>",
      contactListId: "list-1",
      createdById: "user-1",
    });

    await contactsRepository.createMany(["contact1@example.com", "contact2@example.com"]);
    contactsRepository.listMemberships.push({
      contactId: "ccontact1",
      listId: "list-1",
      listName: "Lista Teste",
    });
    contactsRepository.listMemberships.push({
      contactId: "ccontact2",
      listId: "list-2",
      listName: "Outra Lista",
    });

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

    expect(result.total).toEqual(1);
    expect(mailProvider.sentEmails).toHaveLength(1);
    expect(mailProvider.sentEmails[0].to).toEqual("contact1@example.com");
  });

  it("should not allow dispatching a campaign without a selected contact list", async () => {
    const created = await campaignsRepository.create({
      title: "Test",
      subject: "Subject",
      htmlBody: "<p>Body</p>",
      createdById: "user-1",
    });

    await contactsRepository.createMany(["contact1@example.com"]);

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

  it("should not allow dispatching another user's campaign", async () => {
    const created = await campaignsRepository.create({
      title: "Test",
      subject: "Subject",
      htmlBody: "<p>Body</p>",
      contactListId: "list-1",
      createdById: "user-1",
    });

    await expect(sut.execute({ id: created.id, userId: "user-2" })).rejects.toBeInstanceOf(AppError);
  });
});
