import Foundation
import Testing
@testable import CodexBarCore

struct T3ChatUsageFetcherTests {
    private struct StubClaudeFetcher: ClaudeUsageFetching {
        func loadLatestUsage(model _: String) async throws -> ClaudeUsageSnapshot {
            throw ClaudeUsageError.parseFailed("stub")
        }

        func debugRawProbe(model _: String) async -> String {
            "stub"
        }

        func detectVersion() -> String? {
            nil
        }
    }

    private static let now = Date(timeIntervalSince1970: 1_778_000_000)
    // 2026-05-21T12:23:36Z, the usage-window reset that must not drive overage reset display.
    private static let billingNextResetMilliseconds = 1_779_366_216_920
    // 2026-06-06T16:23:29Z, the subscription period end used for overage reset display.
    private static let subscriptionPeriodEndSeconds = 1_780_763_009
    private static let subscriptionPeriodEndMilliseconds = Self.subscriptionPeriodEndSeconds * 1000

    private static let sampleResponse = [
        #"{"json":{"0":[[0],[null,0,0]]}}"#,
        #"{"json":[0,0,[[{"result":0}],["result",0,1]]]}"#,
        #"{"json":[1,0,[[{"data":0}],["data",0,2]]]}"#,
        #"{"json":[2,0,[[{"subTier":"pro","subscription":{"# +
            #""productId":"pro","productName":"pro","status":"active","# +
            #""currentPeriodStart":1778084609000,"currentPeriodEnd":1780763009000,"# +
            #""canceledAt":null,"trialEndsAt":null},"lifetimeBalance":0,"usageBand":"max","# +
            #""billingNextResetAt":1779366216920,"usageFourHourPercentage":12.5,"# +
            #""usageMonthPercentage":34.25,"usageFourHourNextResetAt":1779366216920,"# +
            #""usagePeriodPercentage":44,"usageWindowNextResetAt":1779366216920}]]]}"#,
    ].joined(separator: "\n")

    @Test
    func `parses customer data from json lines response`() throws {
        let snapshot = try T3ChatUsageParser.parseJSONLines(Self.sampleResponse, now: Self.now)

        #expect(snapshot.customerData.subTier == "pro")
        #expect(snapshot.customerData.usageBand == "max")
        #expect(snapshot.customerData.usageFourHourPercentage == 12.5)
        #expect(snapshot.customerData.usageMonthPercentage == 34.25)
        #expect(snapshot.customerData.subscription?.status == "active")
    }

    @Test
    func `maps customer data to base and overage windows`() throws {
        let usage = try T3ChatUsageParser.parseJSONLines(Self.sampleResponse, now: Self.now)
            .toUsageSnapshot()

        #expect(usage.primary?.usedPercent == 12.5)
        #expect(usage.primary?.windowMinutes == 240)
        #expect(usage.primary?.resetDescription == "Base - max")
        #expect(usage.secondary?.usedPercent == 34.25)
        #expect(usage.secondary?.resetDescription == "Overage")
        #expect(usage.secondary?.resetsAt.map { Int($0.timeIntervalSince1970) } == Self.subscriptionPeriodEndSeconds)
        #expect(usage.identity?.providerID == .t3chat)
        #expect(usage.identity?.loginMethod == "Pro")
    }

    @Test
    func `falls back to usage period percentage when month percentage is absent`() throws {
        let response = """
        {"json":[2,0,[[{"subTier":"free","usageFourHourPercentage":5,"usagePeriodPercentage":65}]]]}
        """
        let usage = try T3ChatUsageParser.parseJSONLines(response, now: Self.now)
            .toUsageSnapshot()

        #expect(usage.primary?.usedPercent == 5)
        #expect(usage.secondary?.usedPercent == 65)
    }

    @Test
    func `overage reset ignores billing next reset`() throws {
        let response = Self.customerDataResponse(
            #"{"usageMonthPercentage":20,"billingNextResetAt":\#(Self.billingNextResetMilliseconds)}"#)
        let usage = try T3ChatUsageParser.parseJSONLines(response, now: Self.now)
            .toUsageSnapshot()

        #expect(usage.secondary?.usedPercent == 20)
        #expect(usage.secondary?.resetsAt == nil)
    }

    @Test
    func `overage reset uses subscription current period end`() throws {
        let currentPeriodEnd = Self.subscriptionPeriodEndMilliseconds
        let response = Self.customerDataResponse(
            #"{"usageMonthPercentage":20,"subscription":{"currentPeriodEnd":\#(currentPeriodEnd)}}"#)
        let usage = try T3ChatUsageParser.parseJSONLines(response, now: Self.now)
            .toUsageSnapshot()

        #expect(usage.secondary?.usedPercent == 20)
        #expect(usage.secondary?.resetsAt.map { Int($0.timeIntervalSince1970) } == Self.subscriptionPeriodEndSeconds)
    }

    @Test
    func `fetch sends trpc headers and cookie`() async throws {
        let stub = ProviderHTTPTransportStub { request in
            #expect(request.url?.host == "t3.chat")
            #expect(request.url?.path == "/api/trpc/getCustomerData")
            #expect(request.value(forHTTPHeaderField: "Cookie") == "session=abc")
            #expect(request.value(forHTTPHeaderField: "trpc-accept") == "application/jsonl")
            #expect(request.value(forHTTPHeaderField: "x-trpc-source") == "web-client")
            #expect(request.value(forHTTPHeaderField: "Sec-Fetch-Site") == "same-origin")
            #expect(request.url?.query?.contains("batch=1") == true)
            let response = HTTPURLResponse(
                url: request.url!,
                statusCode: 200,
                httpVersion: nil,
                headerFields: nil)!
            return (Data(Self.sampleResponse.utf8), response)
        }

        let snapshot = try await T3ChatUsageFetcher.fetchCustomerData(
            cookieHeader: "session=abc",
            now: Self.now,
            transport: stub)

        #expect(snapshot.customerData.planName == "Pro")
    }

    @Test
    func `full curl capture forwards browser fingerprint headers`() async throws {
        let curl = """
        curl 'https://t3.chat/api/trpc/getCustomerData?batch=1&input=ignored' \\
          -H 'User-Agent: Mozilla/5.0 Firefox/151.0' \\
          --header "Referer: https://t3.chat/settings/customization" \\
          -H 'trpc-accept: application/jsonl' \\
          -H 'x-trpc-source: web-client' \\
          -H 'x-trpc-batch: true' \\
          -H 'X-Deployment-Id: dpl_test' \\
          -H 'x-client-context: eyJjbGllbnQiOnsidmVyc2lvbiI6IjEuMTIuNCJ9fQ==' \\
          -H 'Cookie: session=abc'
        """
        let stub = ProviderHTTPTransportStub { request in
            #expect(request.value(forHTTPHeaderField: "Cookie") == "session=abc")
            #expect(request.value(forHTTPHeaderField: "User-Agent") == "Mozilla/5.0 Firefox/151.0")
            #expect(request.value(forHTTPHeaderField: "Referer") == "https://t3.chat/settings/customization")
            #expect(request.value(forHTTPHeaderField: "X-Deployment-Id") == "dpl_test")
            #expect(request.value(forHTTPHeaderField: "x-client-context") ==
                "eyJjbGllbnQiOnsidmVyc2lvbiI6IjEuMTIuNCJ9fQ==")
            let response = HTTPURLResponse(
                url: request.url!,
                statusCode: 200,
                httpVersion: nil,
                headerFields: nil)!
            return (Data(Self.sampleResponse.utf8), response)
        }

        let fetcher = T3ChatUsageFetcher(browserDetection: BrowserDetection(cacheTTL: 0))
        _ = try await fetcher.fetch(
            cookieHeaderOverride: curl,
            now: Self.now,
            transport: stub)
    }

    @Test
    func `curl capture forwards ansi quoted and equals header forms`() async throws {
        let curl = """
        curl 'https://t3.chat/api/trpc/getCustomerData?batch=1&input=ignored' \\
          --header=$'User-Agent: Browser\\'s Agent' \\
          --header 'X-Deployment-Id: dpl_test' \\
          -H 'Cookie: session=abc'
        """
        let stub = ProviderHTTPTransportStub { request in
            #expect(request.value(forHTTPHeaderField: "Cookie") == "session=abc")
            #expect(request.value(forHTTPHeaderField: "User-Agent") == "Browser's Agent")
            #expect(request.value(forHTTPHeaderField: "X-Deployment-Id") == "dpl_test")
            let response = HTTPURLResponse(
                url: request.url!,
                statusCode: 200,
                httpVersion: nil,
                headerFields: nil)!
            return (Data(Self.sampleResponse.utf8), response)
        }

        let fetcher = T3ChatUsageFetcher(browserDetection: BrowserDetection(cacheTTL: 0))
        _ = try await fetcher.fetch(
            cookieHeaderOverride: curl,
            now: Self.now,
            transport: stub)
    }

    @Test
    func `full curl capture extracts cookie from long header form`() async throws {
        let curl = """
        curl 'https://t3.chat/api/trpc/getCustomerData?batch=1&input=ignored' \\
          --compressed \\
          --header "Referer: https://t3.chat/settings/customization" \\
          --header "Cookie: session=abc; cf_clearance=token" \\
          --header "X-Deployment-Id: dpl_test"
        """
        let stub = ProviderHTTPTransportStub { request in
            #expect(request.value(forHTTPHeaderField: "Cookie") == "session=abc; cf_clearance=token")
            #expect(request.value(forHTTPHeaderField: "Referer") == "https://t3.chat/settings/customization")
            #expect(request.value(forHTTPHeaderField: "X-Deployment-Id") == "dpl_test")
            let response = HTTPURLResponse(
                url: request.url!,
                statusCode: 200,
                httpVersion: nil,
                headerFields: nil)!
            return (Data(Self.sampleResponse.utf8), response)
        }

        let fetcher = T3ChatUsageFetcher(browserDetection: BrowserDetection(cacheTTL: 0))
        _ = try await fetcher.fetch(
            cookieHeaderOverride: curl,
            now: Self.now,
            transport: stub)
    }

    @Test
    func `manual strategy accepts full curl capture`() async {
        let curl = """
        curl 'https://t3.chat/api/trpc/getCustomerData?batch=1&input=ignored' \\
          --header "Referer: https://t3.chat/settings/customization" \\
          --header "Cookie: session=abc; cf_clearance=token" \\
          --header "X-Deployment-Id: dpl_test"
        """
        let settings = ProviderSettingsSnapshot.make(
            t3chat: ProviderSettingsSnapshot.T3ChatProviderSettings(
                cookieSource: .manual,
                manualCookieHeader: curl))

        #expect(await T3ChatWebFetchStrategy().isAvailable(Self.makeContext(settings: settings)))
    }

    @Test
    func `unauthorized response is invalid credentials`() async throws {
        let stub = ProviderHTTPTransportStub { request in
            let response = HTTPURLResponse(
                url: request.url!,
                statusCode: 401,
                httpVersion: nil,
                headerFields: nil)!
            return (Data("unauthorized".utf8), response)
        }

        await #expect {
            _ = try await T3ChatUsageFetcher.fetchCustomerData(
                cookieHeader: "session=abc",
                now: Self.now,
                transport: stub)
        } throws: { error in
            guard case T3ChatUsageError.invalidCredentials = error else { return false }
            return true
        }
    }

    @Test
    func `vercel challenge response asks for full curl capture`() async throws {
        let stub = ProviderHTTPTransportStub { request in
            let response = HTTPURLResponse(
                url: request.url!,
                statusCode: 429,
                httpVersion: nil,
                headerFields: ["x-vercel-mitigated": "challenge"])!
            return (Data("checkpoint".utf8), response)
        }

        await #expect {
            _ = try await T3ChatUsageFetcher.fetchCustomerData(
                cookieHeader: "session=abc",
                now: Self.now,
                transport: stub)
        } throws: { error in
            guard case T3ChatUsageError.vercelChallenge = error else { return false }
            return true
        }
    }

    private static func customerDataResponse(_ customerDataJSON: String) -> String {
        #"{"json":[2,0,[[\#(customerDataJSON)]]]}"# + "\n"
    }

    private static func makeContext(settings: ProviderSettingsSnapshot) -> ProviderFetchContext {
        ProviderFetchContext(
            runtime: .app,
            sourceMode: .auto,
            includeCredits: false,
            webTimeout: 1,
            webDebugDumpHTML: false,
            verbose: false,
            env: [:],
            settings: settings,
            fetcher: UsageFetcher(environment: [:]),
            claudeFetcher: StubClaudeFetcher(),
            browserDetection: BrowserDetection(cacheTTL: 0))
    }
}
