import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

public struct CopilotUsageFetcher: Sendable {
    public struct GitHubUserIdentity: Decodable, Equatable, Sendable {
        public let id: Int64
        public let login: String

        public init(id: Int64, login: String) {
            self.id = id
            self.login = login
        }
    }

    private let token: String
    private let enterpriseHost: String?
    private let transport: any ProviderHTTPTransport

    public init(
        token: String,
        enterpriseHost: String? = nil,
        transport: any ProviderHTTPTransport = ProviderHTTPClient.shared)
    {
        self.token = token
        self.enterpriseHost = enterpriseHost
        self.transport = transport
    }

    public static func apiHost(enterpriseHost: String?) -> String {
        let host = CopilotDeviceFlow.normalizedHost(enterpriseHost)
        if host == CopilotDeviceFlow.defaultHost {
            return "api.github.com"
        }
        if host.hasPrefix("api.") {
            return host
        }
        return "api.\(host)"
    }

    public static func usageURL(enterpriseHost: String?) -> URL? {
        CopilotDeviceFlow.makeRequestURL(
            host: self.apiHost(enterpriseHost: enterpriseHost),
            path: "/copilot_internal/user")
    }

    public func fetch() async throws -> UsageSnapshot {
        guard let url = Self.usageURL(enterpriseHost: self.enterpriseHost) else {
            throw URLError(.badURL)
        }

        var request = URLRequest(url: url)
        // Use the GitHub OAuth token directly, not the Copilot token.
        request.setValue("token \(self.token)", forHTTPHeaderField: "Authorization")
        self.addCommonHeaders(to: &request)

        let response = try await self.transport.response(for: request)

        if response.statusCode == 401 || response.statusCode == 403 {
            throw URLError(.userAuthenticationRequired)
        }

        guard response.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }

        let usage = try JSONDecoder().decode(CopilotUsageResponse.self, from: response.data)
        let resetsAt = Self.parseQuotaResetDate(usage.quotaResetDate)
        let premiumSnapshot = usage.quotaSnapshots.premiumInteractions
        let chatSnapshot = usage.quotaSnapshots.chat
        let premium = Self.makeRateWindow(from: premiumSnapshot, resetsAt: resetsAt)
        let chat = Self.makeRateWindow(from: chatSnapshot, resetsAt: resetsAt)
        let creditsUsed = premiumSnapshot?.creditsUsed ?? chatSnapshot?.creditsUsed
        let details: [ProviderDetailSection] = creditsUsed.map { creditsUsed in
            [.makeSection(title: "Credits", rows: [
                .makeRow(
                    label: "Credits used",
                    value: UsageFormatter.creditsNumberString(from: creditsUsed),
                    secondaryValue: resetsAt.map { UsageFormatter.resetDescription(from: $0) }),
            ])]
        } ?? []
        let hasUnlimitedQuota = premiumSnapshot?.unlimited == true || chatSnapshot?.unlimited == true

        let primary: RateWindow?
        let secondary: RateWindow?
        if let premium {
            primary = premium
            secondary = chat
        } else if let chatWindow = chat {
            // Keep chat in the secondary slot so provider labels remain accurate
            // ("Premium" for primary, "Chat" for secondary) on chat-only plans.
            primary = nil
            secondary = chatWindow
        } else if usage.tokenBasedBilling || hasUnlimitedQuota {
            // Copilot Business token-based billing placeholders and explicitly unlimited quota
            // markers are not metered windows, so surface the plan without fake usage.
            primary = nil
            secondary = nil
        } else {
            throw URLError(.cannotDecodeRawData)
        }

        let identity = ProviderIdentitySnapshot(
            providerID: .copilot,
            accountEmail: nil,
            accountOrganization: nil,
            loginMethod: usage.copilotPlan.capitalized)
        return UsageSnapshot(
            primary: primary,
            secondary: secondary,
            tertiary: nil,
            providerCost: nil,
            details: details,
            updatedAt: Date(),
            identity: identity)
    }

    public static func fetchGitHubUsername(token: String) async throws -> String {
        try await self.fetchGitHubIdentity(token: token).login
    }

    public static func fetchGitHubIdentity(
        token: String,
        transport: any ProviderHTTPTransport = ProviderHTTPClient.shared)
        async throws -> GitHubUserIdentity
    {
        guard let url = URL(string: "https://api.github.com/user") else {
            throw URLError(.badURL)
        }
        var request = URLRequest(url: url)
        request.setValue("token \(token)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Accept")

        let response = try await transport.response(for: request)
        switch response.statusCode {
        case 200:
            return try JSONDecoder().decode(GitHubUserIdentity.self, from: response.data)
        case 401, 403:
            throw URLError(.userAuthenticationRequired)
        default:
            throw URLError(.badServerResponse)
        }
    }

    private func addCommonHeaders(to request: inout URLRequest) {
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        request.setValue("vscode/1.96.2", forHTTPHeaderField: "Editor-Version")
        request.setValue("copilot-chat/0.26.7", forHTTPHeaderField: "Editor-Plugin-Version")
        request.setValue("GitHubCopilotChat/0.26.7", forHTTPHeaderField: "User-Agent")
        request.setValue("2025-04-01", forHTTPHeaderField: "X-Github-Api-Version")
    }

    static func makeRateWindow(
        from snapshot: CopilotUsageResponse.QuotaSnapshot?,
        resetsAt: Date? = nil) -> RateWindow?
    {
        guard let snapshot else { return nil }
        guard !snapshot.unlimited else { return nil }
        guard !snapshot.isPlaceholder else { return nil }
        guard snapshot.hasPercentRemaining else { return nil }
        let usedPercent = snapshot.usedPercent
        let overQuotaDescription = snapshot.overQuotaUsedPercent.map { used in
            String(format: "%.0f%% used", used)
        }

        return RateWindow(
            usedPercent: usedPercent,
            windowMinutes: nil,
            resetsAt: resetsAt,
            resetDescription: overQuotaDescription)
    }

    static func parseQuotaResetDate(_ value: String?) -> Date? {
        guard let raw = value?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty else {
            return nil
        }

        let fractionalISO = ISO8601DateFormatter()
        fractionalISO.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        if let date = fractionalISO.date(from: raw) {
            return date
        }

        let internetISO = ISO8601DateFormatter()
        internetISO.formatOptions = [.withInternetDateTime]
        if let date = internetISO.date(from: raw) {
            return date
        }

        let formatter = DateFormatter()
        formatter.calendar = Calendar(identifier: .gregorian)
        formatter.isLenient = false
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.dateFormat = "yyyy-MM-dd"
        return formatter.date(from: raw)
    }
}
