import Foundation
import Testing
@testable import CodexBarCore

struct OpenCodeGoUsageParserTests {
    @Test
    func `parses workspace ids`() {
        let text = ";0x00000089;((self.$R=self.$R||{})[\"codexbar\"]=[]," +
            "($R=>$R[0]=[$R[1]={id:\"wrk_01K6AR1ZET89H8NB691FQ2C2VB\",name:\"Default\",slug:null}])" +
            "($R[\"codexbar\"]))"
        let ids = OpenCodeGoUsageFetcher.parseWorkspaceIDs(text: text)
        #expect(ids == ["wrk_01K6AR1ZET89H8NB691FQ2C2VB"])
    }

    @Test
    func `parses subscription usage from seroval response`() throws {
        let text =
            "$R[16]($R[30],$R[41]={rollingUsage:$R[42]={status:\"ok\",resetInSec:5944,usagePercent:17}," +
            "weeklyUsage:$R[43]={status:\"ok\",resetInSec:278201,usagePercent:75}," +
            "monthlyUsage:$R[44]={status:\"ok\",resetInSec:880201,usagePercent:91}});"
        let now = Date(timeIntervalSince1970: 0)

        let snapshot = try OpenCodeGoUsageFetcher.parseSubscription(text: text, now: now)

        #expect(snapshot.rollingUsagePercent == 17)
        #expect(snapshot.weeklyUsagePercent == 75)
        #expect(snapshot.hasMonthlyUsage == true)
        #expect(snapshot.monthlyUsagePercent == 91)
        #expect(snapshot.rollingResetInSec == 5944)
        #expect(snapshot.weeklyResetInSec == 278_201)
        #expect(snapshot.monthlyResetInSec == 880_201)
    }

    @Test
    func `parses zen balance from workspace page text`() {
        let text = """
        <main>
        <h2>現在の残高 $1,234.56</h2>
        <p>Claude Opus and GPT-5 models enabled</p>
        </main>
        """

        #expect(OpenCodeGoUsageFetcher.parseZenBalance(text: text) == 1234.56)
    }

    @Test
    func `parses zen balance from nested JSON`() throws {
        let payload: [String: Any] = [
            "data": [
                "billing": [
                    "balanceEnabled": true,
                    "zenBalance": "1,042.75",
                ],
            ],
        ]
        let data = try JSONSerialization.data(withJSONObject: payload)
        let text = String(data: data, encoding: .utf8) ?? ""

        #expect(OpenCodeGoUsageFetcher.parseZenBalance(text: text) == 1042.75)
    }

    @Test
    func `parses scaled zen balance from billing server response`() {
        let text =
            #";0x00000120;((self.$R=self.$R||{})["server-fn:test"]=[],"# +
            #"($R=>$R[0]=$R[1]={customerID:"cus_test",balance:$R[2]=2375000000,reload:!1})"# +
            #"($R["server-fn:test"]))"#

        #expect(OpenCodeGoZenBalanceParser.parseBillingServerResponse(text: text) == 23.75)
    }

    @Test
    func `billing server parser ignores unrelated balance metadata`() {
        let text = #"$R[0]={balanceEnabled:!0,balanceUpdatedAt:1800000000}"#

        #expect(OpenCodeGoZenBalanceParser.parseBillingServerResponse(text: text) == nil)
    }

    @Test
    func `billing server parser ignores balance when billing is disabled`() {
        let text = #"$R[0]={customerID:null,balance:0,reload:!1}"#

        #expect(OpenCodeGoZenBalanceParser.parseBillingServerResponse(text: text) == nil)
    }

    @Test
    func `zen balance parser ignores metadata before amount`() throws {
        let payload: [String: Any] = [
            "data": [
                "billing": [
                    "balanceUpdatedAt": 1_800_000_000,
                    "balanceRefreshInterval": 60,
                    "zenBalance": "42.50",
                ],
            ],
        ]
        let data = try JSONSerialization.data(withJSONObject: payload)
        let text = String(data: data, encoding: .utf8) ?? ""

        #expect(OpenCodeGoUsageFetcher.parseZenBalance(text: text) == 42.50)
    }

    @Test
    func `parses subscription usage from live go page hydration`() throws {
        let rollingResetInSec = 17591
        let weeklyResetInSec = 444_552
        let monthlyResetInSec = 2_591_424
        let text =
            "_$HY.r[\"lite.subscription.get[\\\"wrk_LIVE123\\\"]\"]=$R[17]=$R[2]($R[18]={p:0,s:0,f:0});" +
            "$R[24]($R[18],$R[27]={mine:!0,useBalance:!1," +
            "rollingUsage:$R[28]={status:\"ok\",resetInSec:\(rollingResetInSec),usagePercent:0}," +
            "weeklyUsage:$R[29]={status:\"ok\",resetInSec:\(weeklyResetInSec),usagePercent:0}," +
            "monthlyUsage:$R[30]={status:\"ok\",resetInSec:\(monthlyResetInSec),usagePercent:0}});"
        let now = Date(timeIntervalSince1970: 0)

        let snapshot = try OpenCodeGoUsageFetcher.parseSubscription(text: text, now: now)

        #expect(snapshot.rollingUsagePercent == 0)
        #expect(snapshot.weeklyUsagePercent == 0)
        #expect(snapshot.hasMonthlyUsage == true)
        #expect(snapshot.monthlyUsagePercent == 0)
        #expect(snapshot.rollingResetInSec == rollingResetInSec)
        #expect(snapshot.weeklyResetInSec == weeklyResetInSec)
        #expect(snapshot.monthlyResetInSec == monthlyResetInSec)
    }

    @Test
    func `parses rolling only usage from seroval response`() throws {
        let text =
            "$R[16]($R[30],$R[41]={rollingUsage:$R[42]={status:\"ok\",resetInSec:5944,usagePercent:17}});"
        let now = Date(timeIntervalSince1970: 0)

        let snapshot = try OpenCodeGoUsageFetcher.parseSubscription(text: text, now: now)
        let usage = snapshot.toUsageSnapshot()

        #expect(snapshot.rollingUsagePercent == 17)
        #expect(snapshot.rollingResetInSec == 5944)
        #expect(snapshot.hasWeeklyUsage == false)
        #expect(usage.primary?.usedPercent == 17)
        #expect(usage.secondary == nil)
        #expect(usage.tertiary == nil)
    }

    @Test
    func `parses rolling only usage from JSON response`() throws {
        let now = Date(timeIntervalSince1970: 1_700_000_000)
        let payload: [String: Any] = [
            "usage": [
                "rollingUsage": [
                    "usagePercent": 25,
                    "resetInSec": 600,
                ],
            ],
        ]
        let data = try JSONSerialization.data(withJSONObject: payload)
        let text = String(data: data, encoding: .utf8) ?? ""

        let snapshot = try OpenCodeGoUsageFetcher.parseSubscription(text: text, now: now)
        let usage = snapshot.toUsageSnapshot()

        #expect(snapshot.rollingUsagePercent == 25)
        #expect(snapshot.rollingResetInSec == 600)
        #expect(snapshot.hasWeeklyUsage == false)
        #expect(usage.primary?.usedPercent == 25)
        #expect(usage.secondary == nil)
        #expect(usage.tertiary == nil)
    }

    @Test
    func `recovers weekly usage from nested JSON window`() throws {
        let now = Date(timeIntervalSince1970: 1_700_000_000)
        let payload: [String: Any] = [
            "usage": [
                "rollingUsage": [
                    "usagePercent": 25,
                    "resetInSec": 600,
                ],
                "weeklyUsage": [
                    "window": [
                        "usagePercent": 75,
                        "resetInSec": 7200,
                    ],
                ],
            ],
        ]
        let data = try JSONSerialization.data(withJSONObject: payload)
        let text = String(data: data, encoding: .utf8) ?? ""

        let snapshot = try OpenCodeGoUsageFetcher.parseSubscription(text: text, now: now)
        let usage = snapshot.toUsageSnapshot()

        #expect(snapshot.hasWeeklyUsage == true)
        #expect(snapshot.weeklyUsagePercent == 75)
        #expect(snapshot.weeklyResetInSec == 7200)
        #expect(usage.secondary?.usedPercent == 75)
    }

    @Test
    func `parses subscription from JSON with reset at and ratio percentages`() throws {
        let now = Date(timeIntervalSince1970: 1_700_000_000)
        let rollingResetAt = now.addingTimeInterval(3600)
        let monthlyResetAt = now.addingTimeInterval(86400)
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        let payload: [String: Any] = [
            "usage": [
                "rollingUsage": [
                    "usagePercent": 0.25,
                    "resetAt": formatter.string(from: rollingResetAt),
                ],
                "weeklyUsage": [
                    "usagePercent": 75,
                    "resetInSec": 7200,
                ],
                "monthlyUsage": [
                    "usagePercent": 0.9,
                    "resetAt": formatter.string(from: monthlyResetAt),
                ],
            ],
        ]
        let data = try JSONSerialization.data(withJSONObject: payload)
        let text = String(data: data, encoding: .utf8) ?? ""

        let snapshot = try OpenCodeGoUsageFetcher.parseSubscription(text: text, now: now)

        #expect(snapshot.rollingUsagePercent == 25)
        #expect(snapshot.weeklyUsagePercent == 75)
        #expect(snapshot.hasMonthlyUsage == true)
        #expect(snapshot.monthlyUsagePercent == 90)
        #expect(snapshot.rollingResetInSec == 3600)
        #expect(snapshot.weeklyResetInSec == 7200)
        #expect(snapshot.monthlyResetInSec == 86400)
    }

    @Test(arguments: ["1e309", "1e308"])
    func `ignores reset timestamps outside integer range`(resetAt: String) throws {
        let text = """
        {
          "rollingUsage": { "usagePercent": 17, "resetAt": "\(resetAt)" },
          "weeklyUsage": { "usagePercent": 75, "resetInSec": 7200 }
        }
        """

        let snapshot = try OpenCodeGoUsageFetcher.parseSubscription(
            text: text,
            now: Date(timeIntervalSince1970: 1_700_000_000))

        #expect(snapshot.rollingUsagePercent == 17)
        #expect(snapshot.weeklyUsagePercent == 75)
        #expect(snapshot.rollingResetInSec == 0)
        #expect(snapshot.weeklyResetInSec == 7200)
    }

    @Test
    func `computes usage percent from totals and treats monthly as optional`() throws {
        let now = Date(timeIntervalSince1970: 1_700_000_000)
        let payload: [String: Any] = [
            "rollingUsage": [
                "used": 25,
                "limit": 100,
                "resetInSec": 600,
            ],
            "weeklyUsage": [
                "used": 50,
                "limit": 200,
                "resetInSec": 3600,
            ],
        ]
        let data = try JSONSerialization.data(withJSONObject: payload)
        let text = String(data: data, encoding: .utf8) ?? ""

        let snapshot = try OpenCodeGoUsageFetcher.parseSubscription(text: text, now: now)
        let usage = snapshot.toUsageSnapshot()

        #expect(snapshot.rollingUsagePercent == 25)
        #expect(snapshot.weeklyUsagePercent == 25)
        #expect(snapshot.hasMonthlyUsage == false)
        #expect(snapshot.monthlyUsagePercent == 0)
        #expect(snapshot.monthlyResetInSec == 0)
        #expect(usage.tertiary == nil)
    }

    @Test
    func `snapshot exposes zen balance as provider cost`() {
        let now = Date(timeIntervalSince1970: 1_700_000_000)
        let snapshot = OpenCodeGoUsageSnapshot(
            hasMonthlyUsage: false,
            rollingUsagePercent: 10,
            weeklyUsagePercent: 20,
            monthlyUsagePercent: 0,
            rollingResetInSec: 600,
            weeklyResetInSec: 3600,
            monthlyResetInSec: 0,
            zenBalanceUSD: 12.34,
            updatedAt: now)

        let usage = snapshot.toUsageSnapshot()

        #expect(usage.providerCost?.period == "Zen balance")
        #expect(usage.providerCost?.used == 12.34)
        #expect(usage.providerCost?.limit == 0)
        #expect(usage.providerCost?.currencyCode == "USD")
    }

    @Test
    func `zen balance parser ignores balance flags without amounts`() throws {
        let payload: [String: Any] = [
            "billing": [
                "balanceEnabled": true,
                "useBalance": false,
            ],
        ]
        let data = try JSONSerialization.data(withJSONObject: payload)
        let text = String(data: data, encoding: .utf8) ?? ""

        #expect(OpenCodeGoUsageFetcher.parseZenBalance(text: text) == nil)
    }

    @Test
    func `parses subscription from nested candidate windows`() throws {
        let now = Date(timeIntervalSince1970: 1_700_000_000)
        let payload: [String: Any] = [
            "windows": [
                "primaryWindow": [
                    "used": 15,
                    "limit": 100,
                    "resetInSec": 600,
                ],
                "weeklyQuota": [
                    "used": 80,
                    "limit": 200,
                    "resetInSec": 7200,
                ],
                "monthlyBucket": [
                    "used": 90,
                    "limit": 300,
                    "resetInSec": 86400,
                ],
            ],
        ]
        let data = try JSONSerialization.data(withJSONObject: payload)
        let text = String(data: data, encoding: .utf8) ?? ""

        let snapshot = try OpenCodeGoUsageFetcher.parseSubscription(text: text, now: now)

        #expect(snapshot.rollingUsagePercent == 15)
        #expect(snapshot.weeklyUsagePercent == 40)
        #expect(snapshot.hasMonthlyUsage == true)
        #expect(snapshot.monthlyUsagePercent == 30)
        #expect(snapshot.monthlyResetInSec == 86400)
    }

    @Test
    func `candidate fallback preserves missing weekly window`() throws {
        let now = Date(timeIntervalSince1970: 1_700_000_000)
        let payload: [String: Any] = [
            "windows": [
                "primaryWindow": [
                    "used": 15,
                    "limit": 100,
                    "resetInSec": 600,
                ],
                "monthlyBucket": [
                    "used": 90,
                    "limit": 300,
                    "resetInSec": 86400,
                ],
            ],
        ]
        let data = try JSONSerialization.data(withJSONObject: payload)
        let text = String(data: data, encoding: .utf8) ?? ""

        let snapshot = try OpenCodeGoUsageFetcher.parseSubscription(text: text, now: now)
        let usage = snapshot.toUsageSnapshot()

        #expect(snapshot.rollingUsagePercent == 15)
        #expect(snapshot.hasWeeklyUsage == false)
        #expect(snapshot.hasMonthlyUsage == true)
        #expect(snapshot.monthlyUsagePercent == 30)
        #expect(usage.secondary == nil)
        #expect(usage.tertiary?.usedPercent == 30)
    }

    @Test
    func `clamps invalid percentages`() throws {
        let now = Date(timeIntervalSince1970: 1_700_000_000)
        let payload: [String: Any] = [
            "rollingUsage": [
                "usagePercent": 150,
                "resetInSec": 60,
            ],
            "weeklyUsage": [
                "usagePercent": -10,
                "resetInSec": 120,
            ],
        ]
        let data = try JSONSerialization.data(withJSONObject: payload)
        let text = String(data: data, encoding: .utf8) ?? ""

        let snapshot = try OpenCodeGoUsageFetcher.parseSubscription(text: text, now: now)

        #expect(snapshot.rollingUsagePercent == 100)
        #expect(snapshot.weeklyUsagePercent == 0)
    }

    @Test
    func `parse subscription throws when required fields are missing`() {
        let now = Date(timeIntervalSince1970: 1_700_000_000)
        let text = "{\"monthlyUsage\":{\"usagePercent\":50,\"resetInSec\":123}}"

        #expect(throws: OpenCodeGoUsageError.self) {
            _ = try OpenCodeGoUsageFetcher.parseSubscription(text: text, now: now)
        }
    }

    @Test
    func `renewsAt parses from ISO8601 renewAt key`() throws {
        let now = Date(timeIntervalSince1970: 1_700_000_000)
        let renewAt = now.addingTimeInterval(86400 * 30)
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        let payload: [String: Any] = [
            "rollingUsage": ["usagePercent": 10, "resetInSec": 600],
            "weeklyUsage": ["usagePercent": 50, "resetInSec": 3600],
            "monthlyUsage": ["usagePercent": 25, "resetInSec": 7200],
            "renewAt": formatter.string(from: renewAt),
        ]
        let data = try JSONSerialization.data(withJSONObject: payload)
        let text = String(data: data, encoding: .utf8) ?? ""

        let snapshot = try OpenCodeGoUsageFetcher.parseSubscription(text: text, now: now)

        #expect(snapshot.renewsAt != nil)
        #expect(snapshot.renewsAt == renewAt)
    }

    @Test
    func `renewsAt parses from renew_at key`() throws {
        let now = Date(timeIntervalSince1970: 1_700_000_000)
        let renewAt = now.addingTimeInterval(86400 * 30)
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        let payload: [String: Any] = [
            "rollingUsage": ["usagePercent": 10, "resetInSec": 600],
            "weeklyUsage": ["usagePercent": 50, "resetInSec": 3600],
            "monthlyUsage": ["usagePercent": 25, "resetInSec": 7200],
            "renew_at": formatter.string(from: renewAt),
        ]
        let data = try JSONSerialization.data(withJSONObject: payload)
        let text = String(data: data, encoding: .utf8) ?? ""

        let snapshot = try OpenCodeGoUsageFetcher.parseSubscription(text: text, now: now)

        #expect(snapshot.renewsAt != nil)
        #expect(snapshot.renewsAt == renewAt)
    }

    @Test
    func `renewsAt is nil when absent`() throws {
        let now = Date(timeIntervalSince1970: 1_700_000_000)
        let payload: [String: Any] = [
            "rollingUsage": ["usagePercent": 10, "resetInSec": 600],
            "weeklyUsage": ["usagePercent": 50, "resetInSec": 3600],
            "monthlyUsage": ["usagePercent": 25, "resetInSec": 7200],
        ]
        let data = try JSONSerialization.data(withJSONObject: payload)
        let text = String(data: data, encoding: .utf8) ?? ""

        let snapshot = try OpenCodeGoUsageFetcher.parseSubscription(text: text, now: now)

        #expect(snapshot.renewsAt == nil)
    }

    @Test
    func `top level renewAt is preserved for nested usage object`() throws {
        let now = Date(timeIntervalSince1970: 1_700_000_000)
        let renewAt = now.addingTimeInterval(86400 * 30)
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        let payload: [String: Any] = [
            "renewAt": formatter.string(from: renewAt),
            "usage": [
                "rollingUsage": ["usagePercent": 10, "resetInSec": 600],
                "weeklyUsage": ["usagePercent": 50, "resetInSec": 3600],
                "monthlyUsage": ["usagePercent": 25, "resetInSec": 7200],
            ],
        ]
        let data = try JSONSerialization.data(withJSONObject: payload)
        let text = String(data: data, encoding: .utf8) ?? ""

        let snapshot = try OpenCodeGoUsageFetcher.parseSubscription(text: text, now: now)

        #expect(snapshot.renewsAt == renewAt)
    }

    @Test
    func `top level renew_at is preserved for nested usage object`() throws {
        let now = Date(timeIntervalSince1970: 1_700_000_000)
        let renewAt = now.addingTimeInterval(86400 * 30)
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        let payload: [String: Any] = [
            "renew_at": formatter.string(from: renewAt),
            "usage": [
                "rollingUsage": ["usagePercent": 10, "resetInSec": 600],
                "weeklyUsage": ["usagePercent": 50, "resetInSec": 3600],
                "monthlyUsage": ["usagePercent": 25, "resetInSec": 7200],
            ],
        ]
        let data = try JSONSerialization.data(withJSONObject: payload)
        let text = String(data: data, encoding: .utf8) ?? ""

        let snapshot = try OpenCodeGoUsageFetcher.parseSubscription(text: text, now: now)

        #expect(snapshot.renewsAt == renewAt)
    }

    @Test
    func `child renewAt overrides parent renewAt`() throws {
        let now = Date(timeIntervalSince1970: 1_700_000_000)
        let parentRenewAt = now.addingTimeInterval(86400 * 30)
        let childRenewAt = now.addingTimeInterval(86400 * 45)
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        let payload: [String: Any] = [
            "renewAt": formatter.string(from: parentRenewAt),
            "usage": [
                "renewAt": formatter.string(from: childRenewAt),
                "rollingUsage": ["usagePercent": 10, "resetInSec": 600],
                "weeklyUsage": ["usagePercent": 50, "resetInSec": 3600],
                "monthlyUsage": ["usagePercent": 25, "resetInSec": 7200],
            ],
        ]
        let data = try JSONSerialization.data(withJSONObject: payload)
        let text = String(data: data, encoding: .utf8) ?? ""

        let snapshot = try OpenCodeGoUsageFetcher.parseSubscription(text: text, now: now)

        #expect(snapshot.renewsAt == childRenewAt)
    }

    @Test
    func `toUsageSnapshot includes renewal NamedRateWindow when renewsAt present`() throws {
        let now = Date(timeIntervalSince1970: 1_700_000_000)
        let renewAt = now.addingTimeInterval(86400 * 30)
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        let payload: [String: Any] = [
            "rollingUsage": ["usagePercent": 10, "resetInSec": 600],
            "weeklyUsage": ["usagePercent": 50, "resetInSec": 3600],
            "monthlyUsage": ["usagePercent": 25, "resetInSec": 7200],
            "renewAt": formatter.string(from: renewAt),
        ]
        let data = try JSONSerialization.data(withJSONObject: payload)
        let text = String(data: data, encoding: .utf8) ?? ""

        let snapshot = try OpenCodeGoUsageFetcher.parseSubscription(text: text, now: now)
        let usage = snapshot.toUsageSnapshot()

        #expect(usage.extraRateWindows != nil)
        #expect(usage.extraRateWindows?.count == 1)
        #expect(usage.extraRateWindows?[0].id == "renewal")
        #expect(usage.extraRateWindows?[0].title == "Renews")
        #expect(usage.extraRateWindows?[0].window.resetsAt == renewAt)
    }
}
