import Foundation

public struct ClaudeAdminAPIUsageSnapshot: Codable, Equatable, Sendable {
    public struct DailyBucket: Codable, Equatable, Sendable, Identifiable {
        public let day: String
        public let startTime: Date
        public let endTime: Date
        public let costUSD: Double
        public let inputTokens: Int
        public let cacheCreationInputTokens: Int
        public let cacheReadInputTokens: Int
        public let outputTokens: Int
        public let totalTokens: Int
        public let costItems: [CostBreakdown]
        public let models: [ModelBreakdown]

        public var id: String {
            self.day
        }

        public init(
            day: String,
            startTime: Date,
            endTime: Date,
            costUSD: Double,
            inputTokens: Int,
            cacheCreationInputTokens: Int,
            cacheReadInputTokens: Int,
            outputTokens: Int,
            totalTokens: Int,
            costItems: [CostBreakdown],
            models: [ModelBreakdown])
        {
            self.day = day
            self.startTime = startTime
            self.endTime = endTime
            self.costUSD = costUSD
            self.inputTokens = inputTokens
            self.cacheCreationInputTokens = cacheCreationInputTokens
            self.cacheReadInputTokens = cacheReadInputTokens
            self.outputTokens = outputTokens
            self.totalTokens = totalTokens
            self.costItems = costItems
            self.models = models
        }
    }

    public struct CostBreakdown: Codable, Equatable, Sendable, Identifiable {
        public let name: String
        public let costUSD: Double

        public var id: String {
            self.name
        }

        public init(name: String, costUSD: Double) {
            self.name = name
            self.costUSD = costUSD
        }
    }

    public struct ModelBreakdown: Codable, Equatable, Sendable, Identifiable {
        public let name: String
        public let inputTokens: Int
        public let cacheCreationInputTokens: Int
        public let cacheReadInputTokens: Int
        public let outputTokens: Int
        public let totalTokens: Int

        public var id: String {
            self.name
        }

        public init(
            name: String,
            inputTokens: Int,
            cacheCreationInputTokens: Int,
            cacheReadInputTokens: Int,
            outputTokens: Int,
            totalTokens: Int)
        {
            self.name = name
            self.inputTokens = inputTokens
            self.cacheCreationInputTokens = cacheCreationInputTokens
            self.cacheReadInputTokens = cacheReadInputTokens
            self.outputTokens = outputTokens
            self.totalTokens = totalTokens
        }
    }

    public struct Summary: Equatable, Sendable {
        public let costUSD: Double
        public let inputTokens: Int
        public let cacheCreationInputTokens: Int
        public let cacheReadInputTokens: Int
        public let outputTokens: Int
        public let totalTokens: Int

        public init(
            costUSD: Double,
            inputTokens: Int,
            cacheCreationInputTokens: Int,
            cacheReadInputTokens: Int,
            outputTokens: Int,
            totalTokens: Int)
        {
            self.costUSD = costUSD
            self.inputTokens = inputTokens
            self.cacheCreationInputTokens = cacheCreationInputTokens
            self.cacheReadInputTokens = cacheReadInputTokens
            self.outputTokens = outputTokens
            self.totalTokens = totalTokens
        }
    }

    public let daily: [DailyBucket]
    public let updatedAt: Date

    public init(daily: [DailyBucket], updatedAt: Date) {
        self.daily = daily.sorted { $0.startTime < $1.startTime }
        self.updatedAt = updatedAt
    }

    public var last30Days: Summary {
        self.summary(days: 30)
    }

    public var last7Days: Summary {
        self.summary(days: 7)
    }

    public var currentDay: Summary {
        self.summary(forLocalDayContaining: self.updatedAt)
    }

    public var latestDay: Summary {
        self.summary(days: 1)
    }

    public func summary(forLocalDayContaining date: Date, calendar _: Calendar = .current) -> Summary {
        let selected = self.daily.filter { bucket in
            CostUsageBucketInterval.contains(
                date,
                startTime: bucket.startTime,
                endTime: bucket.endTime)
        }
        return Summary(
            costUSD: selected.reduce(0) { $0 + $1.costUSD },
            inputTokens: selected.reduce(0) { $0 + $1.inputTokens },
            cacheCreationInputTokens: selected.reduce(0) { $0 + $1.cacheCreationInputTokens },
            cacheReadInputTokens: selected.reduce(0) { $0 + $1.cacheReadInputTokens },
            outputTokens: selected.reduce(0) { $0 + $1.outputTokens },
            totalTokens: selected.reduce(0) { $0 + $1.totalTokens })
    }

    public func summary(days: Int) -> Summary {
        let selected = self.daily.suffix(max(1, days))
        return Summary(
            costUSD: selected.reduce(0) { $0 + $1.costUSD },
            inputTokens: selected.reduce(0) { $0 + $1.inputTokens },
            cacheCreationInputTokens: selected.reduce(0) { $0 + $1.cacheCreationInputTokens },
            cacheReadInputTokens: selected.reduce(0) { $0 + $1.cacheReadInputTokens },
            outputTokens: selected.reduce(0) { $0 + $1.outputTokens },
            totalTokens: selected.reduce(0) { $0 + $1.totalTokens })
    }

    public var topModels: [ModelBreakdown] {
        var totals: [String: ModelAccumulator] = [:]
        for day in self.daily {
            for model in day.models {
                totals[model.name, default: ModelAccumulator()].add(model)
            }
        }
        return totals
            .map { name, total in total.makeModel(name: name) }
            .sorted {
                if $0.totalTokens == $1.totalTokens { return $0.name < $1.name }
                return $0.totalTokens > $1.totalTokens
            }
    }

    public var topCostItems: [CostBreakdown] {
        var totals: [String: Double] = [:]
        for day in self.daily {
            for item in day.costItems {
                totals[item.name, default: 0] += item.costUSD
            }
        }
        return totals
            .map { CostBreakdown(name: $0.key, costUSD: $0.value) }
            .sorted {
                if $0.costUSD == $1.costUSD { return $0.name < $1.name }
                return $0.costUSD > $1.costUSD
            }
    }

    public func toUsageSnapshot() -> UsageSnapshot {
        let total = self.last30Days
        let today = self.currentDay
        let seven = self.last7Days
        var summaryRows: [ProviderDetailSection.Row] = [
            .makeRow(label: "Today spend", value: UsageFormatter.usdString(today.costUSD)),
            .makeRow(label: "7d spend", value: UsageFormatter.usdString(seven.costUSD)),
            .makeRow(label: "30d spend", value: UsageFormatter.usdString(total.costUSD)),
            .makeRow(label: "Today tokens", value: Self.countString(today.totalTokens)),
            .makeRow(label: "30d tokens", value: Self.countString(total.totalTokens)),
            .makeRow(label: "Cache read", value: Self.countString(total.cacheReadInputTokens)),
        ]
        if let topModel = self.topModels.first {
            summaryRows.append(.makeRow(label: "Top model", value: topModel.name))
        }
        var details: [ProviderDetailSection] = [.makeSection(
            title: "Usage summary",
            rows: summaryRows,
            chart: self.daily.isEmpty ? nil : .makeChart(
                title: "Daily spend",
                unit: "USD",
                points: self.daily.map { ($0.day, $0.costUSD) }))]
        if !self.topCostItems.isEmpty {
            details.append(.makeSection(title: "Cost items", rows: self.topCostItems.prefix(20).map {
                .makeRow(label: $0.name, value: UsageFormatter.usdString($0.costUSD))
            }))
        }
        return UsageSnapshot(
            primary: nil,
            secondary: nil,
            providerCost: ProviderCostSnapshot(
                used: total.costUSD,
                limit: 0,
                currencyCode: "USD",
                period: "Last 30 days",
                updatedAt: self.updatedAt),
            details: details,
            updatedAt: self.updatedAt,
            identity: ProviderIdentitySnapshot(
                providerID: .claude,
                accountEmail: nil,
                accountOrganization: nil,
                loginMethod: "Admin API"))
    }

    private static func countString(_ value: Int) -> String {
        value.formatted(.number.grouping(.automatic).locale(Locale(identifier: "en_US")))
    }

    private struct ModelAccumulator {
        var inputTokens = 0
        var cacheCreationInputTokens = 0
        var cacheReadInputTokens = 0
        var outputTokens = 0
        var totalTokens = 0

        mutating func add(_ model: ModelBreakdown) {
            self.inputTokens += model.inputTokens
            self.cacheCreationInputTokens += model.cacheCreationInputTokens
            self.cacheReadInputTokens += model.cacheReadInputTokens
            self.outputTokens += model.outputTokens
            self.totalTokens += model.totalTokens
        }

        func makeModel(name: String) -> ModelBreakdown {
            ModelBreakdown(
                name: name,
                inputTokens: self.inputTokens,
                cacheCreationInputTokens: self.cacheCreationInputTokens,
                cacheReadInputTokens: self.cacheReadInputTokens,
                outputTokens: self.outputTokens,
                totalTokens: self.totalTokens)
        }
    }
}
