import { Campaign, CampaignLog, Contact, LogStatus, Sender } from "@prisma/client";
import { CampaignWithSender } from "./campaigns-repository";
import {
  CampaignLogsRepository,
  CampaignLogStatusCounts,
  CampaignLogWithCampaignAndSenderName,
  FindByContactIdOptions,
} from "./campaign-logs-repository";

type InMemoryCampaignLog = CampaignLog & {
  contact?: Contact;
  campaign?: CampaignWithSender | (Campaign & { sender: Pick<Sender, "name" | "fromEmail"> });
};

export class InMemoryCampaignLogsRepository implements CampaignLogsRepository {
  public items: InMemoryCampaignLog[] = [];
  /**
   * Campo só-de-teste: como o repositório em memória não tem um `EmailEvent` relacional de
   * verdade, os specs marcam aqui quais logs devem ser considerados "abertos" para exercitar o
   * filtro opened/unopened (que na implementação Prisma é resolvido via relation-filter em
   * `campaign.emailEvents`).
   */
  public openedLogIds: Set<string> = new Set();

  async upsertPending(campaignId: string, contactId: string): Promise<CampaignLog> {
    const existing = this.items.find(
      (item) => item.campaignId === campaignId && item.contactId === contactId
    );
    if (existing) {
      existing.status = LogStatus.PENDING;
      return existing;
    }

    const log: InMemoryCampaignLog = {
      id: `log-${this.items.length + 1}`,
      status: LogStatus.PENDING,
      sentAt: null,
      errorMessage: null,
      retryCount: 0,
      messageId: null,
      campaignId,
      contactId,
      createdAt: new Date(),
      updatedAt: new Date(),
    };
    this.items.push(log);
    return log;
  }

  async markSuccess(logId: string): Promise<void> {
    const log = this.items.find((item) => item.id === logId);
    if (!log) return;
    log.status = LogStatus.SUCCESS;
    log.sentAt = new Date();
    log.errorMessage = null;
  }

  async markFailed(logId: string, errorMessage: string): Promise<void> {
    const log = this.items.find((item) => item.id === logId);
    if (!log) return;
    log.status = LogStatus.FAILED;
    log.errorMessage = errorMessage;
  }

  async incrementRetryCount(logId: string): Promise<void> {
    const log = this.items.find((item) => item.id === logId);
    if (!log) return;
    log.retryCount += 1;
  }

  async updateMessageId(logId: string, messageId: string): Promise<void> {
    const log = this.items.find((item) => item.id === logId);
    if (log) log.messageId = messageId;
  }

  async findByMessageId(messageId: string): Promise<(CampaignLog & { contact: Contact }) | null> {
    const log = this.items.find((item) => item.messageId === messageId);
    if (!log || !log.contact) return null;
    return log as CampaignLog & { contact: Contact };
  }

  async findFailedByCampaignId(campaignId: string): Promise<(CampaignLog & { contact: Contact })[]> {
    return this.items.filter(
      (item): item is CampaignLog & { contact: Contact } =>
        item.campaignId === campaignId && item.status === LogStatus.FAILED && item.contact !== undefined
    );
  }

  async countFailedByCampaignId(campaignId: string): Promise<number> {
    return this.items.filter(
      (item) => item.campaignId === campaignId && item.status === LogStatus.FAILED
    ).length;
  }

  async countByStatusForCampaignIds(campaignIds: string[]): Promise<CampaignLogStatusCounts> {
    const relevant = this.items.filter((item) => campaignIds.includes(item.campaignId));

    return {
      total: relevant.length,
      success: relevant.filter((item) => item.status === LogStatus.SUCCESS).length,
      failed: relevant.filter((item) => item.status === LogStatus.FAILED).length,
      pending: relevant.filter((item) => item.status === LogStatus.PENDING).length,
    };
  }

  async findCampaignAndContactIdsByLogId(
    logId: string
  ): Promise<{ campaignId: string; contactId: string } | null> {
    const log = this.items.find((item) => item.id === logId);
    if (!log) return null;
    return { campaignId: log.campaignId, contactId: log.contactId };
  }

  async findByIdWithCampaignAndContact(
    logId: string
  ): Promise<(CampaignLog & { campaign: CampaignWithSender; contact: Contact }) | null> {
    const log = this.items.find((item) => item.id === logId);
    if (!log || !log.contact || !log.campaign) return null;
    return log as CampaignLog & { campaign: CampaignWithSender; contact: Contact };
  }

  async findByContactId(
    contactId: string,
    { skip, take, status }: FindByContactIdOptions
  ): Promise<CampaignLogWithCampaignAndSenderName[]> {
    const filtered = this.items
      .filter((item): item is InMemoryCampaignLog & { campaign: CampaignLogWithCampaignAndSenderName["campaign"] } =>
        item.contactId === contactId && item.campaign !== undefined
      )
      .filter((item) => {
        if (status === "opened") return this.openedLogIds.has(item.id);
        if (status === "unopened") return !this.openedLogIds.has(item.id);
        return true;
      })
      .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());

    return filtered.slice(skip, skip + take) as CampaignLogWithCampaignAndSenderName[];
  }

  async countByContactId(contactId: string, { status }: { status?: "opened" | "unopened" }): Promise<number> {
    return this.items.filter((item) => {
      if (item.contactId !== contactId || item.campaign === undefined) return false;
      if (status === "opened") return this.openedLogIds.has(item.id);
      if (status === "unopened") return !this.openedLogIds.has(item.id);
      return true;
    }).length;
  }

  async findLogsForAnalytics(
    campaignIds: string[],
    startDate?: Date,
    endDate?: Date
  ): Promise<
    Array<{
      id: string;
      status: string;
      createdAt: Date;
      sentAt: Date | null;
      retryCount: number;
      contact: { email: string };
    }>
  > {
    return this.items
      .filter((item) => {
        if (!campaignIds.includes(item.campaignId)) return false;
        if (startDate && item.createdAt < startDate) return false;
        if (endDate && item.createdAt > endDate) return false;
        return true;
      })
      .map((item) => ({
        id: item.id,
        status: item.status,
        createdAt: item.createdAt,
        sentAt: item.sentAt,
        retryCount: item.retryCount,
        contact: {
          email: item.contact?.email || "contato@exemplo.com",
        },
      }));
  }
}

