// LoopFollow
// SimpleStatsViewModel.swift

import Combine
import Foundation

class SimpleStatsViewModel: ObservableObject {
    @Published var gmi: Double?
    @Published var avgGlucose: Double?
    @Published var stdDeviation: Double?
    @Published var coefficientOfVariation: Double?
    @Published var totalDailyDose: Double?
    @Published var programmedBasal: Double?
    @Published var actualBasal: Double?
    @Published var avgBolus: Double?
    @Published var avgCarbs: Double?
    @Published var totalNegativeBasal: Double?
    @Published var totalPositiveBasal: Double?

    private let dataService: StatsDataService

    init(dataService: StatsDataService) {
        self.dataService = dataService
    }

    func clearStats() {
        gmi = nil
        avgGlucose = nil
        stdDeviation = nil
        coefficientOfVariation = nil
        totalDailyDose = nil
        programmedBasal = nil
        actualBasal = nil
        avgBolus = nil
        avgCarbs = nil
        totalNegativeBasal = nil
        totalPositiveBasal = nil
    }

    func calculateStats() {
        let unitSettings = UnitSettingsStore.shared
        let bgData = dataService.getBGData()
        guard !bgData.isEmpty else { return }

        let totalGlucose = bgData.reduce(0) { $0 + $1.sgv }
        let avgBGmgdL = Double(totalGlucose) / Double(bgData.count)
        avgGlucose = unitSettings.convertMgdlToDisplay(avgBGmgdL)

        let variance = bgData.reduce(0.0) { sum, reading in
            let diff = Double(reading.sgv) - avgBGmgdL
            return sum + (diff * diff)
        }
        let stdDevMgdL = sqrt(variance / Double(bgData.count))
        stdDeviation = unitSettings.convertMgdlToDisplay(stdDevMgdL)

        gmi = GlycemicMetricCalculator.calculateGMI(avgGlucoseInDisplayUnits: avgGlucose)

        if avgBGmgdL > 0 {
            coefficientOfVariation = (stdDevMgdL / avgBGmgdL) * 100.0
        } else {
            coefficientOfVariation = nil
        }

        let bolusesInPeriod = dataService.getBolusData()
        let smbInPeriod = dataService.getSMBData()
        let bolusTotal = bolusesInPeriod.reduce(0.0) { $0 + $1.value }
        let smbTotal = smbInPeriod.reduce(0.0) { $0 + $1.value }
        let totalBolusInPeriod = bolusTotal + smbTotal

        let cutoffTime = dataService.startDate.timeIntervalSince1970
        let allBolusDates = (bolusesInPeriod + smbInPeriod).map { $0.date }.filter { $0 >= cutoffTime }
        let actualDays = calculateActualDaysCovered(dates: allBolusDates, requestedDays: dataService.daysToAnalyze)

        if actualDays > 0 {
            avgBolus = totalBolusInPeriod / Double(actualDays)
        } else {
            avgBolus = nil
        }

        let carbsInPeriod = dataService.getCarbData()

        let calendar = dateTimeUtils.displayCalendar()
        var dailyCarbs: [Date: Double] = [:]

        for carb in carbsInPeriod {
            let carbDate = Date(timeIntervalSince1970: carb.date)
            let dayStart = calendar.startOfDay(for: carbDate)

            if dailyCarbs[dayStart] == nil {
                dailyCarbs[dayStart] = 0.0
            }
            dailyCarbs[dayStart]? += carb.value
        }

        let totalCarbsInPeriod = dailyCarbs.values.reduce(0.0, +)

        let daysWithData = dataService.daysToAnalyze

        if daysWithData > 0 {
            avgCarbs = totalCarbsInPeriod / Double(daysWithData)
        } else {
            avgCarbs = nil
        }

        let basalProfile = dataService.getBasalProfile()
        let dailyProgrammedBasal = calculateProgrammedBasalFromProfile(basalProfile: basalProfile)
        programmedBasal = dailyProgrammedBasal

        // Calculate actual basal using temp basal adjustments
        let tempBasalData = dataService.getTempBasalData()
        let (totalActualBasal, actualBasalDays, totalPositiveAdjustment, totalNegativeAdjustment) = calculateActualBasalFromTempBasals(
            tempBasals: tempBasalData,
            basalProfile: basalProfile,
            dailyProgrammedBasal: dailyProgrammedBasal
        )

        if actualBasalDays > 0 {
            totalPositiveBasal = totalPositiveAdjustment / Double(actualBasalDays)
            totalNegativeBasal = totalNegativeAdjustment / Double(actualBasalDays)
        } else {
            totalPositiveBasal = nil
            totalNegativeBasal = nil
        }

        var avgDailyBolus = 0.0
        var avgDailyBasal = 0.0

        if actualDays > 0 {
            avgDailyBolus = totalBolusInPeriod / Double(actualDays)
        }

        if actualBasalDays > 0 {
            avgDailyBasal = totalActualBasal / Double(actualBasalDays)
            actualBasal = avgDailyBasal
        } else {
            actualBasal = nil
        }

        if actualDays > 0 || actualBasalDays > 0 {
            totalDailyDose = avgDailyBolus + avgDailyBasal
        } else {
            totalDailyDose = nil
        }
    }

    /// Calculates actual basal delivered using:
    /// Actual = Programmed Basal + Sum of all temp basal adjustments
    /// Where adjustment = (temp_rate - scheduled_rate) * duration
    private func calculateActualBasalFromTempBasals(
        tempBasals: [StatsDataService.TempBasalEntry],
        basalProfile: [MainViewController.basalProfileStruct],
        dailyProgrammedBasal: Double
    ) -> (totalBasal: Double, daysWithData: Int, totalPositiveAdjustment: Double, totalNegativeAdjustment: Double) {
        let cutoffTime = dataService.startDate.timeIntervalSince1970
        let endTime = dataService.endDate.timeIntervalSince1970

        // Filter temp basals to the analysis period
        let relevantTempBasals = tempBasals.filter { tempBasal in
            let tempEnd = tempBasal.startTime + (tempBasal.durationMinutes * 60)
            return tempEnd > cutoffTime && tempBasal.startTime < endTime
        }

        guard !relevantTempBasals.isEmpty else {
            // No temp basals - return programmed basal for the period
            return (dailyProgrammedBasal, dataService.daysToAnalyze, 0.0, 0.0)
        }

        // Calculate total adjustment from all temp basals
        var totalAdjustment = 0.0
        var totalPositiveAdjustment = 0.0
        var totalNegativeAdjustment = 0.0

        for tempBasal in relevantTempBasals {
            // Clamp temp basal to analysis window
            let effectiveStart = max(tempBasal.startTime, cutoffTime)
            let effectiveEnd = min(tempBasal.startTime + (tempBasal.durationMinutes * 60), endTime)

            guard effectiveEnd > effectiveStart else { continue }

            // For each segment of this temp basal, calculate the adjustment
            // We need to handle cases where the temp basal spans multiple profile segments
            let adjustment = calculateAdjustmentForTempBasal(
                tempRate: tempBasal.rate,
                startTime: effectiveStart,
                endTime: effectiveEnd,
                profile: basalProfile
            )
            totalAdjustment += adjustment

            if adjustment > 0 {
                totalPositiveAdjustment += adjustment
            } else if adjustment < 0 {
                totalNegativeAdjustment += adjustment
            }
        }

        // Calculate days with data
        let calendar = dateTimeUtils.displayCalendar()
        var uniqueDays = Set<Date>()
        for tempBasal in relevantTempBasals {
            let dateObj = Date(timeIntervalSince1970: tempBasal.startTime)
            let dayStart = calendar.startOfDay(for: dateObj)
            uniqueDays.insert(dayStart)
        }
        let daysWithData = min(uniqueDays.count, dataService.daysToAnalyze)

        // Actual = Programmed + Adjustments
        let totalActualBasal = (dailyProgrammedBasal * Double(daysWithData)) + totalAdjustment

        return (totalActualBasal, daysWithData, totalPositiveAdjustment, totalNegativeAdjustment)
    }

    /// Calculates the adjustment (positive or negative) for a single temp basal
    /// Adjustment = (temp_rate - scheduled_rate) * duration_hours
    private func calculateAdjustmentForTempBasal(
        tempRate: Double,
        startTime: TimeInterval,
        endTime: TimeInterval,
        profile: [MainViewController.basalProfileStruct]
    ) -> Double {
        guard !profile.isEmpty, endTime > startTime else { return 0.0 }

        var totalAdjustment = 0.0
        let sortedProfile = profile.sorted { $0.timeAsSeconds < $1.timeAsSeconds }
        let calendar = dateTimeUtils.displayCalendar()

        var currentTime = startTime
        while currentTime < endTime {
            let currentDate = Date(timeIntervalSince1970: currentTime)
            let dayStart = calendar.startOfDay(for: currentDate).timeIntervalSince1970
            let nextDayStart = dayStart + 24 * 60 * 60

            // Process each profile segment for this day
            for i in 0 ..< sortedProfile.count {
                let scheduledRate = sortedProfile[i].value
                let segmentStartInDay = dayStart + sortedProfile[i].timeAsSeconds

                let segmentEndInDay: TimeInterval
                if i < sortedProfile.count - 1 {
                    segmentEndInDay = dayStart + sortedProfile[i + 1].timeAsSeconds
                } else {
                    segmentEndInDay = nextDayStart
                }

                // Calculate overlap between this profile segment and the temp basal
                let overlapStart = max(currentTime, segmentStartInDay)
                let overlapEnd = min(endTime, segmentEndInDay)

                if overlapEnd > overlapStart {
                    let durationHours = (overlapEnd - overlapStart) / 3600.0
                    // Adjustment = (temp_rate - scheduled_rate) * duration
                    let adjustment = (tempRate - scheduledRate) * durationHours
                    totalAdjustment += adjustment
                }
            }

            // Move to next day
            currentTime = nextDayStart
        }

        return totalAdjustment
    }

    private func getScheduledBasalRate(for time: TimeInterval, profile: [MainViewController.basalProfileStruct]) -> Double {
        guard !profile.isEmpty else { return 0.0 }

        let calendar = dateTimeUtils.displayCalendar()
        let date = Date(timeIntervalSince1970: time)
        let components = calendar.dateComponents([.hour, .minute, .second], from: date)

        let hours = components.hour ?? 0
        let minutes = components.minute ?? 0
        let seconds = components.second ?? 0
        let secondsSinceMidnight = Double(hours * 3600 + minutes * 60 + seconds)

        let sortedProfile = profile.sorted { $0.timeAsSeconds < $1.timeAsSeconds }

        for i in 0 ..< sortedProfile.count {
            let current = sortedProfile[i]
            let nextTime: Double
            if i < sortedProfile.count - 1 {
                nextTime = sortedProfile[i + 1].timeAsSeconds
            } else {
                nextTime = 24 * 60 * 60
            }

            if secondsSinceMidnight >= current.timeAsSeconds && secondsSinceMidnight < nextTime {
                return current.value
            }
        }

        return sortedProfile.first?.value ?? 0.0
    }

    private func calculateActualDaysCovered(dates: [TimeInterval], requestedDays: Int) -> Int {
        guard !dates.isEmpty else { return requestedDays }

        let calendar = dateTimeUtils.displayCalendar()
        let cutoffTime = dataService.startDate.timeIntervalSince1970
        let filteredDates = dates.filter { $0 >= cutoffTime }

        var uniqueDays = Set<Date>()
        for date in filteredDates {
            let dateObj = Date(timeIntervalSince1970: date)
            let dayStart = calendar.startOfDay(for: dateObj)
            uniqueDays.insert(dayStart)
        }

        return min(uniqueDays.count, requestedDays)
    }

    private func calculateProgrammedBasalFromProfile(basalProfile: [MainViewController.basalProfileStruct]) -> Double {
        guard !basalProfile.isEmpty else { return 0.0 }

        let sortedProfile = basalProfile.sorted { $0.timeAsSeconds < $1.timeAsSeconds }

        var totalBasal = 0.0
        let secondsInDay = 24 * 60 * 60

        for i in 0 ..< sortedProfile.count {
            let current = sortedProfile[i]
            let currentTime = Double(current.timeAsSeconds)

            let nextTime: Double
            if i < sortedProfile.count - 1 {
                nextTime = Double(sortedProfile[i + 1].timeAsSeconds)
            } else {
                nextTime = Double(secondsInDay)
            }

            let durationHours = (nextTime - currentTime) / 3600.0
            totalBasal += current.value * durationHours
        }

        return totalBasal
    }
}
