import { render, screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { describe, it, expect, vi, beforeEach } from "vitest"
import { AuthProvider, useAuth } from "./AuthContext"
import { api, UnauthorizedError } from "@/api/client"

const USER = { id: "u1", email: "a@b.com" }
const PROJECT = { id: "p1", name: "Default" }

function Probe() {
  const { user, project, loading, login, register, logout } = useAuth()
  return (
    <div>
      <span data-testid="loading">{String(loading)}</span>
      <span data-testid="user">{user ? user.email : "none"}</span>
      <span data-testid="project">{project ? project.name : "none"}</span>
      <button onClick={() => login("a@b.com", "secret")}>login</button>
      <button onClick={() => register("a@b.com", "secret")}>register</button>
      <button onClick={() => logout()}>logout</button>
    </div>
  )
}

function renderProbe() {
  return render(
    <AuthProvider>
      <Probe />
    </AuthProvider>,
  )
}

beforeEach(() => {
  vi.restoreAllMocks()
})

describe("AuthContext", () => {
  it("populates user/project from api.auth.me() on mount", async () => {
    vi.spyOn(api.auth, "me").mockResolvedValue({ user: USER, project: PROJECT })
    renderProbe()

    expect(screen.getByTestId("loading").textContent).toBe("true")

    await waitFor(() => expect(screen.getByTestId("loading").textContent).toBe("false"))
    expect(screen.getByTestId("user").textContent).toBe(USER.email)
    expect(screen.getByTestId("project").textContent).toBe(PROJECT.name)
  })

  it("treats 401 from api.auth.me() as unauthenticated, not an error", async () => {
    vi.spyOn(api.auth, "me").mockRejectedValue(new UnauthorizedError())
    renderProbe()

    await waitFor(() => expect(screen.getByTestId("loading").textContent).toBe("false"))
    expect(screen.getByTestId("user").textContent).toBe("none")
    expect(screen.getByTestId("project").textContent).toBe("none")
  })

  it("login sets user/project in context", async () => {
    vi.spyOn(api.auth, "me").mockRejectedValue(new UnauthorizedError())
    vi.spyOn(api.auth, "login").mockResolvedValue({ user: USER, project: PROJECT })
    const user = userEvent.setup()
    renderProbe()

    await waitFor(() => expect(screen.getByTestId("loading").textContent).toBe("false"))
    await user.click(screen.getByRole("button", { name: "login" }))

    await waitFor(() => expect(screen.getByTestId("user").textContent).toBe(USER.email))
    expect(screen.getByTestId("project").textContent).toBe(PROJECT.name)
  })

  it("treats a non-401 error from api.auth.me() as logged-out but logs it for diagnostics", async () => {
    const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
    const err = new Error("network down")
    vi.spyOn(api.auth, "me").mockRejectedValue(err)
    renderProbe()

    await waitFor(() => expect(screen.getByTestId("loading").textContent).toBe("false"))
    expect(screen.getByTestId("user").textContent).toBe("none")
    expect(screen.getByTestId("project").textContent).toBe("none")
    expect(consoleErrorSpy).toHaveBeenCalledWith(err)
  })

  it("logout clears user/project from context", async () => {
    vi.spyOn(api.auth, "me").mockResolvedValue({ user: USER, project: PROJECT })
    vi.spyOn(api.auth, "logout").mockResolvedValue(undefined)
    const user = userEvent.setup()
    renderProbe()

    await waitFor(() => expect(screen.getByTestId("user").textContent).toBe(USER.email))
    await user.click(screen.getByRole("button", { name: "logout" }))

    await waitFor(() => expect(screen.getByTestId("user").textContent).toBe("none"))
    expect(screen.getByTestId("project").textContent).toBe("none")
  })
})
