import CodexBarCore
import Foundation
import Testing

@Suite(.serialized)
struct CodexActiveSourceConfigTests {
    @Test
    func `legacy config without codex active source decodes to nil`() throws {
        let legacyJSON = """
        {
            "version": 1,
            "providers": [
                {
                    "id": "codex"
                }
            ]
        }
        """

        let decoded = try JSONDecoder().decode(
            CodexBarConfig.self,
            from: Data(legacyJSON.utf8))

        #expect(decoded.providerConfig(for: .codex)?.codexActiveSource == nil)
        #expect(decoded.providerConfig(for: .codex)?.quotaWarnings == nil)
    }

    @Test
    func `provider config round trips quota warning overrides`() throws {
        let config = CodexBarConfig(
            providers: [
                ProviderConfig(
                    id: .codex,
                    quotaWarnings: QuotaWarningConfig(
                        session: QuotaWarningWindowConfig(thresholds: [10]),
                        weekly: QuotaWarningWindowConfig(thresholds: [50, 20]))),
            ])

        let data = try JSONEncoder().encode(config)
        let decoded = try JSONDecoder().decode(CodexBarConfig.self, from: data)
        let quotaWarnings = try #require(decoded.providerConfig(for: .codex)?.quotaWarnings)

        #expect(quotaWarnings.thresholds(for: .session, global: [80]) == [10])
        #expect(quotaWarnings.thresholds(for: .weekly, global: [80]) == [50, 20])
    }

    @Test
    func `quota warning window enabled defaults stay backward compatible`() throws {
        let legacyJSON = """
        {
            "version": 1,
            "providers": [
                {
                    "id": "codex",
                    "quotaWarnings": {
                        "session": { "thresholds": [10] },
                        "weekly": { "enabled": false }
                    }
                }
            ]
        }
        """

        let decoded = try JSONDecoder().decode(CodexBarConfig.self, from: Data(legacyJSON.utf8))
        let quotaWarnings = try #require(decoded.providerConfig(for: .codex)?.quotaWarnings)

        #expect(quotaWarnings.isEnabled(for: .session, global: false) == true)
        #expect(quotaWarnings.isEnabled(for: .weekly, global: true) == false)
        #expect(quotaWarnings.hasOverride(for: .session) == true)
        #expect(quotaWarnings.hasOverride(for: .weekly) == true)
    }

    @Test
    func `provider config encodes live system active source with expected schema`() throws {
        let config = CodexBarConfig(
            providers: [
                codexProviderConfig(activeSource: .liveSystem),
            ])

        let data = try JSONEncoder().encode(config)
        let object = try JSONSerialization.jsonObject(with: data) as? [String: Any]
        let providers = try #require(object?["providers"] as? [[String: Any]])
        let provider = try #require(providers.first(where: { $0["id"] as? String == "codex" }))
        let activeSource = try #require(provider["codexActiveSource"] as? [String: Any])

        #expect(activeSource.count == 1)
        #expect(activeSource["kind"] as? String == "liveSystem")
        #expect(activeSource["accountID"] == nil)
    }

    @Test
    func `provider config encodes managed account active source with expected schema`() throws {
        let accountID = UUID()
        let config = CodexBarConfig(
            providers: [
                codexProviderConfig(activeSource: .managedAccount(id: accountID)),
            ])

        let data = try JSONEncoder().encode(config)
        let object = try JSONSerialization.jsonObject(with: data) as? [String: Any]
        let providers = try #require(object?["providers"] as? [[String: Any]])
        let provider = try #require(providers.first(where: { $0["id"] as? String == "codex" }))
        let activeSource = try #require(provider["codexActiveSource"] as? [String: Any])

        #expect(activeSource.count == 2)
        #expect(activeSource["kind"] as? String == "managedAccount")
        #expect((activeSource["accountID"] as? String) == accountID.uuidString)
    }

    @Test
    func `provider config encodes profile home source in downgrade readable envelope`() throws {
        let config = CodexBarConfig(
            providers: [
                codexProviderConfig(
                    activeSource: .profileHome(path: "/Users/test/.codex-work"),
                    profileHomePaths: ["/Users/test/.codex-work"]),
            ])

        let data = try JSONEncoder().encode(config)
        let object = try JSONSerialization.jsonObject(with: data) as? [String: Any]
        let providers = try #require(object?["providers"] as? [[String: Any]])
        let provider = try #require(providers.first(where: { $0["id"] as? String == "codex" }))
        let activeSource = try #require(provider["codexActiveSource"] as? [String: Any])

        #expect(activeSource.count == 2)
        #expect(activeSource["kind"] as? String == "liveSystem")
        #expect(activeSource["homePath"] as? String == "/Users/test/.codex-work")
        #expect(provider["codexProfileHomePaths"] as? [String] == ["/Users/test/.codex-work"])

        let releasedConfig = try JSONDecoder().decode(ReleasedCodexBarConfig.self, from: data)
        #expect(releasedConfig.providers.first?.codexActiveSource == .liveSystem)
    }

    @Test
    func `provider config round trips live system active source`() throws {
        let config = CodexBarConfig(
            providers: [
                codexProviderConfig(activeSource: .liveSystem),
            ])

        let data = try JSONEncoder().encode(config)
        let decoded = try JSONDecoder().decode(CodexBarConfig.self, from: data)

        #expect(decoded.providerConfig(for: .codex)?.codexActiveSource == .liveSystem)
    }

    @Test
    func `provider config round trips managed account active source`() throws {
        let accountID = UUID()
        let config = CodexBarConfig(
            providers: [
                codexProviderConfig(activeSource: .managedAccount(id: accountID)),
            ])

        let data = try JSONEncoder().encode(config)
        let decoded = try JSONDecoder().decode(CodexBarConfig.self, from: data)

        #expect(decoded.providerConfig(for: .codex)?.codexActiveSource == .managedAccount(id: accountID))
    }

    @Test
    func `provider config round trips profile home active source`() throws {
        let config = CodexBarConfig(
            providers: [
                codexProviderConfig(
                    activeSource: .profileHome(path: "/Users/test/.codex-work"),
                    profileHomePaths: ["/Users/test/.codex-work"]),
            ])

        let data = try JSONEncoder().encode(config)
        let decoded = try JSONDecoder().decode(CodexBarConfig.self, from: data)
        let providerConfig = decoded.providerConfig(for: .codex)

        #expect(providerConfig?.codexActiveSource == .profileHome(path: "/Users/test/.codex-work"))
        #expect(providerConfig?.codexProfileHomePaths == ["/Users/test/.codex-work"])
    }

    @Test
    func `profile home discriminator written by development builds still decodes`() throws {
        let data = Data(#"{"kind":"profileHome","homePath":"/Users/test/.codex-work"}"#.utf8)

        let decoded = try JSONDecoder().decode(CodexActiveSource.self, from: data)
        let canonicalData = try JSONEncoder().encode(decoded)
        let canonical = try #require(JSONSerialization.jsonObject(with: canonicalData) as? [String: Any])

        #expect(decoded == .profileHome(path: "/Users/test/.codex-work"))
        #expect(canonical["kind"] as? String == "liveSystem")
        #expect(canonical["homePath"] as? String == "/Users/test/.codex-work")
    }

    @Test
    func `blank profile home sentinel falls back to live system`() throws {
        let data = Data(#"{"kind":"liveSystem","homePath":"  "}"#.utf8)

        let decoded = try JSONDecoder().decode(CodexActiveSource.self, from: data)

        #expect(decoded == .liveSystem)
    }
}

private func codexProviderConfig(
    activeSource: CodexActiveSource,
    profileHomePaths: [String]? = nil) -> ProviderConfig
{
    var config = ProviderConfig(id: .codex)
    config.codexActiveSource = activeSource
    config.codexProfileHomePaths = profileHomePaths
    return config
}

private struct ReleasedCodexBarConfig: Decodable {
    let providers: [ReleasedProviderConfig]
}

private struct ReleasedProviderConfig: Decodable {
    let codexActiveSource: ReleasedCodexActiveSource?
}

private enum ReleasedCodexActiveSource: Decodable, Equatable {
    case liveSystem
    case managedAccount(id: UUID)

    private enum CodingKeys: String, CodingKey {
        case kind
        case accountID
    }

    private enum Kind: String, Decodable {
        case liveSystem
        case managedAccount
    }

    init(from decoder: any Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        switch try container.decode(Kind.self, forKey: .kind) {
        case .liveSystem:
            self = .liveSystem
        case .managedAccount:
            self = try .managedAccount(id: container.decode(UUID.self, forKey: .accountID))
        }
    }
}
