import Foundation
import Testing
@testable import CodexBarCore

struct CopilotUsageModelsTests {
    @Test
    func `decodes quota snapshots payload`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "free",
              "assigned_date": "2025-01-01",
              "quota_reset_date": "2025-02-01",
              "quota_snapshots": {
                "premium_interactions": {
                  "entitlement": 500,
                  "remaining": 450,
                  "percent_remaining": 90,
                  "quota_id": "premium_interactions"
                },
                "chat": {
                  "entitlement": 300,
                  "remaining": 150,
                  "percent_remaining": 50,
                  "quota_id": "chat"
                }
              }
            }
            """)

        #expect(response.copilotPlan == "free")
        #expect(response.assignedDate == "2025-01-01")
        #expect(response.quotaResetDate == "2025-02-01")
        #expect(response.quotaSnapshots.premiumInteractions?.remaining == 450)
        #expect(response.quotaSnapshots.chat?.remaining == 150)
    }

    @Test
    func `decodes chat only quota snapshots payload`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "free",
              "quota_snapshots": {
                "chat": {
                  "entitlement": 200,
                  "remaining": 75,
                  "percent_remaining": 37.5,
                  "quota_id": "chat"
                }
              }
            }
            """)

        #expect(response.quotaSnapshots.premiumInteractions == nil)
        #expect(response.quotaSnapshots.chat?.quotaId == "chat")
        #expect(response.quotaSnapshots.chat?.entitlement == 200)
        #expect(response.quotaSnapshots.chat?.remaining == 75)
    }

    @Test
    func `preserves missing date fields as nil`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "free",
              "quota_snapshots": {
                "chat": {
                  "entitlement": 200,
                  "remaining": 75,
                  "percent_remaining": 37.5,
                  "quota_id": "chat"
                }
              }
            }
            """)

        #expect(response.assignedDate == nil)
        #expect(response.quotaResetDate == nil)
    }

    @Test
    func `preserves explicit empty date fields`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "free",
              "assigned_date": "",
              "quota_reset_date": "",
              "quota_snapshots": {
                "chat": {
                  "entitlement": 200,
                  "remaining": 75,
                  "percent_remaining": 37.5,
                  "quota_id": "chat"
                }
              }
            }
            """)

        #expect(response.assignedDate?.isEmpty == true)
        #expect(response.quotaResetDate?.isEmpty == true)
    }

    @Test
    func `decodes monthly and limited quota payload`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "free",
              "monthly_quotas": {
                "chat": "500",
                "completions": 300
              },
              "limited_user_quotas": {
                "chat": 125,
                "completions": "75"
              }
            }
            """)

        #expect(response.quotaSnapshots.premiumInteractions?.quotaId == "completions")
        #expect(response.quotaSnapshots.premiumInteractions?.entitlement == 300)
        #expect(response.quotaSnapshots.premiumInteractions?.remaining == 75)
        #expect(response.quotaSnapshots.premiumInteractions?.percentRemaining == 25)

        #expect(response.quotaSnapshots.chat?.quotaId == "chat")
        #expect(response.quotaSnapshots.chat?.entitlement == 500)
        #expect(response.quotaSnapshots.chat?.remaining == 125)
        #expect(response.quotaSnapshots.chat?.percentRemaining == 25)
    }

    @Test
    func `does not assume full quota when limited quotas are missing`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "free",
              "monthly_quotas": {
                "chat": 500,
                "completions": 300
              }
            }
            """)

        #expect(response.quotaSnapshots.premiumInteractions == nil)
        #expect(response.quotaSnapshots.chat == nil)
    }

    @Test
    func `computes monthly fallback per quota only when limited value exists`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "free",
              "monthly_quotas": {
                "chat": 500,
                "completions": 300
              },
              "limited_user_quotas": {
                "completions": 60
              }
            }
            """)

        #expect(response.quotaSnapshots.premiumInteractions?.quotaId == "completions")
        #expect(response.quotaSnapshots.premiumInteractions?.entitlement == 300)
        #expect(response.quotaSnapshots.premiumInteractions?.remaining == 60)
        #expect(response.quotaSnapshots.premiumInteractions?.percentRemaining == 20)
        #expect(response.quotaSnapshots.chat == nil)
    }

    @Test
    func `merges direct and monthly fallback lanes when direct is partial`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "free",
              "quota_snapshots": {
                "chat": {
                  "entitlement": 200,
                  "remaining": 75,
                  "percent_remaining": 37.5,
                  "quota_id": "chat"
                }
              },
              "monthly_quotas": {
                "chat": 500,
                "completions": 300
              },
              "limited_user_quotas": {
                "chat": 125,
                "completions": 60
              }
            }
            """)

        #expect(response.quotaSnapshots.chat?.quotaId == "chat")
        #expect(response.quotaSnapshots.chat?.entitlement == 200)
        #expect(response.quotaSnapshots.chat?.remaining == 75)
        #expect(response.quotaSnapshots.chat?.percentRemaining == 37.5)

        #expect(response.quotaSnapshots.premiumInteractions?.quotaId == "completions")
        #expect(response.quotaSnapshots.premiumInteractions?.entitlement == 300)
        #expect(response.quotaSnapshots.premiumInteractions?.remaining == 60)
        #expect(response.quotaSnapshots.premiumInteractions?.percentRemaining == 20)
    }

    @Test
    func `decodes unknown quota snapshot keys using fallback`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "free",
              "quota_snapshots": {
                "mystery_bucket": {
                  "entitlement": 100,
                  "remaining": 40,
                  "percent_remaining": 40,
                  "quota_id": "mystery_bucket"
                }
              }
            }
            """)

        #expect(response.quotaSnapshots.premiumInteractions == nil)
        #expect(response.quotaSnapshots.chat?.quotaId == "mystery_bucket")
        #expect(response.quotaSnapshots.chat?.entitlement == 100)
        #expect(response.quotaSnapshots.chat?.remaining == 40)
        #expect(response.quotaSnapshots.chat?.percentRemaining == 40)
    }

    @Test
    func `ignores placeholder known snapshot when selecting unknown key fallback`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "free",
              "quota_snapshots": {
                "premium_interactions": {},
                "mystery_bucket": {
                  "entitlement": 100,
                  "remaining": 40,
                  "percent_remaining": 40,
                  "quota_id": "mystery_bucket"
                }
              }
            }
            """)

        #expect(response.quotaSnapshots.premiumInteractions == nil)
        #expect(response.quotaSnapshots.chat?.quotaId == "mystery_bucket")
        #expect(response.quotaSnapshots.chat?.hasPercentRemaining == true)
    }

    @Test
    func `derives percent remaining when missing but entitlement exists`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "free",
              "quota_snapshots": {
                "chat": {
                  "entitlement": 120,
                  "remaining": 30,
                  "quota_id": "chat"
                }
              }
            }
            """)

        #expect(response.quotaSnapshots.chat?.hasPercentRemaining == true)
        #expect(response.quotaSnapshots.chat?.percentRemaining == 25)
    }

    @Test
    func `preserves over quota percent remaining`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "paid",
              "quota_snapshots": {
                "premium_interactions": {
                  "entitlement": 500,
                  "remaining": -75,
                  "percent_remaining": -15,
                  "quota_id": "premium_interactions"
                }
              }
            }
            """)

        let snapshot = try #require(response.quotaSnapshots.premiumInteractions)
        #expect(snapshot.percentRemaining == -15)
        #expect(snapshot.usedPercent == 115)
        #expect(snapshot.overQuotaUsedPercent == 115)
        let window = try #require(CopilotUsageFetcher.makeRateWindow(from: snapshot))
        #expect(window.usedPercent == 115)
        #expect(window.resetDescription == "115% used")
    }

    @Test
    func `derives over quota percent from negative remaining`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "paid",
              "quota_snapshots": {
                "chat": {
                  "entitlement": 500,
                  "remaining": -75,
                  "quota_id": "chat"
                }
              }
            }
            """)

        let snapshot = try #require(response.quotaSnapshots.chat)
        #expect(snapshot.hasPercentRemaining)
        #expect(snapshot.percentRemaining == -15)
        #expect(snapshot.usedPercent == 115)
        #expect(snapshot.overQuotaUsedPercent == 115)
    }

    @Test
    func `marks percent remaining as unavailable when underdetermined`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "free",
              "quota_snapshots": {
                "chat": {
                  "remaining": 30,
                  "quota_id": "chat"
                }
              }
            }
            """)

        #expect(response.quotaSnapshots.chat?.hasPercentRemaining == false)
        #expect(response.quotaSnapshots.chat?.percentRemaining == 0)
    }

    @Test
    func `marks percent remaining as unavailable when remaining is missing`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "free",
              "quota_snapshots": {
                "chat": {
                  "entitlement": 120,
                  "quota_id": "chat"
                }
              }
            }
            """)

        #expect(response.quotaSnapshots.chat?.hasPercentRemaining == false)
        #expect(response.quotaSnapshots.chat?.percentRemaining == 0)
    }

    @Test
    func `falls back to monthly when direct snapshot is missing remaining`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "free",
              "quota_snapshots": {
                "chat": {
                  "entitlement": 120,
                  "quota_id": "chat"
                }
              },
              "monthly_quotas": {
                "chat": 400
              },
              "limited_user_quotas": {
                "chat": 100
              }
            }
            """)

        #expect(response.quotaSnapshots.premiumInteractions == nil)
        #expect(response.quotaSnapshots.chat?.quotaId == "chat")
        #expect(response.quotaSnapshots.chat?.entitlement == 400)
        #expect(response.quotaSnapshots.chat?.remaining == 100)
        #expect(response.quotaSnapshots.chat?.percentRemaining == 25)
        #expect(response.quotaSnapshots.chat?.hasPercentRemaining == true)
    }

    @Test
    func `falls back to monthly when direct snapshots lack computable percent`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "free",
              "quota_snapshots": {
                "chat": {
                  "remaining": 30,
                  "quota_id": "chat"
                }
              },
              "monthly_quotas": {
                "chat": 400
              },
              "limited_user_quotas": {
                "chat": 100
              }
            }
            """)

        #expect(response.quotaSnapshots.premiumInteractions == nil)
        #expect(response.quotaSnapshots.chat?.quotaId == "chat")
        #expect(response.quotaSnapshots.chat?.entitlement == 400)
        #expect(response.quotaSnapshots.chat?.remaining == 100)
        #expect(response.quotaSnapshots.chat?.percentRemaining == 25)
        #expect(response.quotaSnapshots.chat?.hasPercentRemaining == true)
    }

    @Test
    func `skips monthly fallback when monthly denominator is zero`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "free",
              "monthly_quotas": {
                "chat": 0
              },
              "limited_user_quotas": {
                "chat": 0
              }
            }
            """)

        #expect(response.quotaSnapshots.premiumInteractions == nil)
        #expect(response.quotaSnapshots.chat == nil)
    }

    @Test
    func `treats business token billing zero entitlement quotas as unavailable`() throws {
        // GitHub Copilot Business token-based billing reports every quota as
        // entitlement=0, remaining=0, percent_remaining=100. That previously rendered as a
        // misleading "0% used" (100 - 100). A zero-entitlement quota carries no usage signal,
        // so the snapshots must drop out instead of showing as usage. (#1258)
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "business",
              "token_based_billing": true,
              "quota_snapshots": {
                "premium_interactions": {
                  "entitlement": 0,
                  "remaining": 0,
                  "percent_remaining": 100,
                  "quota_id": "premium_interactions"
                },
                "chat": {
                  "entitlement": 0,
                  "remaining": 0,
                  "percent_remaining": 100,
                  "quota_id": "chat"
                },
                "completions": {
                  "entitlement": 0,
                  "remaining": 0,
                  "percent_remaining": 100,
                  "quota_id": "completions"
                }
              }
            }
            """)

        #expect(response.tokenBasedBilling)
        #expect(response.quotaSnapshots.premiumInteractions == nil)
        #expect(response.quotaSnapshots.chat == nil)
    }

    @Test
    func `keeps unlimited chat fallback quota without percent remaining`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "individual",
              "quota_snapshots": {
                "premium_interactions": {
                  "entitlement": 200,
                  "remaining": 191,
                  "percent_remaining": 95.5,
                  "quota_id": "premium_interactions"
                },
                "chat_messages": {
                  "entitlement": 0,
                  "remaining": 0,
                  "quota_id": "chat_messages",
                  "unlimited": true
                }
              }
            }
            """)

        #expect(response.quotaSnapshots.premiumInteractions?.quotaId == "premium_interactions")
        #expect(response.quotaSnapshots.chat?.quotaId == "chat_messages")
        #expect(response.quotaSnapshots.chat?.unlimited == true)
        #expect(response.quotaSnapshots.chat?.usedPercent == 0)
    }

    @Test
    func `unlimited quota overrides placeholder percent remaining`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "individual",
              "quota_snapshots": {
                "chat": {
                  "entitlement": 0,
                  "remaining": 0,
                  "percent_remaining": 0,
                  "quota_id": "chat",
                  "unlimited": true
                }
              }
            }
            """)

        let chat = try #require(response.quotaSnapshots.chat)
        #expect(chat.percentRemaining == 100)
        #expect(chat.usedPercent == 0)
        #expect(!chat.isPlaceholder)
    }

    @Test
    func `decodes credits used counter from token billed business payload`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "business",
              "token_based_billing": true,
              "quota_reset_date": "2026-09-01",
              "quota_snapshots": {
                "premium_interactions": {
                  "unlimited": true,
                  "entitlement": 0,
                  "remaining": 0,
                  "percent_remaining": 100.0,
                  "quota_id": "premium_interactions",
                  "credits_used": 31
                },
                "chat": {
                  "unlimited": true,
                  "entitlement": 0,
                  "remaining": 0,
                  "percent_remaining": 100.0,
                  "quota_id": "chat",
                  "credits_used": 0
                }
              }
            }
            """)

        let premium = try #require(response.quotaSnapshots.premiumInteractions)
        #expect(response.tokenBasedBilling == true)
        #expect(premium.creditsUsed == 31)
        #expect(premium.unlimited)
        #expect(premium.usedPercent == 0)
    }

    @Test
    func `keeps credit counter when zero entitlement direct snapshot falls back to monthly quota`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "business",
              "token_based_billing": true,
              "monthly_quotas": { "completions": 300 },
              "limited_user_quotas": { "completions": 75 },
              "quota_snapshots": {
                "premium_interactions": {
                  "entitlement": 0,
                  "remaining": 0,
                  "percent_remaining": 100,
                  "quota_id": "premium_interactions",
                  "credits_used": 31
                }
              }
            }
            """)

        let premium = try #require(response.quotaSnapshots.premiumInteractions)
        #expect(premium.creditsUsed == 31)
        #expect(premium.quotaId == "completions")
        #expect(premium.usedPercent == 75)
    }

    @Test
    func `keeps credit counter when unlimited direct snapshot falls back to monthly quota`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "business",
              "token_based_billing": true,
              "monthly_quotas": { "completions": 300 },
              "limited_user_quotas": { "completions": 75 },
              "quota_snapshots": {
                "premium_interactions": {
                  "unlimited": true,
                  "entitlement": 0,
                  "remaining": 0,
                  "percent_remaining": 100,
                  "quota_id": "premium_interactions",
                  "credits_used": 31
                }
              }
            }
            """)

        let premium = try #require(response.quotaSnapshots.premiumInteractions)
        #expect(premium.creditsUsed == 31)
        #expect(premium.quotaId == "completions")
        #expect(premium.usedPercent == 75)
    }

    @Test
    func `keeps credit counter accessible without unlimited marker`() throws {
        // A zero-entitlement snapshot without `unlimited` is still a placeholder
        // for percentage windows, but its absolute counter must not be lost.
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "business",
              "token_based_billing": true,
              "quota_snapshots": {
                "premium_interactions": {
                  "entitlement": 0,
                  "remaining": 0,
                  "percent_remaining": 100,
                  "quota_id": "premium_interactions",
                  "credits_used": 31
                }
              }
            }
            """)

        let premium = try #require(response.quotaSnapshots.premiumInteractions)
        #expect(premium.creditsUsed == 31)
        #expect(premium.isPlaceholder)
    }

    @Test
    func `flags zero entitlement snapshot as placeholder`() {
        let snapshot = CopilotUsageResponse.QuotaSnapshot(
            entitlement: 0,
            remaining: 0,
            percentRemaining: 100,
            quotaId: "chat")
        #expect(snapshot.isPlaceholder)
    }

    @Test
    func `keeps fully consumed quota with positive entitlement`() {
        // entitlement > 0 with remaining 0 is a real "100% used" window, not a placeholder.
        let snapshot = CopilotUsageResponse.QuotaSnapshot(
            entitlement: 500,
            remaining: 0,
            percentRemaining: 0,
            quotaId: "premium_interactions")
        #expect(!snapshot.isPlaceholder)
        #expect(snapshot.usedPercent == 100)
    }

    @Test
    func `keeps percent only quota snapshots available`() throws {
        let response = try Self.decodeFixture(
            """
            {
              "copilot_plan": "business",
              "quota_snapshots": {
                "chat": {
                  "percent_remaining": 40,
                  "quota_id": "chat"
                }
              }
            }
            """)

        #expect(response.quotaSnapshots.chat?.percentRemaining == 40)
        #expect(response.quotaSnapshots.chat?.usedPercent == 60)
        #expect(response.quotaSnapshots.chat?.isPlaceholder == false)
    }

    private static func decodeFixture(_ fixture: String) throws -> CopilotUsageResponse {
        try JSONDecoder().decode(CopilotUsageResponse.self, from: Data(fixture.utf8))
    }
}
