import { describe, it, expect, beforeEach } from "vitest";
import { ContactStatus } from "@prisma/client";
import { InMemoryContactImportJobsRepository } from "../repositories/in-memory-contact-import-jobs-repository";
import { InMemoryContactImportRowsRepository } from "../repositories/in-memory-contact-import-rows-repository";
import { InMemoryContactsRepository } from "../repositories/in-memory-contacts-repository";
import { InMemoryContactListsRepository } from "../repositories/in-memory-contact-lists-repository";
import { ProcessImportBatchUseCase } from "./process-import-batch-use-case";
import { AppError } from "@/core/errors/app-error";

let jobsRepository: InMemoryContactImportJobsRepository;
let rowsRepository: InMemoryContactImportRowsRepository;
let contactsRepository: InMemoryContactsRepository;
let contactListsRepository: InMemoryContactListsRepository;
let sut: ProcessImportBatchUseCase;

async function createConfiguredJob(rows: Record<string, string>[], overrides: Partial<{ duplicateStrategy: "IGNORE" | "UPDATE" | "CREATE_ANYWAY"; targetListId: string }> = {}) {
  const job = await jobsRepository.create({ fileName: "contatos.csv", totalRows: rows.length, createdById: "user-1" });
  await rowsRepository.createMany(
    job.id,
    rows.map((rawData, index) => ({ rowIndex: index, rawData }))
  );
  await jobsRepository.updateMapping(job.id, {
    columnMapping: { email: "E-mail", name: "Nome" },
    duplicateStrategy: overrides.duplicateStrategy ?? "IGNORE",
    targetListId: overrides.targetListId,
  });
  return job;
}

describe("Process Import Batch Use Case", () => {
  beforeEach(() => {
    jobsRepository = new InMemoryContactImportJobsRepository();
    rowsRepository = new InMemoryContactImportRowsRepository();
    contactsRepository = new InMemoryContactsRepository();
    contactListsRepository = new InMemoryContactListsRepository();
    sut = new ProcessImportBatchUseCase(jobsRepository, rowsRepository, contactsRepository, contactListsRepository);
  });

  it("throws AppError.notFound for an unknown job", async () => {
    await expect(sut.execute({ jobId: "does-not-exist" })).rejects.toBeInstanceOf(AppError);
  });

  it("creates new contacts for valid, non-duplicate rows and marks the job completed", async () => {
    const job = await createConfiguredJob([
      { "E-mail": "maria@example.com", Nome: "Maria" },
      { "E-mail": "joao@example.com", Nome: "João" },
    ]);

    const result = await sut.execute({ jobId: job.id });

    expect(result.job.status).toBe("COMPLETED");
    expect(result.job.importedCount).toBe(2);
    expect(result.job.errorCount).toBe(0);
    expect(contactsRepository.items).toHaveLength(2);
    expect(contactsRepository.items.every((c) => c.source === "IMPORT")).toBe(true);
  });

  it("marks invalid rows as ERROR without creating a contact", async () => {
    const job = await createConfiguredJob([{ "E-mail": "not-an-email", Nome: "Maria" }]);

    const result = await sut.execute({ jobId: job.id });

    expect(result.job.errorCount).toBe(1);
    expect(result.job.importedCount).toBe(0);
    expect(contactsRepository.items).toHaveLength(0);
  });

  it("skips rows whose e-mail already exists when duplicateStrategy is IGNORE", async () => {
    contactsRepository.items.push({
      id: "existing-1",
      email: "maria@example.com",
      name: "Maria Antiga",
      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(),
    });

    const job = await createConfiguredJob([{ "E-mail": "maria@example.com", Nome: "Maria Nova" }], {
      duplicateStrategy: "IGNORE",
    });

    const result = await sut.execute({ jobId: job.id });

    expect(result.job.skippedCount).toBe(1);
    expect(contactsRepository.items[0].name).toBe("Maria Antiga");
  });

  it("updates the existing contact when duplicateStrategy is UPDATE", async () => {
    contactsRepository.items.push({
      id: "existing-1",
      email: "maria@example.com",
      name: "Maria Antiga",
      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(),
    });

    const job = await createConfiguredJob([{ "E-mail": "maria@example.com", Nome: "Maria Nova" }], {
      duplicateStrategy: "UPDATE",
    });

    const result = await sut.execute({ jobId: job.id });

    expect(result.job.updatedCount).toBe(1);
    expect(contactsRepository.items[0].name).toBe("Maria Nova");
    expect(contactsRepository.items).toHaveLength(1);
  });

  it("adds the contact to the target list when one is configured", async () => {
    const job = await createConfiguredJob([{ "E-mail": "maria@example.com", Nome: "Maria" }], {
      targetListId: "list-1",
    });

    await sut.execute({ jobId: job.id });

    const contact = contactsRepository.items[0];
    expect(contactListsRepository.memberships).toContainEqual({ contactId: contact.id, listId: "list-1" });
  });

  it("returns the already-completed job without reprocessing when called again", async () => {
    const job = await createConfiguredJob([{ "E-mail": "maria@example.com", Nome: "Maria" }]);
    await sut.execute({ jobId: job.id });

    const second = await sut.execute({ jobId: job.id });

    expect(second.job.status).toBe("COMPLETED");
    expect(contactsRepository.items).toHaveLength(1);
  });
});
