import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test"

import type { Doc, Id } from "../convex/_generated/dataModel"
import { resolveTicketHandler, reopenTicketHandler } from "../convex/tickets"
import { requireStaff, requireUser } from "../convex/rbac"

type ResolveCtx = Parameters<typeof resolveTicketHandler>[0]
type ReopenCtx = Parameters<typeof reopenTicketHandler>[0]

type TicketDoc = Doc<"tickets">

type MockedTicket = TicketDoc & { _id: Id<"tickets"> }

defineMocks()

function defineMocks() {
  vi.mock("../convex/rbac", () => ({
    requireStaff: vi.fn(),
    requireUser: vi.fn(),
    requireAdmin: vi.fn(),
  }))
}

const mockedRequireStaff = vi.mocked(requireStaff)
const mockedRequireUser = vi.mocked(requireUser)

const NOW = 1_706_700_000_000

beforeEach(() => {
  vi.setSystemTime(NOW)
})

afterEach(() => {
  vi.clearAllMocks()
})

function buildTicket(overrides: Partial<TicketDoc> = {}): MockedTicket {
  const base: TicketDoc = {
    _id: "ticket_main" as Id<"tickets">,
    _creationTime: NOW - 10_000,
    tenantId: "tenant-1",
    reference: 41_000,
    subject: "Computador não liga",
    summary: undefined,
    status: "AWAITING_ATTENDANCE",
    priority: "MEDIUM",
    channel: "EMAIL",
    queueId: undefined,
    requesterId: "user_requester" as Id<"users">,
    requesterSnapshot: { name: "Cliente", email: "cliente@example.com" },
    assigneeId: "user_agent" as Id<"users">,
    assigneeSnapshot: { name: "Agente", email: "agente@example.com" },
    companyId: undefined,
    companySnapshot: undefined,
    machineId: undefined,
    machineSnapshot: undefined,
    slaPolicyId: undefined,
    dueAt: undefined,
    firstResponseAt: undefined,
    resolvedAt: undefined,
    createdAt: NOW - 50_000,
    updatedAt: NOW - 1_000,
    tags: [],
    customFields: [],
    activeSessionId: undefined,
    totalWorkedMs: 0,
    internalWorkedMs: 0,
    externalWorkedMs: 0,
    csatScore: undefined,
    csatMaxScore: undefined,
    csatComment: undefined,
    csatRatedAt: undefined,
    csatRatedBy: undefined,
    csatAssigneeId: undefined,
    csatAssigneeSnapshot: undefined,
    reopenDeadline: undefined,
    reopenedAt: undefined,
    formTemplate: undefined,
    chatEnabled: true,
    relatedTicketIds: undefined,
    resolvedWithTicketId: undefined,
  }
  return { ...(base as TicketDoc), ...overrides }
}

function createResolveCtx(tickets: Record<string, TicketDoc>, events: Doc<"ticketEvents">[] = []) {
  const patch = vi.fn(async () => undefined)
  const insert = vi.fn(async () => undefined)
  const collect = vi.fn(async () => events)
  const query = vi.fn(() => ({
    withIndex: vi.fn((_index: string, cb: (builder: { eq: (field: string, value: unknown) => { collect: typeof collect } }) => { collect: typeof collect }) => {
      const cursor = {
        eq: vi.fn(() => ({ collect })),
      }
      const result = cb(cursor as { eq: (field: string, value: unknown) => { collect: typeof collect } })
      return result ?? { collect }
    }),
  }))

  const ctx: ResolveCtx = {
    db: {
      get: vi.fn(async (id: Id<"tickets"> | Id<"users">) => {
        return tickets[String(id)] ?? null
      }),
      patch,
      insert,
      query,
      delete: vi.fn(),
    },
  } as unknown as ResolveCtx

  return { ctx, patch, insert }
}

function createReopenCtx(ticket: TicketDoc) {
  const patch = vi.fn(async () => undefined)
  const insert = vi.fn(async () => undefined)
  const ctx: ReopenCtx = {
    db: {
      get: vi.fn(async () => ticket),
      patch,
      insert,
      query: vi.fn(),
      delete: vi.fn(),
    },
  } as unknown as ReopenCtx

  return { ctx, patch, insert }
}

describe("convex.tickets.resolveTicketHandler", () => {
  it("marca o ticket como resolvido, vincula outro ticket e define prazo de reabertura", async () => {
    const ticketMain = buildTicket()
    const ticketLinked = buildTicket({
      _id: "ticket_related" as Id<"tickets">,
      reference: 41_001,
      status: "AWAITING_ATTENDANCE",
    })

    const { ctx, patch, insert } = createResolveCtx({
      [String(ticketMain._id)]: ticketMain,
      [String(ticketLinked._id)]: ticketLinked,
    })

    mockedRequireStaff.mockResolvedValue({
      user: {
        _id: "user_agent" as Id<"users">,
        _creationTime: NOW - 5_000,
        tenantId: "tenant-1",
        name: "Agente",
        email: "agente@example.com",
        role: "ADMIN",
        teams: [],
      },
      role: "ADMIN",
    })

    const result = await resolveTicketHandler(ctx, {
      ticketId: ticketMain._id,
      actorId: "user_agent" as Id<"users">,
      resolvedWithTicketId: ticketLinked._id,
      reopenWindowDays: 14,
    })

    expect(result.ok).toBe(true)
    expect(result.reopenWindowDays).toBe(14)
    expect(result.reopenDeadline).toBe(NOW + 14 * 24 * 60 * 60 * 1000)

    expect(patch).toHaveBeenCalledWith(
      ticketMain._id,
      expect.objectContaining({
        status: "RESOLVED",
        reopenDeadline: result.reopenDeadline,
        resolvedWithTicketId: ticketLinked._id,
      })
    )

    expect(patch).toHaveBeenCalledWith(
      ticketLinked._id,
      expect.objectContaining({
        relatedTicketIds: expect.arrayContaining([ticketMain._id]),
      })
    )

    expect(insert).toHaveBeenCalledWith(
      "ticketEvents",
      expect.objectContaining({
        ticketId: ticketMain._id,
        type: "STATUS_CHANGED",
        payload: expect.objectContaining({ to: "RESOLVED" }),
      })
    )
    expect(insert).toHaveBeenCalledWith(
      "ticketEvents",
      expect.objectContaining({
        ticketId: ticketLinked._id,
        type: "TICKET_LINKED",
        payload: expect.objectContaining({ linkedTicketId: ticketMain._id }),
      })
    )
  })

  it("impede encerrar ticket quando há checklist obrigatório pendente", async () => {
    const ticketMain = buildTicket({
      checklist: [{ id: "c1", text: "Validar backup", done: false, required: true }] as unknown as TicketDoc["checklist"],
    })

    const { ctx, patch } = createResolveCtx({
      [String(ticketMain._id)]: ticketMain,
    })

    mockedRequireStaff.mockResolvedValue({
      user: {
        _id: "user_agent" as Id<"users">,
        _creationTime: NOW - 5_000,
        tenantId: "tenant-1",
        name: "Agente",
        email: "agente@example.com",
        role: "ADMIN",
        teams: [],
      },
      role: "ADMIN",
    })

    await expect(
      resolveTicketHandler(ctx, {
        ticketId: ticketMain._id,
        actorId: "user_agent" as Id<"users">,
      })
    ).rejects.toThrow("checklist")

    expect(patch).not.toHaveBeenCalled()
  })
})

describe("convex.tickets.reopenTicketHandler", () => {
  it("reabre o ticket quando dentro do prazo e permissões válidas", async () => {
    const ticket = buildTicket({
      status: "RESOLVED",
      reopenDeadline: NOW + 3 * 24 * 60 * 60 * 1000,
      resolvedAt: NOW - 60_000,
    })

    const { ctx, patch, insert } = createReopenCtx(ticket)

    mockedRequireUser.mockResolvedValue({
      user: {
        _id: ticket.requesterId,
        _creationTime: NOW - 20_000,
        tenantId: "tenant-1",
        email: "cliente@example.com",
        name: "Cliente",
        role: "COLLABORATOR",
        companyId: undefined,
        teams: [],
      },
      role: "COLLABORATOR",
    })

    const result = await reopenTicketHandler(ctx, {
      ticketId: ticket._id,
      actorId: ticket.requesterId,
    })

    expect(result.ok).toBe(true)
    expect(result.reopenedAt).toBe(NOW)
    expect(patch).toHaveBeenCalledWith(
      ticket._id,
      expect.objectContaining({ status: "AWAITING_ATTENDANCE", reopenedAt: NOW, resolvedAt: undefined })
    )
    expect(insert).toHaveBeenCalledWith(
      "ticketEvents",
      expect.objectContaining({ ticketId: ticket._id, type: "TICKET_REOPENED" })
    )
  })

  it("falha quando o prazo para reabrir expirou", async () => {
    const ticket = buildTicket({
      status: "RESOLVED",
      reopenDeadline: NOW - 1,
    })

    const { ctx } = createReopenCtx(ticket)
    mockedRequireUser.mockResolvedValue({
      user: {
        _id: ticket.requesterId,
        _creationTime: NOW - 25_000,
        tenantId: "tenant-1",
        email: "cliente@example.com",
        name: "Cliente",
        role: "COLLABORATOR",
        companyId: undefined,
        teams: [],
      },
      role: "COLLABORATOR",
    })

    await expect(
      reopenTicketHandler(ctx, {
        ticketId: ticket._id,
        actorId: ticket.requesterId,
      })
    ).rejects.toThrow("O prazo para reabrir este chamado expirou")
  })
})
