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

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

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

type SubmitCsatCtx = Parameters<typeof submitCsatHandler>[0]

const mockedRequireUser = vi.mocked(requireUser)

const FIXED_NOW = 1_706_500_000_000

function makeTicket(overrides: Partial<Doc<"tickets">> = {}): Doc<"tickets"> {
  const ticket = {
    _id: "ticket_1" as Id<"tickets">,
    _creationTime: FIXED_NOW - 10_000,
    tenantId: "tenant-1",
    reference: 42_100,
    subject: "Computador não liga",
    summary: undefined,
    status: "RESOLVED",
    priority: "MEDIUM",
    channel: "EMAIL",
    queueId: undefined,
    requesterId: "user_requester" as Id<"users">,
    requesterSnapshot: { name: "Cliente", email: "cliente@example.com" },
    assigneeId: undefined,
    assigneeSnapshot: undefined,
    companyId: undefined,
    companySnapshot: undefined,
    machineId: undefined,
    machineSnapshot: undefined,
    slaPolicyId: undefined,
    dueAt: undefined,
    firstResponseAt: undefined,
    resolvedAt: FIXED_NOW - 1000,
    createdAt: FIXED_NOW - 20_000,
    updatedAt: FIXED_NOW - 100,
    tags: [],
    customFields: [],
    activeSessionId: undefined,
    totalWorkedMs: 0,
    internalWorkedMs: 0,
    externalWorkedMs: 0,
    csatScore: undefined,
    csatMaxScore: undefined,
    csatComment: undefined,
    csatRatedAt: undefined,
    csatRatedBy: undefined,
  } satisfies Partial<Doc<"tickets">>
  return { ...(ticket as Doc<"tickets">), ...overrides }
}

function createCtx(ticket: Doc<"tickets">, events: Doc<"ticketEvents">[] = []) {
  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 patch = vi.fn(async () => undefined)
  const insert = vi.fn(async () => undefined)
  const deleteFn = vi.fn(async () => undefined)

  const ctx: SubmitCsatCtx = {
    db: {
      get: vi.fn(async (id: Id<"tickets"> | Id<"users">) => {
        if (id === ticket._id) return ticket
        return null
      }),
      patch,
      query,
      delete: deleteFn,
      insert,
    },
  } as unknown as SubmitCsatCtx

  return { ctx, patch, insert, deleteFn, query }
}

describe("convex.tickets.submitCsat", () => {
  afterEach(() => {
    vi.clearAllMocks()
  })

  it("permite que o solicitante avalie um chamado resolvido", async () => {
    const ticket = makeTicket()
    const { ctx, patch, insert, deleteFn } = createCtx(ticket)
    mockedRequireUser.mockResolvedValue({
      user: {
        _id: "user_requester" as Id<"users">,
        _creationTime: FIXED_NOW - 30_000,
        tenantId: "tenant-1",
        name: "Cliente",
        email: "cliente@example.com",
        role: "COLLABORATOR",
        companyId: undefined,
        teams: [],
      },
      role: "COLLABORATOR",
    })

    const result = await submitCsatHandler(ctx, {
      ticketId: ticket._id,
      actorId: "user_requester" as Id<"users">,
      score: 4,
      maxScore: 5,
      comment: "Atendimento excelente!",
    })

    expect(result.score).toBe(4)
    expect(result.comment).toBe("Atendimento excelente!")
    expect(patch).toHaveBeenCalledWith(
      ticket._id,
      expect.objectContaining({
        csatScore: 4,
        csatMaxScore: 5,
        csatComment: "Atendimento excelente!",
        csatRatedBy: "user_requester",
      })
    )
    expect(insert).toHaveBeenCalledWith(
      "ticketEvents",
      expect.objectContaining({
        ticketId: ticket._id,
        type: "CSAT_RATED",
        payload: expect.objectContaining({ score: 4, maxScore: 5, comment: "Atendimento excelente!" }),
      })
    )
    expect(deleteFn).not.toHaveBeenCalled()
  })

  it("bloqueia avaliações antes do encerramento do chamado para colaboradores", async () => {
    const ticket = makeTicket({ status: "PENDING" })
    const { ctx, patch, insert } = createCtx(ticket)
    mockedRequireUser.mockResolvedValue({
      user: {
        _id: "user_requester" as Id<"users">,
        _creationTime: FIXED_NOW - 30_000,
        tenantId: "tenant-1",
        name: "Cliente",
        email: "cliente@example.com",
        role: "COLLABORATOR",
        companyId: undefined,
        teams: [],
      },
      role: "COLLABORATOR",
    })

    await expect(
      submitCsatHandler(ctx, {
        ticketId: ticket._id,
        actorId: "user_requester" as Id<"users">,
        score: 5,
        comment: "Perfeito",
      })
    ).rejects.toThrow("Avaliações só são permitidas após o encerramento do chamado")
    expect(patch).not.toHaveBeenCalled()
    expect(insert).not.toHaveBeenCalled()
  })

  it("não permite registrar uma segunda avaliação", async () => {
    const ticket = makeTicket({
      csatScore: 4,
      csatMaxScore: 5,
      csatRatedAt: FIXED_NOW - 2000,
      csatRatedBy: "user_requester" as Id<"users">,
    })
    const { ctx, patch, insert, deleteFn } = createCtx(ticket)
    mockedRequireUser.mockResolvedValue({
      user: {
        _id: "user_requester" as Id<"users">,
        _creationTime: FIXED_NOW - 30_000,
        tenantId: "tenant-1",
        name: "Cliente",
        email: "cliente@example.com",
        role: "COLLABORATOR",
        companyId: undefined,
        teams: [],
      },
      role: "COLLABORATOR",
    })

    await expect(
      submitCsatHandler(ctx, {
        ticketId: ticket._id,
        actorId: "user_requester" as Id<"users">,
        score: 5,
        comment: "Vou tentar atualizar",
      })
    ).rejects.toThrow("Este chamado já possui uma avaliação registrada")
    expect(patch).not.toHaveBeenCalled()
    expect(insert).not.toHaveBeenCalled()
    expect(deleteFn).not.toHaveBeenCalled()
  })

  it("impede que administradores registrem avaliação", async () => {
    const ticket = makeTicket()
    const { ctx, patch, insert } = createCtx(ticket)
    mockedRequireUser.mockResolvedValue({
      user: {
        _id: "user_admin" as Id<"users">,
        _creationTime: FIXED_NOW - 40_000,
        tenantId: "tenant-1",
        name: "Administrador",
        email: "admin@example.com",
        role: "ADMIN",
        companyId: undefined,
        teams: [],
      },
      role: "ADMIN",
    })

    await expect(
      submitCsatHandler(ctx, {
        ticketId: ticket._id,
        actorId: "user_admin" as Id<"users">,
        score: 4,
        comment: "somente testes",
      })
    ).rejects.toThrow("Somente o solicitante pode avaliar o chamado")
    expect(patch).not.toHaveBeenCalled()
    expect(insert).not.toHaveBeenCalled()
  })
})
