import { afterEach, describe, expect, it } from "bun:test"
import { startCallbackServer, type CallbackServer } from "./callback-server"

const nativeFetch = Bun.fetch.bind(Bun)

describe("startCallbackServer", () => {
  let server: CallbackServer | null = null

  afterEach(async () => {
    server?.close()
    server = null
    // Allow time for port to be released before next test
    await Bun.sleep(10)
  })

  it("starts server and returns port", async () => {
    // given - no preconditions

    // when
    server = await startCallbackServer()

    // then
    expect(server.port).toBeGreaterThanOrEqual(19877)
    expect(typeof server.waitForCallback).toBe("function")
    expect(typeof server.close).toBe("function")
  })

  it("resolves callback with code and state from query params", async () => {
    // given
    server = await startCallbackServer()
    const callbackUrl = `http://127.0.0.1:${server.port}/oauth/callback?code=test-code&state=test-state`

    // when
    // Use Promise.all to ensure fetch and waitForCallback run concurrently
    // This prevents race condition where waitForCallback blocks before fetch starts
    const [result, response] = await Promise.all([
      server.waitForCallback(),
      nativeFetch(callbackUrl)
    ])

    // then
    expect(result).toEqual({ code: "test-code", state: "test-state" })
    expect(response.status).toBe(200)
    const html = await response.text()
    expect(html).toContain("Authorization successful")
  })

  it("returns 404 for non-callback routes", async () => {
    // given
    server = await startCallbackServer()

    // when
    const response = await nativeFetch(`http://127.0.0.1:${server.port}/other`)

    // then
    expect(response.status).toBe(404)
  })

  it("returns 400 and rejects when code is missing", async () => {
    // given
    server = await startCallbackServer()
    const callbackRejection = server.waitForCallback().catch((e: Error) => e)

    // when
    const response = await nativeFetch(`http://127.0.0.1:${server.port}/oauth/callback?state=s`)

    // then
    expect(response.status).toBe(400)
    const error = await callbackRejection
    expect(error).toBeInstanceOf(Error)
    expect((error as Error).message).toContain("missing code or state")
  })

  it("returns 400 and rejects when state is missing", async () => {
    // given
    server = await startCallbackServer()
    const callbackRejection = server.waitForCallback().catch((e: Error) => e)

    // when
    const response = await nativeFetch(`http://127.0.0.1:${server.port}/oauth/callback?code=c`)

    // then
    expect(response.status).toBe(400)
    const error = await callbackRejection
    expect(error).toBeInstanceOf(Error)
    expect((error as Error).message).toContain("missing code or state")
  })

  it("close stops the server immediately", async () => {
    // given
    server = await startCallbackServer()
    const port = server.port

    // when
    server.close()
    server = null

    // then
    try {
      await nativeFetch(`http://127.0.0.1:${port}/oauth/callback?code=c&state=s`)
      expect(true).toBe(false)
    } catch (error) {
      expect(error).toBeDefined()
    }
  })
})
