import { Contact, ContactStatus } from "@prisma/client";
import {
  ContactListFilters,
  ContactsRepository,
  ContactWithLists,
  CreateContactData,
  FindPaginatedOptions,
  UpdateContactData,
} from "./contacts-repository";

function daysAgo(days: number): Date {
  return new Date(Date.now() - days * 24 * 60 * 60 * 1000);
}

export class InMemoryContactsRepository implements ContactsRepository {
  public items: Contact[] = [];
  public listMemberships: { contactId: string; listId: string; listName: string }[] = [];

  async findAll(): Promise<Contact[]> {
    return [...this.items].sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
  }

  async findAllWithLists(): Promise<ContactWithLists[]> {
    const contacts = await this.findAll();
    return contacts.map((contact) => ({
      ...contact,
      listMemberships: this.listMemberships
        .filter((m) => m.contactId === contact.id)
        .map((m) => ({ list: { id: m.listId, name: m.listName } })),
    }));
  }

  async findById(id: string): Promise<Contact | null> {
    return this.items.find((item) => item.id === id) ?? null;
  }

  async findByIdWithLists(id: string): Promise<ContactWithLists | null> {
    const contact = await this.findById(id);
    if (!contact) return null;
    return {
      ...contact,
      listMemberships: this.listMemberships
        .filter((m) => m.contactId === contact.id)
        .map((m) => ({ list: { id: m.listId, name: m.listName } })),
    };
  }

  async findAllActive(): Promise<Contact[]> {
    return this.items.filter((item) => item.status === ContactStatus.ACTIVE);
  }

  async findActiveByListId(listId: string): Promise<Contact[]> {
    return this.items.filter(
      (item) =>
        item.status === ContactStatus.ACTIVE &&
        this.listMemberships.some((m) => m.contactId === item.id && m.listId === listId)
    );
  }

  async createMany(emails: string[]): Promise<{ count: number }> {
    let createdCount = 0;

    for (const email of emails) {
      const exists = this.items.some((item) => item.email === email);
      if (!exists) {
        const contact: Contact = {
          id: `ccontact${this.items.length + 1}`,
          email,
          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(),
        };
        this.items.push(contact);
        createdCount++;
      }
    }

    return { count: createdCount };
  }

  async findManyByEmails(emails: string[]): Promise<Contact[]> {
    return this.items.filter((item) => emails.includes(item.email));
  }

  async delete(id: string): Promise<void> {
    this.items = this.items.filter((item) => item.id !== id);
    this.listMemberships = this.listMemberships.filter((m) => m.contactId !== id);
  }

  async deleteContactsByListId(listId: string): Promise<number> {
    // Encontra contatos que pertencem EXCLUSIVAMENTE a essa lista
    const exclusiveIds = this.items
      .filter((contact) => {
        const memberships = this.listMemberships.filter((m) => m.contactId === contact.id);
        return memberships.length > 0 && memberships.every((m) => m.listId === listId);
      })
      .map((c) => c.id);

    this.items = this.items.filter((item) => !exclusiveIds.includes(item.id));
    this.listMemberships = this.listMemberships.filter(
      (m) => !exclusiveIds.includes(m.contactId)
    );
    return exclusiveIds.length;
  }

  async updateStatus(id: string, status: ContactStatus): Promise<Contact> {
    const index = this.items.findIndex((item) => item.id === id);
    if (index === -1) throw new Error("Contact not found");
    this.items[index] = { ...this.items[index], status, updatedAt: new Date() };
    return this.items[index];
  }

  async deleteMany(ids: string[]): Promise<number> {
    const initialLen = this.items.length;
    this.items = this.items.filter((item) => !ids.includes(item.id));
    this.listMemberships = this.listMemberships.filter((m) => !ids.includes(m.contactId));
    return initialLen - this.items.length;
  }

  private matchesFilters(item: Contact, filters: ContactListFilters): boolean {
    if (filters.search) {
      const q = filters.search.toLowerCase();
      const matchName = item.name?.toLowerCase().includes(q);
      const matchEmail = item.email.toLowerCase().includes(q);
      const matchPhone = item.phone?.toLowerCase().includes(q);
      if (!matchName && !matchEmail && !matchPhone) return false;
    }
    if (filters.tag && !item.tags.includes(filters.tag)) return false;
    if (filters.status && item.status !== filters.status) return false;
    if (
      filters.listId &&
      !this.listMemberships.some((m) => m.contactId === item.id && m.listId === filters.listId)
    )
      return false;
    if (filters.subscribedWithinDays && item.createdAt < daysAgo(filters.subscribedWithinDays)) return false;
    if (filters.openedWithinDays && !this.openedContactIds.has(item.id)) return false;

    return true;
  }

  /** Campo só-de-teste: specs marcam aqui quais contatos têm um EmailEvent OPEN recente, já que
   * este repositório não modela EmailEvent de verdade (mesmo espírito do `openedLogIds` em
   * InMemoryCampaignLogsRepository). */
  public openedContactIds: Set<string> = new Set();

  async findPaginated({ skip, take, sortBy, sortDir, ...filters }: FindPaginatedOptions): Promise<ContactWithLists[]> {
    const filtered = this.items.filter((item) => this.matchesFilters(item, filters));
    const key = sortBy ?? "createdAt";
    const dir = sortDir ?? "desc";

    filtered.sort((a, b) => {
      const aVal = a[key] ?? "";
      const bVal = b[key] ?? "";
      const cmp = aVal instanceof Date && bVal instanceof Date ? aVal.getTime() - bVal.getTime() : String(aVal).localeCompare(String(bVal));
      return dir === "asc" ? cmp : -cmp;
    });

    const page = filtered.slice(skip, skip + take);
    return page.map((contact) => ({
      ...contact,
      listMemberships: this.listMemberships
        .filter((m) => m.contactId === contact.id)
        .map((m) => ({ list: { id: m.listId, name: m.listName } })),
    }));
  }

  async countFiltered(filters: ContactListFilters): Promise<number> {
    return this.items.filter((item) => this.matchesFilters(item, filters)).length;
  }

  async create(data: CreateContactData): Promise<Contact> {
    const contact: Contact = {
      id: `ccontact${this.items.length + 1}`,
      email: data.email,
      name: data.name ?? null,
      lastName: data.lastName ?? null,
      phone: data.phone ?? null,
      bairro: data.bairro ?? null,
      cidade: data.cidade ?? null,
      uf: data.uf ?? null,
      idioma: data.idioma ?? null,
      empresa: data.empresa ?? null,
      cep: data.cep ?? null,
      codigoEstabelecimento: data.codigoEstabelecimento ?? null,
      nomeEstabelecimento: data.nomeEstabelecimento ?? null,
      cdate: data.cdate ?? null,
      tags: data.tags ?? [],
      source: data.source ?? "MANUAL",
      status: ContactStatus.ACTIVE,
      createdAt: new Date(),
      updatedAt: new Date(),
    };
    this.items.push(contact);
    return contact;
  }

  async update(id: string, data: UpdateContactData): Promise<Contact> {
    const index = this.items.findIndex((item) => item.id === id);
    if (index === -1) {
      throw new Error("Contact not found");
    }

    const updated: Contact = {
      ...this.items[index],
      ...(data.status !== undefined && { status: data.status }),
      ...(data.name !== undefined && { name: data.name }),
      ...(data.lastName !== undefined && { lastName: data.lastName }),
      ...(data.phone !== undefined && { phone: data.phone }),
      ...(data.bairro !== undefined && { bairro: data.bairro }),
      ...(data.cidade !== undefined && { cidade: data.cidade }),
      ...(data.uf !== undefined && { uf: data.uf }),
      ...(data.idioma !== undefined && { idioma: data.idioma }),
      ...(data.empresa !== undefined && { empresa: data.empresa }),
      ...(data.cep !== undefined && { cep: data.cep }),
      ...(data.codigoEstabelecimento !== undefined && { codigoEstabelecimento: data.codigoEstabelecimento }),
      ...(data.nomeEstabelecimento !== undefined && { nomeEstabelecimento: data.nomeEstabelecimento }),
      ...(data.cdate !== undefined && { cdate: data.cdate }),
      ...(data.tags !== undefined && { tags: data.tags }),
      updatedAt: new Date(),
    };
    this.items[index] = updated;
    return updated;
  }

  async addTagsToMany(ids: string[], tags: string[], _userId?: string): Promise<void> {
    this.items = this.items.map((item) =>
      ids.includes(item.id) ? { ...item, tags: [...new Set([...item.tags, ...tags])] } : item
    );
  }

  async upsertByEmail(data: CreateContactData): Promise<{ contact: Contact; created: boolean }> {
    const existing = this.items.find((item) => item.email === data.email);
    if (!existing) {
      const contact = await this.create(data);
      return { contact, created: true };
    }

    const updated: Contact = {
      ...existing,
      ...(data.name !== undefined && data.name !== null && { name: data.name }),
      ...(data.phone !== undefined && data.phone !== null && { phone: data.phone }),
      ...(data.tags?.length && { tags: [...new Set([...existing.tags, ...data.tags])] }),
      updatedAt: new Date(),
    };
    const index = this.items.findIndex((item) => item.id === existing.id);
    this.items[index] = updated;
    return { contact: updated, created: false };
  }
}
