import { ContactImportJob } from "@prisma/client";
import { AppError } from "@/core/errors/app-error";
import { ColumnMapping, ContactImportJobsRepository } from "../repositories/contact-import-jobs-repository";
import { ContactImportRowsRepository } from "../repositories/contact-import-rows-repository";
import { ContactsRepository } from "../repositories/contacts-repository";
import { ContactListsRepository } from "../repositories/contact-lists-repository";
import { mapImportRow } from "../entities/map-import-row";

const BATCH_SIZE = 200;

interface ProcessImportBatchUseCaseRequest {
  jobId: string;
}

interface ProcessImportBatchUseCaseResponse {
  job: ContactImportJob;
}

/**
 * Processa até `BATCH_SIZE` linhas pendentes de um job por chamada — o front chama esse
 * endpoint repetidamente (polling) até `job.status === "COMPLETED"`, o que evita precisar de
 * fila/worker em background (não há Redis/BullMQ no projeto) e mantém cada chamada dentro do
 * tempo de execução de uma função serverless.
 *
 * LIMITAÇÃO: como `Contact.email` é único, a estratégia `CREATE_ANYWAY` não pode de fato criar um
 * segundo contato com o mesmo e-mail — é tratada como sinônimo de `UPDATE` (atualiza o contato
 * existente em vez de duplicar), e a linha é marcada como `UPDATED`. Isso é intencional e
 * documentado aqui em vez de escondido: "criar mesmo assim" só faz sentido se e-mail não fosse a
 * chave de unicidade do sistema.
 */
export class ProcessImportBatchUseCase {
  constructor(
    private contactImportJobsRepository: ContactImportJobsRepository,
    private contactImportRowsRepository: ContactImportRowsRepository,
    private contactsRepository: ContactsRepository,
    private contactListsRepository: ContactListsRepository
  ) {}

  async execute({ jobId }: ProcessImportBatchUseCaseRequest): Promise<ProcessImportBatchUseCaseResponse> {
    const job = await this.contactImportJobsRepository.findById(jobId);
    if (!job) {
      throw AppError.notFound("Job de importação não encontrado");
    }
    if (job.status === "COMPLETED") {
      return { job };
    }
    if (job.status !== "PROCESSING" || !job.columnMapping || !job.duplicateStrategy) {
      throw AppError.badRequest("O mapeamento de colunas ainda não foi confirmado para este job");
    }

    const mapping = job.columnMapping as unknown as ColumnMapping;
    const batch = await this.contactImportRowsRepository.findPendingBatch(jobId, BATCH_SIZE);

    let imported = 0;
    let updated = 0;
    let skipped = 0;
    let errors = 0;

    for (const row of batch) {
      const mapped = mapImportRow(row.rawData as Record<string, string>, mapping);

      if (!mapped.valid) {
        await this.contactImportRowsRepository.updateStatus(row.id, "ERROR", mapped.error);
        errors++;
        continue;
      }

      const existing = await this.contactsRepository.findManyByEmails([mapped.data.email]);

      if (existing.length > 0 && job.duplicateStrategy === "IGNORE") {
        await this.contactImportRowsRepository.updateStatus(row.id, "SKIPPED");
        skipped++;
        continue;
      }

      const isNew = existing.length === 0;
      const { contact } = await this.contactsRepository.upsertByEmail({
        email: mapped.data.email,
        name: mapped.data.name ?? null,
        phone: mapped.data.phone ?? null,
        tags: mapped.data.tags,
        source: "IMPORT",
      });

      if (job.targetListId) {
        await this.contactListsRepository.addContactsToLists([contact.id], [job.targetListId]);
      }

      await this.contactImportRowsRepository.updateStatus(row.id, isNew ? "IMPORTED" : "UPDATED");
      if (isNew) imported++;
      else updated++;
    }

    await this.contactImportJobsRepository.incrementCounters(jobId, {
      processedRows: batch.length,
      importedCount: imported,
      updatedCount: updated,
      skippedCount: skipped,
      errorCount: errors,
    });

    const remaining = await this.contactImportRowsRepository.countRemaining(jobId);
    if (remaining === 0) {
      const completedJob = await this.contactImportJobsRepository.markCompleted(jobId);
      return { job: completedJob };
    }

    const updatedJob = await this.contactImportJobsRepository.findById(jobId);
    return { job: updatedJob! };
  }
}
