import { render, screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { DiffView } from "./DiffView"
import type { ChangesetResponse } from "@/api/types"

const cs: ChangesetResponse = {
  updates: [{ key: "A www.example.com.", kind: "update", type: "A", name: "www.example.com.", desired: ["1.1.1.1"], actual: ["9.9.9.9"], readOnly: false, custom: false }],
  prunes: [{ key: "A old.example.com.", kind: "delete", type: "A", name: "old.example.com.", actual: ["2.2.2.2"], readOnly: false, custom: false }],
  customs: [],
  readOnly: [{ key: "NS example.com.", kind: "update", type: "NS", name: "example.com.", desired: ["ns1."], actual: ["ns2."], readOnly: true, custom: false }],
  inSyncCount: 3,
}

function noop() { /* unused in most tests */ }

function renderDiff(overrides: Partial<Parameters<typeof DiffView>[0]> = {}) {
  return render(
    <DiffView
      changeset={cs}
      selectedUpdates={new Set(["A www.example.com."])}
      selectedPrunes={new Set()}
      onToggleUpdate={noop}
      onTogglePrune={noop}
      onToggleAllUpdates={noop}
      onToggleAllPrunes={noop}
      {...overrides}
    />,
  )
}

test("renders all sections with counts", () => {
  renderDiff()
  expect(screen.getByText(/www\.example\.com\./)).toBeInTheDocument()
  expect(screen.getByText(/old\.example\.com\./)).toBeInTheDocument()
  // Anchored (vs. the brief's bare /example\.com\./) — "www.example.com." and
  // "old.example.com." both contain "example.com." as a trailing substring,
  // so the unanchored pattern matches all three rows and getByText throws
  // "multiple elements found". Anchoring targets the read-only apex record
  // specifically, which is what this assertion is actually verifying.
  expect(screen.getByText(/^example\.com\.$/)).toBeInTheDocument()
  expect(screen.getByText(/3/)).toBeInTheDocument() // in-sync count
})

test("marks read-only records", () => {
  renderDiff()
  expect(screen.getByText(/NS/)).toBeInTheDocument()
})

test("renders a very long unbreakable value (DKIM key) without crashing", () => {
  // Real DKIM records ship a ~400-char unbroken p= blob. This must not
  // throw and the value must land in the DOM (wrapping itself is a CSS
  // concern verified manually, not via jsdom layout).
  const longValue = "v=DKIM1; k=rsa; p=" + "A".repeat(400)
  const csWithDkim: ChangesetResponse = {
    updates: [
      {
        key: "TXT default._domainkey.example.com.",
        kind: "update",
        type: "TXT",
        name: "default._domainkey.example.com.",
        desired: [longValue],
        actual: [],
        readOnly: false,
        custom: false,
      },
    ],
    prunes: [],
    customs: [],
    readOnly: [],
    inSyncCount: 0,
  }
  renderDiff({ changeset: csWithDkim, selectedUpdates: new Set() })
  expect(screen.getByText(new RegExp(longValue))).toBeInTheDocument()
})

test("does not crash when changeset fields are null", () => {
  // An empty changeset from an older/edge backend can arrive with null slices
  // instead of []. DiffView must normalise them, not blow up on .length/.map.
  const nullish = {
    updates: null,
    prunes: null,
    readOnly: null,
    inSyncCount: 5,
  } as unknown as ChangesetResponse
  renderDiff({ changeset: nullish, selectedUpdates: new Set() })
  expect(screen.getByText(/5/)).toBeInTheDocument()
  expect(screen.getByText(/in sync/)).toBeInTheDocument()
})

test("renders a checkbox for update and prune rows but not for read-only rows", () => {
  renderDiff()
  // 2 select-all (update + prune headers) + 2 row checkboxes (one update, one prune).
  // Read-only section contributes none: no select-all, no row checkbox.
  const checkboxes = screen.getAllByRole("checkbox")
  expect(checkboxes).toHaveLength(4)

  const updateRowCheckbox = screen.getByRole("checkbox", { name: /www\.example\.com\./ })
  expect(updateRowCheckbox).toBeInTheDocument()
  const pruneRowCheckbox = screen.getByRole("checkbox", { name: /old\.example\.com\./ })
  expect(pruneRowCheckbox).toBeInTheDocument()

  expect(screen.queryByRole("checkbox", { name: /example\.com\..*NS|NS.*example\.com\./ })).not.toBeInTheDocument()
})

test("clicking an update row checkbox calls onToggleUpdate with the record key", async () => {
  const onToggleUpdate = vi.fn()
  const user = userEvent.setup()
  renderDiff({ onToggleUpdate })

  await user.click(screen.getByRole("checkbox", { name: /www\.example\.com\./ }))
  expect(onToggleUpdate).toHaveBeenCalledWith("A www.example.com.")
})

test("clicking a prune row checkbox calls onTogglePrune with the record key", async () => {
  const onTogglePrune = vi.fn()
  const user = userEvent.setup()
  renderDiff({ onTogglePrune })

  await user.click(screen.getByRole("checkbox", { name: /old\.example\.com\./ }))
  expect(onTogglePrune).toHaveBeenCalledWith("A old.example.com.")
})

test("select-all header checkbox is checked when all rows in the section are selected", () => {
  renderDiff({ selectedUpdates: new Set(["A www.example.com."]) })
  const selectAll = screen.getByRole("checkbox", { name: /выбрать все.*updates/i })
  expect(selectAll).toHaveAttribute("aria-checked", "true")
})

test("select-all header checkbox calls onToggleAllUpdates(true) when clicked while none selected", async () => {
  const onToggleAllUpdates = vi.fn()
  const user = userEvent.setup()
  renderDiff({ selectedUpdates: new Set(), onToggleAllUpdates })

  await user.click(screen.getByRole("checkbox", { name: /выбрать все.*updates/i }))
  expect(onToggleAllUpdates).toHaveBeenCalledWith(true)
})

test("select-all header checkbox is indeterminate when only some update rows are selected", () => {
  const csWithMultipleUpdates: ChangesetResponse = {
    updates: [
      { key: "A www.example.com.", kind: "update", type: "A", name: "www.example.com.", desired: ["1.1.1.1"], actual: ["9.9.9.9"], readOnly: false, custom: false },
      { key: "A api.example.com.", kind: "update", type: "A", name: "api.example.com.", desired: ["1.1.1.2"], actual: ["9.9.9.8"], readOnly: false, custom: false },
      { key: "A cdn.example.com.", kind: "update", type: "A", name: "cdn.example.com.", desired: ["1.1.1.3"], actual: ["9.9.9.7"], readOnly: false, custom: false },
    ],
    prunes: [],
    customs: [],
    readOnly: [],
    inSyncCount: 0,
  }
  renderDiff({
    changeset: csWithMultipleUpdates,
    // Partial selection: one of three keys — neither all nor none — is what
    // must drive the header checkbox into the indeterminate ("mixed") state.
    selectedUpdates: new Set(["A www.example.com."]),
  })

  const selectAll = screen.getByRole("checkbox", { name: /выбрать все.*updates/i })
  expect(selectAll).toHaveAttribute("aria-checked", "mixed")
})
