import Foundation
import SweetCookieKit

public enum CopilotProviderDescriptor {
    public static let descriptor: ProviderDescriptor = Self.makeDescriptor()
    private static let credentials = ProviderCredentialAdapter.apiKey(
        environmentKey: "COPILOT_API_TOKEN",
        resolve: { ProviderConfig.clean($0["COPILOT_API_TOKEN"]) },
        tokenAccountSupport: TokenAccountSupport(
            title: "GitHub accounts",
            subtitle: "Sign in with multiple GitHub accounts via OAuth.",
            placeholder: "Paste GitHub token…",
            injection: .environment(key: "COPILOT_API_TOKEN"),
            requiresManualCookieSource: false,
            cookieName: nil,
            clearsAPIKeyOnMutation: true,
            primaryAddActionTitle: "Add Account"))

    /// Budget imports stay Chrome-only to avoid prompting unrelated browsers.
    private static var browserCookieOrder: BrowserCookieImportOrder? {
        #if os(macOS)
        [.chrome]
        #else
        nil
        #endif
    }

    static func makeDescriptor() -> ProviderDescriptor {
        ProviderDescriptor(
            id: .copilot,
            settingsSection: .init(CopilotProviderSettingsKey.self, cookieSettings: { settings in
                CookieProviderSettings(
                    cookieSource: settings.budgetCookieSource,
                    manualCookieHeader: settings.manualBudgetCookieHeader)
            }),
            credentials: self.credentials,
            config: ProviderConfigCapabilities(supportsEnterpriseHost: true),
            metadata: ProviderMetadata(
                id: .copilot,
                displayName: "Copilot",
                sessionLabel: "Premium",
                weeklyLabel: "Chat",
                opusLabel: nil,
                supportsOpus: false,
                supportsCredits: false,
                creditsHint: "",
                toggleTitle: "Show Copilot usage",
                cliName: "copilot",
                defaultEnabled: false,
                isPrimaryProvider: false,
                usesAccountFallback: false,
                sharePlanLabels: [
                    "free": "Free", "individual": "Individual", "pro": "Individual",
                    "business": "Business", "enterprise": "Enterprise",
                ],
                debugLogUnavailableMessage: "Copilot debug log not yet implemented",
                browserCookieOrder: self.browserCookieOrder,
                dashboardURL: "https://github.com/settings/copilot",
                statusPageURL: "https://www.githubstatus.com/"),
            branding: ProviderBranding(
                iconStyle: .init(provider: .copilot),
                iconResourceName: "ProviderIcon-copilot",
                color: ProviderColor(red: 168 / 255, green: 85 / 255, blue: 247 / 255),
                confettiPalette: [
                    ProviderColor(hex: 0x8534F3),
                    ProviderColor(hex: 0xF08A3A),
                    ProviderColor(hex: 0xC898FD),
                ]),
            tokenCost: ProviderTokenCostConfig(
                supportsTokenCost: false,
                noDataMessage: { "Copilot cost summary is not supported." }),
            pace: ProviderPaceCapability(
                resetWindowPace: .resetDatePresent,
                inferredMonthlyDuration: .windowDurationMissing),
            presentation: ProviderUsagePresentation(
                iconWindowResolver: { context in
                    guard let id = context.secondaryOverrideWindowID,
                          let extra = context.snapshot.extraRateWindows?.first(where: { $0.id == id })?.window
                    else {
                        return ProviderUsageWindowPair(
                            primary: context.snapshot.primary,
                            secondary: context.snapshot.secondary)
                    }
                    return ProviderUsageWindowPair(primary: context.snapshot.primary, secondary: extra)
                },
                automaticSelectionPrioritizesExhaustedWindow: false,
                menuBarWindowResolver: { context in
                    guard context.metric == .automatic,
                          let primary = context.snapshot.primary,
                          let secondary = context.snapshot.secondary
                    else { return .unhandled }
                    return .resolved(primary.usedPercent >= secondary.usedPercent ? primary : secondary)
                },
                menuCard: ProviderMenuCardPresentation(primaryDescriptionPlacement: .detailLeft)),
            fetchPlan: ProviderFetchPlan(
                sourceModes: [.auto, .api],
                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [CopilotAPIFetchStrategy()] })),
            cli: ProviderCLIConfig(
                name: "copilot",
                versionDetector: nil))
    }
}

struct CopilotAPIFetchStrategy: ProviderFetchStrategy {
    let id: String = "copilot.api"
    let kind: ProviderFetchKind = .apiToken

    func isAvailable(_ context: ProviderFetchContext) async -> Bool {
        Self.resolveToken(context: context) != nil
    }

    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {
        guard let token = Self.resolveToken(context: context), !token.isEmpty else {
            throw URLError(.userAuthenticationRequired)
        }
        let fetcher = CopilotUsageFetcher(
            token: token,
            enterpriseHost: context.settings?.copilot?.enterpriseHost)
        let usage = try await fetcher.fetch()
        let snap = await self.addBudgetWindowsIfNeeded(to: usage, token: token, context: context)
        return self.makeResult(
            usage: snap,
            sourceLabel: "api")
    }

    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {
        false
    }

    private static func resolveToken(context: ProviderFetchContext) -> String? {
        ProviderTokenResolver.token(for: .copilot, environment: context.env)
            ?? ProviderTokenResolver.resolution(for: .copilot, environment: [
                "COPILOT_API_TOKEN": context.settings?.copilot?.apiToken ?? "",
            ])?.token
    }

    private func addBudgetWindowsIfNeeded(
        to usage: UsageSnapshot,
        token: String,
        context: ProviderFetchContext) async -> UsageSnapshot
    {
        guard let settings = context.settings?.copilot,
              settings.budgetExtrasEnabled,
              settings.budgetCookieSource != .off
        else { return usage }

        let manualCookieHeader = Self.budgetCookieHeaderOverride(from: settings)
        if settings.budgetCookieSource == .manual, manualCookieHeader == nil {
            return usage
        }
        do {
            let expectedAccountIdentifier = try await self.expectedBudgetAccountIdentifier(
                token: token,
                settings: settings)
            let extraRateWindows = try await CopilotBudgetWebFetcher(
                cookieHeaderOverride: manualCookieHeader,
                expectedGitHubAccountIdentifier: expectedAccountIdentifier,
                browserDetection: context.browserDetection)
                .fetchBudgetWindows()
            guard !extraRateWindows.isEmpty else { return usage }
            return usage.with(extraRateWindows: extraRateWindows)
        } catch {
            CodexBarLog.logger(LogCategories.providers).warning(
                "Copilot budget extras unavailable",
                metadata: ["error": "\(error.localizedDescription)"])
            return usage
        }
    }

    static func budgetCookieHeaderOverride(
        from settings: ProviderSettingsSnapshot.CopilotProviderSettings) -> String?
    {
        guard settings.budgetCookieSource == .manual else { return nil }
        return CookieHeaderNormalizer.normalize(settings.manualBudgetCookieHeader)
    }

    private func expectedBudgetAccountIdentifier(
        token: String,
        settings: ProviderSettingsSnapshot.CopilotProviderSettings) async throws -> String
    {
        let identity = try await CopilotUsageFetcher.fetchGitHubIdentity(token: token)
        let tokenIdentifier = CopilotBudgetWebFetcher.normalizedGitHubAccountIdentifier(for: identity)
        if let selectedIdentifier = Self.normalizedBudgetAccountIdentifier(settings.selectedAccountExternalIdentifier),
           selectedIdentifier != tokenIdentifier.lowercased(),
           selectedIdentifier != identity.login.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
        {
            CodexBarLog.logger(LogCategories.providers).warning(
                "Ignoring stale Copilot account identifier")
        }
        return tokenIdentifier
    }

    private static func normalizedBudgetAccountIdentifier(_ identifier: String?) -> String? {
        let trimmed = identifier?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
        return trimmed.isEmpty ? nil : trimmed.lowercased()
    }
}
