import { APIRequestContext, request } from "@playwright/test"
import { ADMIN_TOKEN, API_BASE } from "../../fixtures/test-constants"

let adminContext: APIRequestContext | null = null

type CollectionEntry = {
    id?: string
    _id?: string | { $oid?: string }
    [key: string]: unknown
}

type CollectionQueryOptions = {
    filter?: Record<string, unknown>
    sort?: string
    limit?: number
    offset?: number
    projection?: string
    lookup?: string
}

function getEntryId(entry: CollectionEntry): string | undefined {
    if (typeof entry.id === "string") return entry.id
    if (typeof entry._id === "string") return entry._id
    if (entry._id && typeof entry._id === "object" && typeof entry._id.$oid === "string") return entry._id.$oid
    return undefined
}

function buildCollectionUrl(collection: string, options: CollectionQueryOptions = {}, id?: string): string {
    const searchParams = new URLSearchParams()

    if (options.filter) searchParams.set("filter", JSON.stringify(options.filter))
    if (options.sort) searchParams.set("sort", options.sort)
    if (typeof options.limit === "number") searchParams.set("limit", String(options.limit))
    if (typeof options.offset === "number") searchParams.set("offset", String(options.offset))
    if (options.projection) searchParams.set("projection", options.projection)
    if (options.lookup) searchParams.set("lookup", options.lookup)

    const path = `${API_BASE}/${collection}${id ? `/${id}` : ""}`
    const query = searchParams.toString()
    return query ? `${path}?${query}` : path
}

async function parseJsonResponse<T>(
    res: Awaited<ReturnType<APIRequestContext["get"]>>,
    contextLabel: string
): Promise<T> {
    const contentType = res.headers()["content-type"] || ""
    if (contentType.includes("text/html")) {
        throw new Error(`${contextLabel} returned HTML instead of JSON. Check CODING_URL or API proxy setup.`)
    }

    return (await res.json()) as T
}

/**
 * Get or create a singleton admin API context with the ADMIN_TOKEN.
 */
export async function getAdminContext(baseURL: string): Promise<APIRequestContext> {
    if (ADMIN_TOKEN === "CHANGE_ME") {
        throw new Error("ADMIN_TOKEN is not configured for Playwright seed and cleanup")
    }

    if (!adminContext) {
        adminContext = await request.newContext({
            baseURL,
            ignoreHTTPSErrors: true,
            extraHTTPHeaders: {
                Token: ADMIN_TOKEN,
            },
        })
    }
    return adminContext
}

export async function listCollectionEntries<T extends CollectionEntry = CollectionEntry>(
    baseURL: string,
    collection: string,
    options: CollectionQueryOptions = {}
): Promise<T[]> {
    const ctx = await getAdminContext(baseURL)
    const res = await ctx.get(buildCollectionUrl(collection, options))
    if (!res.ok()) {
        throw new Error(`Failed to list ${collection}: ${res.status()} ${res.statusText()}`)
    }

    const body = await parseJsonResponse<unknown>(res, `Listing ${collection}`)
    if (Array.isArray(body)) return body as T[]
    if (body && typeof body === "object" && "data" in body && Array.isArray(body.data)) return body.data as T[]
    return []
}

export async function createCollectionEntry<T extends CollectionEntry = CollectionEntry>(
    baseURL: string,
    collection: string,
    entry: Record<string, unknown>
): Promise<T> {
    const ctx = await getAdminContext(baseURL)
    const res = await ctx.post(`${API_BASE}/${collection}`, { data: entry })
    if (!res.ok()) {
        throw new Error(`Failed to create ${collection}: ${res.status()} ${res.statusText()}`)
    }

    return await parseJsonResponse<T>(res, `Creating ${collection}`)
}

export async function updateCollectionEntry<T extends CollectionEntry = CollectionEntry>(
    baseURL: string,
    collection: string,
    id: string,
    entry: Record<string, unknown>
): Promise<T> {
    const ctx = await getAdminContext(baseURL)
    const res = await ctx.put(`${API_BASE}/${collection}/${id}`, { data: entry })
    if (!res.ok()) {
        throw new Error(`Failed to update ${collection}/${id}: ${res.status()} ${res.statusText()}`)
    }

    return await parseJsonResponse<T>(res, `Updating ${collection}/${id}`)
}

export async function deleteCollectionEntry(baseURL: string, collection: string, id: string): Promise<boolean> {
    const ctx = await getAdminContext(baseURL)
    const res = await ctx.delete(`${API_BASE}/${collection}/${id}`)
    return res.ok()
}

export async function findFirstEntry<T extends CollectionEntry = CollectionEntry>(
    baseURL: string,
    collection: string,
    options: CollectionQueryOptions = {}
): Promise<T | null> {
    const entries = await listCollectionEntries<T>(baseURL, collection, { ...options, limit: 1 })
    return entries[0] || null
}

export async function upsertCollectionEntry<T extends CollectionEntry = CollectionEntry>(
    baseURL: string,
    collection: string,
    filter: Record<string, unknown>,
    entry: Record<string, unknown>
): Promise<T> {
    const existing = await findFirstEntry<T>(baseURL, collection, { filter })
    const existingId = existing ? getEntryId(existing) : undefined

    if (existingId) {
        return updateCollectionEntry<T>(baseURL, collection, existingId, entry)
    }

    return createCollectionEntry<T>(baseURL, collection, entry)
}

export async function cleanupAllTestData(baseURL: string): Promise<{ users: number }> {
    void baseURL
    return { users: 0 }
}

/**
 * Dispose the admin API context. Call in globalTeardown.
 */
export async function disposeAdminApi(): Promise<void> {
    if (adminContext) {
        await adminContext.dispose()
        adminContext = null
    }
}
