import { randomUUID } from "node:crypto";
import type { PrismaClient } from "@prisma/client";

type CalendarSnapshotRow = {
  id: string;
  teamId: string;
  contactId: string | null;
  title: string;
  startsAt: string;
  endsAt: string | null;
  note: string | null;
  isArchived: boolean;
  archiveNote: string | null;
  archivedAt: string | null;
};

type ContactNoteSnapshotRow = {
  contactId: string;
  contactName: string;
  content: string;
};

type MessageSnapshotRow = {
  id: string;
  contactId: string;
  contactName: string;
  kind: string;
  direction: string;
  channel: string;
  content: string;
  durationSec: number | null;
  occurredAt: string;
};

type DealSnapshotRow = {
  id: string;
  title: string;
  contactName: string;
  stage: string;
  nextStep: string | null;
  summary: string | null;
};

export type SnapshotState = {
  calendarById: Map<string, CalendarSnapshotRow>;
  noteByContactId: Map<string, ContactNoteSnapshotRow>;
  messageById: Map<string, MessageSnapshotRow>;
  dealById: Map<string, DealSnapshotRow>;
};

export type ChangeItem = {
  entity: "calendar_event" | "contact_note" | "message" | "deal";
  action: "created" | "updated" | "deleted";
  title: string;
  before: string;
  after: string;
};

type UndoOp =
  | { kind: "delete_calendar_event"; id: string }
  | { kind: "restore_calendar_event"; data: CalendarSnapshotRow }
  | { kind: "delete_contact_message"; id: string }
  | { kind: "restore_contact_message"; data: MessageSnapshotRow }
  | { kind: "restore_contact_note"; contactId: string; content: string | null }
  | { kind: "restore_deal"; id: string; stage: string; nextStep: string | null; summary: string | null };

export type ChangeSet = {
  id: string;
  status: "pending" | "confirmed" | "rolled_back";
  createdAt: string;
  summary: string;
  items: ChangeItem[];
  undo: UndoOp[];
};

function fmt(val: string | null | undefined) {
  return (val ?? "").trim();
}

function toCalendarText(row: CalendarSnapshotRow) {
  const when = new Date(row.startsAt).toLocaleString("ru-RU");
  return `${row.title} · ${when}${row.note ? ` · ${row.note}` : ""}`;
}

function toMessageText(row: MessageSnapshotRow) {
  const when = new Date(row.occurredAt).toLocaleString("ru-RU");
  return `${row.contactName} · ${row.channel} · ${row.kind.toLowerCase()} · ${when} · ${row.content}`;
}

function toDealText(row: DealSnapshotRow) {
  return `${row.title} (${row.contactName}) · ${row.stage}${row.nextStep ? ` · next: ${row.nextStep}` : ""}`;
}

export async function captureSnapshot(prisma: PrismaClient, teamId: string): Promise<SnapshotState> {
  const [calendar, notes, messages, deals] = await Promise.all([
    prisma.calendarEvent.findMany({
      where: { teamId },
      select: {
        id: true,
        teamId: true,
        contactId: true,
        title: true,
        startsAt: true,
        endsAt: true,
        note: true,
        isArchived: true,
        archiveNote: true,
        archivedAt: true,
      },
      take: 4000,
    }),
    prisma.contactNote.findMany({
      where: { contact: { teamId } },
      select: { contactId: true, content: true, contact: { select: { name: true } } },
      take: 4000,
    }),
    prisma.contactMessage.findMany({
      where: { contact: { teamId } },
      include: { contact: { select: { name: true } } },
      orderBy: { createdAt: "asc" },
      take: 6000,
    }),
    prisma.deal.findMany({
      where: { teamId },
      include: { contact: { select: { name: true } } },
      take: 4000,
    }),
  ]);

  return {
    calendarById: new Map(
      calendar.map((row) => [
        row.id,
        {
          id: row.id,
          teamId: row.teamId,
          contactId: row.contactId ?? null,
          title: row.title,
          startsAt: row.startsAt.toISOString(),
          endsAt: row.endsAt?.toISOString() ?? null,
          note: row.note ?? null,
          isArchived: Boolean(row.isArchived),
          archiveNote: row.archiveNote ?? null,
          archivedAt: row.archivedAt?.toISOString() ?? null,
        },
      ]),
    ),
    noteByContactId: new Map(
      notes.map((row) => [
        row.contactId,
        {
          contactId: row.contactId,
          contactName: row.contact.name,
          content: row.content,
        },
      ]),
    ),
    messageById: new Map(
      messages.map((row) => [
        row.id,
        {
          id: row.id,
          contactId: row.contactId,
          contactName: row.contact.name,
          kind: row.kind,
          direction: row.direction,
          channel: row.channel,
          content: row.content,
          durationSec: row.durationSec ?? null,
          occurredAt: row.occurredAt.toISOString(),
        },
      ]),
    ),
    dealById: new Map(
      deals.map((row) => [
        row.id,
        {
          id: row.id,
          title: row.title,
          contactName: row.contact.name,
          stage: row.stage,
          nextStep: row.nextStep ?? null,
          summary: row.summary ?? null,
        },
      ]),
    ),
  };
}

export function buildChangeSet(before: SnapshotState, after: SnapshotState): ChangeSet | null {
  const items: ChangeItem[] = [];
  const undo: UndoOp[] = [];

  for (const [id, row] of after.calendarById) {
    const prev = before.calendarById.get(id);
    if (!prev) {
      items.push({
        entity: "calendar_event",
        action: "created",
        title: `Event created: ${row.title}`,
        before: "",
        after: toCalendarText(row),
      });
      undo.push({ kind: "delete_calendar_event", id });
      continue;
    }
    if (
      prev.title !== row.title ||
      prev.startsAt !== row.startsAt ||
      prev.endsAt !== row.endsAt ||
      fmt(prev.note) !== fmt(row.note) ||
      prev.isArchived !== row.isArchived ||
      fmt(prev.archiveNote) !== fmt(row.archiveNote) ||
      fmt(prev.archivedAt) !== fmt(row.archivedAt) ||
      prev.contactId !== row.contactId
    ) {
      items.push({
        entity: "calendar_event",
        action: "updated",
        title: `Event updated: ${row.title}`,
        before: toCalendarText(prev),
        after: toCalendarText(row),
      });
      undo.push({ kind: "restore_calendar_event", data: prev });
    }
  }

  for (const [id, row] of before.calendarById) {
    if (after.calendarById.has(id)) continue;
    items.push({
      entity: "calendar_event",
      action: "deleted",
      title: `Event archived: ${row.title}`,
      before: toCalendarText(row),
      after: "",
    });
    undo.push({ kind: "restore_calendar_event", data: row });
  }

  for (const [contactId, row] of after.noteByContactId) {
    const prev = before.noteByContactId.get(contactId);
    if (!prev) {
      items.push({
        entity: "contact_note",
        action: "created",
        title: `Summary added: ${row.contactName}`,
        before: "",
        after: row.content,
      });
      undo.push({ kind: "restore_contact_note", contactId, content: null });
      continue;
    }
    if (prev.content !== row.content) {
      items.push({
        entity: "contact_note",
        action: "updated",
        title: `Summary updated: ${row.contactName}`,
        before: prev.content,
        after: row.content,
      });
      undo.push({ kind: "restore_contact_note", contactId, content: prev.content });
    }
  }

  for (const [contactId, row] of before.noteByContactId) {
    if (after.noteByContactId.has(contactId)) continue;
    items.push({
      entity: "contact_note",
      action: "deleted",
      title: `Summary cleared: ${row.contactName}`,
      before: row.content,
      after: "",
    });
    undo.push({ kind: "restore_contact_note", contactId, content: row.content });
  }

  for (const [id, row] of after.messageById) {
    if (before.messageById.has(id)) continue;
    items.push({
      entity: "message",
      action: "created",
      title: `Message created: ${row.contactName}`,
      before: "",
      after: toMessageText(row),
    });
    undo.push({ kind: "delete_contact_message", id });
  }

  for (const [id, row] of after.dealById) {
    const prev = before.dealById.get(id);
    if (!prev) continue;
    if (prev.stage !== row.stage || fmt(prev.nextStep) !== fmt(row.nextStep) || fmt(prev.summary) !== fmt(row.summary)) {
      items.push({
        entity: "deal",
        action: "updated",
        title: `Deal updated: ${row.title}`,
        before: toDealText(prev),
        after: toDealText(row),
      });
      undo.push({
        kind: "restore_deal",
        id,
        stage: prev.stage,
        nextStep: prev.nextStep,
        summary: prev.summary,
      });
    }
  }

  if (items.length === 0) return null;

  const created = items.filter((x) => x.action === "created").length;
  const updated = items.filter((x) => x.action === "updated").length;
  const deleted = items.filter((x) => x.action === "deleted").length;

  return {
    id: randomUUID(),
    status: "pending",
    createdAt: new Date().toISOString(),
    summary: `Created: ${created}, Updated: ${updated}, Archived: ${deleted}`,
    items,
    undo,
  };
}

export async function rollbackChangeSet(prisma: PrismaClient, teamId: string, changeSet: ChangeSet) {
  const ops = [...changeSet.undo].reverse();

  await prisma.$transaction(async (tx) => {
    for (const op of ops) {
      if (op.kind === "delete_calendar_event") {
        await tx.calendarEvent.deleteMany({ where: { id: op.id, teamId } });
        continue;
      }

      if (op.kind === "restore_calendar_event") {
        const row = op.data;
        await tx.calendarEvent.upsert({
          where: { id: row.id },
          update: {
            teamId: row.teamId,
            contactId: row.contactId,
            title: row.title,
            startsAt: new Date(row.startsAt),
            endsAt: row.endsAt ? new Date(row.endsAt) : null,
            note: row.note,
            isArchived: row.isArchived,
            archiveNote: row.archiveNote,
            archivedAt: row.archivedAt ? new Date(row.archivedAt) : null,
          },
          create: {
            id: row.id,
            teamId: row.teamId,
            contactId: row.contactId,
            title: row.title,
            startsAt: new Date(row.startsAt),
            endsAt: row.endsAt ? new Date(row.endsAt) : null,
            note: row.note,
            isArchived: row.isArchived,
            archiveNote: row.archiveNote,
            archivedAt: row.archivedAt ? new Date(row.archivedAt) : null,
          },
        });
        continue;
      }

      if (op.kind === "delete_contact_message") {
        await tx.contactMessage.deleteMany({ where: { id: op.id } });
        continue;
      }

      if (op.kind === "restore_contact_message") {
        const row = op.data;
        await tx.contactMessage.upsert({
          where: { id: row.id },
          update: {
            contactId: row.contactId,
            kind: row.kind as any,
            direction: row.direction as any,
            channel: row.channel as any,
            content: row.content,
            durationSec: row.durationSec,
            occurredAt: new Date(row.occurredAt),
          },
          create: {
            id: row.id,
            contactId: row.contactId,
            kind: row.kind as any,
            direction: row.direction as any,
            channel: row.channel as any,
            content: row.content,
            durationSec: row.durationSec,
            occurredAt: new Date(row.occurredAt),
          },
        });
        continue;
      }

      if (op.kind === "restore_contact_note") {
        const contact = await tx.contact.findFirst({ where: { id: op.contactId, teamId }, select: { id: true } });
        if (!contact) continue;
        if (op.content === null) {
          await tx.contactNote.deleteMany({ where: { contactId: op.contactId } });
        } else {
          await tx.contactNote.upsert({
            where: { contactId: op.contactId },
            update: { content: op.content },
            create: { contactId: op.contactId, content: op.content },
          });
        }
        continue;
      }

      if (op.kind === "restore_deal") {
        await tx.deal.updateMany({
          where: { id: op.id, teamId },
          data: {
            stage: op.stage,
            nextStep: op.nextStep,
            summary: op.summary,
          },
        });
      }
    }
  });
}
