import { render, screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { MemoryRouter, Routes, Route } from "react-router-dom"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { DomainDiffPage } from "./DomainDiffPage"
import { AuthProvider } from "@/auth/AuthContext"
import { api } from "@/api/client"
import { vi, beforeEach, test, expect } from "vitest"
import type { Domain } from "@/api/types"

const PROJECT_ID = "p1"

const domainWithTemplate: Domain = {
  id: "d1",
  providerAccountId: "acc1",
  zoneName: "example.com.",
  zoneId: "z1",
  templateId: "t1",
  lastCheckStatus: "drift",
}

function renderPage(qc: QueryClient = new QueryClient()) {
  return render(
    <QueryClientProvider client={qc}>
      <AuthProvider>
        <MemoryRouter initialEntries={["/domains/d1"]}>
          <Routes><Route path="/domains/:id" element={<DomainDiffPage />} /></Routes>
        </MemoryRouter>
      </AuthProvider>
    </QueryClientProvider>,
  )
}

beforeEach(() => {
  vi.restoreAllMocks()
  vi.spyOn(api.auth, "me").mockResolvedValue({
    user: { id: "u1", email: "a@b.com" },
    project: { id: PROJECT_ID, name: "Default" },
  })
  vi.spyOn(api, "domainHistory").mockResolvedValue([])
  vi.spyOn(api, "listDomains").mockResolvedValue([domainWithTemplate])
})

test("apply sends applyPrunes=false by default, true only after opting in", async () => {
  vi.spyOn(api, "checkDomain").mockResolvedValue({
    updates: [{ kind: "update", type: "A", name: "a.", desired: ["1"], actual: ["2"], readOnly: false }],
    prunes: [{ kind: "delete", type: "A", name: "b.", actual: ["3"], readOnly: false }],
    readOnly: [], inSyncCount: 0,
  })
  const applySpy = vi.spyOn(api, "applyDomain").mockResolvedValue({ updates: [], prunes: [], readOnly: [], inSyncCount: 0 })
  const zoneRecordsSpy = vi.spyOn(api, "zoneRecords")
  const user = userEvent.setup()
  renderPage()

  const applyBtn = await screen.findByRole("button", { name: /apply/i })
  await user.click(applyBtn)
  await waitFor(() => expect(applySpy).toHaveBeenCalled())
  expect(applySpy.mock.calls[0]).toEqual([PROJECT_ID, "d1", { applyUpdates: true, applyPrunes: false }])

  // включить prune и применить снова
  const pruneToggle = screen.getByRole("checkbox", { name: /prune|удал/i })
  await user.click(pruneToggle)
  await user.click(screen.getByRole("button", { name: /apply/i }))
  await waitFor(() => expect(applySpy).toHaveBeenCalledTimes(2))
  expect(applySpy.mock.calls[1]).toEqual([PROJECT_ID, "d1", { applyUpdates: true, applyPrunes: true }])

  // домен с шаблоном: записи зоны не нужны для диффа — запрос не должен уходить к провайдеру
  expect(zoneRecordsSpy).not.toHaveBeenCalled()
})

test("пока список доменов грузится — показан общий лоадер, а не баннер об отсутствии шаблона", async () => {
  let resolveListDomains: (domains: Domain[]) => void
  vi.spyOn(api, "listDomains").mockReturnValue(
    new Promise((resolve) => {
      resolveListDomains = resolve
    }),
  )
  const checkSpy = vi.spyOn(api, "checkDomain").mockResolvedValue({
    updates: [], prunes: [], readOnly: [], inSyncCount: 0,
  })
  const zoneRecordsSpy = vi.spyOn(api, "zoneRecords")
  renderPage()

  expect(await screen.findByText(/загрузка/i)).toBeInTheDocument()
  expect(screen.queryByText(/шаблон не привязан/i)).not.toBeInTheDocument()
  expect(checkSpy).not.toHaveBeenCalled()
  expect(zoneRecordsSpy).not.toHaveBeenCalled()

  resolveListDomains!([domainWithTemplate])

  expect(await screen.findByRole("button", { name: /apply/i })).toBeInTheDocument()
})

test("домен без шаблона показывает записи зоны и не вызывает check", async () => {
  vi.spyOn(api, "listDomains").mockResolvedValue([
    { id: "d1", providerAccountId: "acc1", zoneName: "example.com.", zoneId: "z1", templateId: null },
  ])
  const checkSpy = vi.spyOn(api, "checkDomain")
  vi.spyOn(api, "zoneRecords").mockResolvedValue([
    { type: "A", name: "example.com.", ttl: 3600, values: ["1.2.3.4"] },
  ])
  renderPage()

  expect(await screen.findByText(/шаблон не привязан/i)).toBeInTheDocument()
  expect(screen.getByRole("button", { name: /создать шаблон из этой зоны/i })).toBeInTheDocument()
  expect(await screen.findByText("example.com.")).toBeInTheDocument()
  expect(screen.getByText("1.2.3.4")).toBeInTheDocument()

  expect(screen.queryByText(/вычисляю дифф/i)).not.toBeInTheDocument()
  expect(checkSpy).not.toHaveBeenCalled()
})

test("ошибка загрузки списка доменов показывает баннер ошибки и не уходит в ветку без шаблона", async () => {
  vi.spyOn(api, "listDomains").mockRejectedValue(new Error("network down"))
  const checkSpy = vi.spyOn(api, "checkDomain")
  const zoneRecordsSpy = vi.spyOn(api, "zoneRecords")
  // retry:false — иначе react-query переретраит listDomains с экспоненциальной
  // задержкой и findByText не успевает дождаться финального isError.
  renderPage(new QueryClient({ defaultOptions: { queries: { retry: false } } }))

  expect(await screen.findByText(/не удалось загрузить список доменов/i)).toBeInTheDocument()
  expect(screen.getByText("network down")).toBeInTheDocument()

  expect(screen.queryByText(/шаблон не привязан/i)).not.toBeInTheDocument()
  expect(checkSpy).not.toHaveBeenCalled()
  expect(zoneRecordsSpy).not.toHaveBeenCalled()
})

test("создание шаблона из зоны вызывает templateFromZone", async () => {
  vi.spyOn(api, "listDomains").mockResolvedValue([
    { id: "d1", providerAccountId: "acc1", zoneName: "example.com.", zoneId: "z1", templateId: null },
  ])
  vi.spyOn(api, "zoneRecords").mockResolvedValue([])
  const templateFromZoneSpy = vi.spyOn(api, "templateFromZone").mockResolvedValue({
    id: "t9", name: "example.com. snapshot", records: [], version: 1,
  })
  const user = userEvent.setup()
  renderPage()

  const createBtn = await screen.findByRole("button", { name: /создать шаблон из этой зоны/i })
  await user.click(createBtn)

  await waitFor(() => expect(templateFromZoneSpy).toHaveBeenCalledWith(PROJECT_ID, "d1"))
})
