import Foundation

public struct UsageSummaryAccumulator {
    private let now: Date
    private var calendar: Calendar
    private let today: Date
    private let month: DateComponents
    private let trendWindows: [TrendMode: TrendWindow]

    private var providerTotals: [Provider: TokenBucket]
    private var todayProviderTotals: [Provider: TokenBucket]
    private var dailyBuckets: [Date: [Provider: Int]] = [:]
    private var dailyDetails: [Date: DailyUsageDetail] = [:]
    private var trendBuckets: [TrendMode: [Date: [Provider: Int]]] = [:]
    private var todayTokens = 0
    private var monthTokens = 0
    private var totalTokens = 0
    private var cachedTokens = 0
    private var sessions = Set<String>()

    public init(
        now: Date = Date(),
        calendar baseCalendar: Calendar = Calendar.current
    ) {
        self.now = now
        var calendar = baseCalendar
        calendar.firstWeekday = 2
        self.calendar = calendar
        today = calendar.startOfDay(for: now)
        month = calendar.dateComponents([.year, .month], from: now)
        providerTotals = Dictionary(uniqueKeysWithValues: Provider.allCases.map {
            ($0, TokenBucket(provider: $0))
        })
        todayProviderTotals = Dictionary(uniqueKeysWithValues: Provider.allCases.map {
            ($0, TokenBucket(provider: $0))
        })
        trendWindows = Dictionary(uniqueKeysWithValues: TrendMode.allCases.map { mode in
            let reference = mode.periodStart(for: now, calendar: calendar)
            return (mode, TrendWindow(
                reference: reference,
                start: mode.startDate(reference: reference, calendar: calendar),
                component: mode.calendarComponent
            ))
        })
    }

    public mutating func add(_ record: UsageRecord) {
        add(
            provider: record.provider,
            sourcePath: record.sourcePath,
            timestamp: record.timestamp,
            sessionID: record.sessionID,
            inputTokens: record.inputTokens,
            outputTokens: record.outputTokens,
            cachedTokens: record.cachedTokens,
            reasoningTokens: record.reasoningTokens,
            toolTokens: record.toolTokens,
            totalTokens: record.totalTokens
        )
    }

    public mutating func add(
        provider: Provider,
        sourcePath: String,
        timestamp: Date,
        sessionID: String,
        inputTokens: Int,
        outputTokens: Int,
        cachedTokens: Int,
        reasoningTokens: Int,
        toolTokens: Int,
        totalTokens: Int
    ) {
        self.totalTokens += totalTokens
        self.cachedTokens += cachedTokens
        sessions.insert(sessionID.isEmpty ? sourcePath : sessionID)
        providerTotals[provider, default: TokenBucket(provider: provider)].add(
            inputTokens: inputTokens,
            outputTokens: outputTokens,
            cachedTokens: cachedTokens,
            reasoningTokens: reasoningTokens,
            toolTokens: toolTokens,
            totalTokens: totalTokens
        )

        let recordDay = calendar.startOfDay(for: timestamp)
        dailyBuckets[recordDay, default: Provider.zeroTokenMap][provider, default: 0] += totalTokens
        if dailyDetails[recordDay] == nil {
            dailyDetails[recordDay] = DailyUsageDetail(date: recordDay)
        }
        dailyDetails[recordDay]?.add(
            provider: provider,
            inputTokens: inputTokens,
            outputTokens: outputTokens,
            cachedTokens: cachedTokens,
            totalTokens: totalTokens
        )

        if recordDay == today {
            todayTokens += totalTokens
            todayProviderTotals[provider, default: TokenBucket(provider: provider)].add(
                inputTokens: inputTokens,
                outputTokens: outputTokens,
                cachedTokens: cachedTokens,
                reasoningTokens: reasoningTokens,
                toolTokens: toolTokens,
                totalTokens: totalTokens
            )
        }

        let recordMonth = calendar.dateComponents([.year, .month], from: timestamp)
        if recordMonth.year == month.year && recordMonth.month == month.month {
            monthTokens += totalTokens
        }

        for mode in TrendMode.allCases {
            guard let window = trendWindows[mode] else { continue }
            let period = mode.periodStart(for: timestamp, calendar: calendar)
            guard period >= window.start && period <= window.reference else { continue }
            trendBuckets[mode, default: [:]][period, default: Provider.zeroTokenMap][provider, default: 0] += totalTokens
        }
    }

    public func makeSummary() -> UsageSummary {
        let dailyTrend = dailyBuckets
            .sorted { $0.key < $1.key }
            .suffix(30)
            .map { DailyPoint(date: $0.key, values: $0.value) }

        let sortedDailyDetails = dailyDetails.values.sorted { $0.date > $1.date }
        let trendPointsByMode = Dictionary(uniqueKeysWithValues: TrendMode.allCases.map { mode in
            (mode, makeTrendPoints(mode: mode))
        })

        let toolRows = Provider.allCases.compactMap { provider -> ToolRow? in
            guard let bucket = providerTotals[provider], bucket.totalTokens > 0 else { return nil }
            let trendTokens = dailyTrend.map { $0.values[provider, default: 0] }
            return ToolRow(
                provider: provider,
                inputTokens: bucket.inputTokens,
                outputTokens: bucket.outputTokens,
                cachedTokens: bucket.cachedTokens,
                totalTokens: bucket.totalTokens,
                trendTokens: trendTokens
            )
        }
        .sorted { $0.totalTokens > $1.totalTokens }

        let cacheHitRate = totalTokens > 0 ? Int((Double(cachedTokens) / Double(totalTokens) * 100).rounded()) : 0
        let cards = SummaryCards(
            todayTokens: todayTokens,
            monthTokens: monthTokens,
            totalTokens: totalTokens,
            cacheHitRate: cacheHitRate,
            sessionCount: sessions.count
        )

        return UsageSummary(
            generatedAt: now,
            records: [],
            cards: cards,
            providerTotals: providerTotals,
            todayProviderTotals: todayProviderTotals,
            dailyTrend: Array(dailyTrend),
            trendPointsByMode: trendPointsByMode,
            dailyDetails: sortedDailyDetails,
            toolRows: toolRows,
            insights: UsageSummary.makeInsights(
                providerTotals: providerTotals,
                todayTokens: todayTokens,
                cacheHitRate: cacheHitRate
            )
        )
    }

    private func makeTrendPoints(mode: TrendMode) -> [DailyPoint] {
        guard let window = trendWindows[mode] else { return [] }
        let buckets = trendBuckets[mode, default: [:]]
        var points: [DailyPoint] = []
        var cursor = window.start
        while cursor <= window.reference {
            points.append(DailyPoint(date: cursor, values: buckets[cursor, default: Provider.zeroTokenMap]))
            guard let next = calendar.date(byAdding: window.component, value: 1, to: cursor) else { break }
            cursor = next
        }
        return points
    }
}

private struct TrendWindow {
    let reference: Date
    let start: Date
    let component: Calendar.Component
}

public extension UsageSummary {
    static func make(
        records: [UsageRecord],
        now: Date = Date(),
        calendar baseCalendar: Calendar = Calendar.current
    ) -> UsageSummary {
        var accumulator = UsageSummaryAccumulator(now: now, calendar: baseCalendar)
        for record in records {
            accumulator.add(record)
        }
        return accumulator.makeSummary()
    }

    static func makeInsights(
        providerTotals: [Provider: TokenBucket],
        todayTokens: Int,
        cacheHitRate: Int
    ) -> [Insight] {
        let top = providerTotals.values
            .filter { $0.totalTokens > 0 }
            .sorted { $0.totalTokens > $1.totalTokens }
            .first

        var insights: [Insight] = []
        if let top, let provider = top.provider {
            insights.append(Insight(
                tone: .warning,
                title: "\(provider.rawValue) 是当前主要用量来源",
                message: "已从本地日志统计 \(TokenFormatter.format(top.totalTokens)) Tokens。"
            ))
        }
        insights.append(Insight(
            tone: .positive,
            title: "检测到缓存 Tokens",
            message: "当前 \(cacheHitRate)% 的统计用量来自缓存上下文。"
        ))
        insights.append(Insight(
            tone: .accent,
            title: "今日用量快照已更新",
            message: "当前本地日期已统计 \(TokenFormatter.format(todayTokens)) Tokens。"
        ))
        return insights
    }

    func trendPoints(
        mode: TrendMode,
        calendar baseCalendar: Calendar = Calendar.current
    ) -> [DailyPoint] {
        if let points = trendPointsByMode[mode] {
            return points
        }
        return []
    }

    func dailyUsageDetails(calendar baseCalendar: Calendar = Calendar.current) -> [DailyUsageDetail] {
        dailyDetails
    }

    func updatingGeneratedAt(_ date: Date) -> UsageSummary {
        UsageSummary(
            generatedAt: date,
            records: records,
            cards: cards,
            providerTotals: providerTotals,
            todayProviderTotals: todayProviderTotals,
            dailyTrend: dailyTrend,
            trendPointsByMode: trendPointsByMode,
            dailyDetails: dailyDetails,
            toolRows: toolRows,
            insights: insights
        )
    }
}

extension Provider {
    static var zeroTokenMap: [Provider: Int] {
        Dictionary(uniqueKeysWithValues: Provider.allCases.map { ($0, 0) })
    }
}

extension TrendMode {
    var calendarComponent: Calendar.Component {
        switch self {
        case .daily:
            return .hour
        case .weekly:
            return .day
        case .monthly:
            return .day
        case .yearly:
            return .month
        }
    }

    var periodColumn: String {
        switch self {
        case .daily:
            return "hour_start"
        case .weekly, .monthly:
            return "day_start"
        case .yearly:
            return "month_start"
        }
    }

    func periodStart(for date: Date, calendar: Calendar) -> Date {
        switch self {
        case .daily:
            let components = calendar.dateComponents([.year, .month, .day, .hour], from: date)
            return calendar.date(from: components) ?? calendar.startOfDay(for: date)
        case .weekly:
            return calendar.startOfDay(for: date)
        case .monthly:
            return calendar.startOfDay(for: date)
        case .yearly:
            let components = calendar.dateComponents([.year, .month], from: date)
            return calendar.date(from: components) ?? calendar.startOfDay(for: date)
        }
    }

    func startDate(reference: Date, calendar: Calendar) -> Date {
        switch self {
        case .daily:
            return calendar.startOfDay(for: reference)
        case .weekly:
            return calendar.dateInterval(of: .weekOfYear, for: reference)?.start ?? calendar.startOfDay(for: reference)
        case .monthly:
            let components = calendar.dateComponents([.year, .month], from: reference)
            return calendar.date(from: components) ?? calendar.startOfDay(for: reference)
        case .yearly:
            let components = calendar.dateComponents([.year], from: reference)
            return calendar.date(from: components) ?? calendar.startOfDay(for: reference)
        }
    }
}
