//
//  MoodDataSummarizer.swift
//  Reflect
//
//  Created by Claude Code on 12/13/24.
//

import Foundation

/// Summary of mood data for a specific time period, formatted for AI consumption
struct MoodDataSummary {
    let periodName: String
    let totalEntries: Int
    let dateRange: String

    // Mood distribution
    let moodCounts: [String: Int]
    let moodPercentages: [String: Int]
    let averageMoodScore: Double

    // Temporal patterns
    let weekdayAverages: [String: Double]
    let weekendAverage: Double
    let weekdayAverage: Double
    let bestDayOfWeek: String
    let worstDayOfWeek: String

    // Trends
    let recentTrend: String
    let trendMagnitude: Double

    // Streaks
    let currentLoggingStreak: Int
    let longestLoggingStreak: Int
    let longestPositiveStreak: Int
    let longestNegativeStreak: Int

    // Variability
    let moodSwingCount: Int
    let moodStabilityScore: Double

    // Recent context
    let last7DaysAverage: Double
    let last7DaysMoods: [String]

    // Notable observations
    let hasAllMoodTypes: Bool
    let missingMoodTypes: [String]

    // Health data for AI analysis (optional)
    let healthAverages: HealthService.HealthAverages?
}

/// Transforms raw MoodEntryModel data into AI-optimized summaries
class MoodDataSummarizer {
    private let calendar = Calendar.current
    private let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        return formatter
    }()

    // MARK: - Main Summarization

    func summarize(entries: [MoodEntryModel], periodName: String, healthAverages: HealthService.HealthAverages? = nil) -> MoodDataSummary {
        let validEntries = entries.filter { ![.missing, .placeholder].contains($0.mood) }

        guard !validEntries.isEmpty else {
            return emptyDataSummary(periodName: periodName)
        }

        let sortedEntries = validEntries.sorted { $0.forDate < $1.forDate }

        // Calculate all metrics
        let moodDistribution = calculateMoodDistribution(entries: validEntries)
        let temporalPatterns = calculateTemporalPatterns(entries: validEntries)
        let trend = calculateTrend(entries: sortedEntries)
        let streaks = calculateStreaks(entries: sortedEntries)
        let variability = calculateVariability(entries: sortedEntries)
        let recentContext = calculateRecentContext(entries: sortedEntries)
        let moodTypes = calculateMoodTypes(entries: validEntries)

        // Format date range
        let dateRange = formatDateRange(entries: sortedEntries)

        return MoodDataSummary(
            periodName: periodName,
            totalEntries: validEntries.count,
            dateRange: dateRange,
            moodCounts: moodDistribution.counts,
            moodPercentages: moodDistribution.percentages,
            averageMoodScore: moodDistribution.average,
            weekdayAverages: temporalPatterns.weekdayAverages,
            weekendAverage: temporalPatterns.weekendAverage,
            weekdayAverage: temporalPatterns.weekdayAverage,
            bestDayOfWeek: temporalPatterns.bestDay,
            worstDayOfWeek: temporalPatterns.worstDay,
            recentTrend: trend.direction,
            trendMagnitude: trend.magnitude,
            currentLoggingStreak: streaks.current,
            longestLoggingStreak: streaks.longest,
            longestPositiveStreak: streaks.longestPositive,
            longestNegativeStreak: streaks.longestNegative,
            moodSwingCount: variability.swingCount,
            moodStabilityScore: variability.stabilityScore,
            last7DaysAverage: recentContext.average,
            last7DaysMoods: recentContext.moods,
            hasAllMoodTypes: moodTypes.hasAll,
            missingMoodTypes: moodTypes.missing,
            healthAverages: healthAverages
        )
    }

    // MARK: - Mood Distribution

    private func calculateMoodDistribution(entries: [MoodEntryModel]) -> (counts: [String: Int], percentages: [String: Int], average: Double) {
        var counts: [String: Int] = [:]
        var totalScore = 0

        for entry in entries {
            let moodName = entry.mood.widgetDisplayName.lowercased()
            counts[moodName, default: 0] += 1
            // Use 1-5 scale (add 1 to raw 0-4 values) for human-readable averages
            totalScore += Int(entry.moodValue) + 1
        }

        var percentages: [String: Int] = [:]
        for (mood, count) in counts {
            percentages[mood] = Int((Double(count) / Double(entries.count)) * 100)
        }

        let average = Double(totalScore) / Double(entries.count)

        return (counts, percentages, average)
    }

    // MARK: - Temporal Patterns

    private func calculateTemporalPatterns(entries: [MoodEntryModel]) -> (weekdayAverages: [String: Double], weekendAverage: Double, weekdayAverage: Double, bestDay: String, worstDay: String) {
        let weekdayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
        var weekdayTotals: [Int: (total: Int, count: Int)] = [:]

        for entry in entries {
            let weekday = Int(entry.weekDay)
            let current = weekdayTotals[weekday, default: (0, 0)]
            // Use 1-5 scale (add 1 to raw 0-4 values)
            weekdayTotals[weekday] = (current.total + Int(entry.moodValue) + 1, current.count + 1)
        }

        var weekdayAverages: [String: Double] = [:]
        var bestDay = "Monday"
        var worstDay = "Monday"
        var bestAvg = -1.0
        var worstAvg = 6.0

        for (weekday, data) in weekdayTotals {
            let avg = Double(data.total) / Double(data.count)
            let dayName = weekdayNames[weekday - 1]
            weekdayAverages[dayName] = avg

            if avg > bestAvg {
                bestAvg = avg
                bestDay = dayName
            }
            if avg < worstAvg {
                worstAvg = avg
                worstDay = dayName
            }
        }

        // Weekend vs weekday (use 1-5 scale)
        let weekendEntries = entries.filter { [1, 7].contains(Int($0.weekDay)) }
        let weekdayEntries = entries.filter { ![1, 7].contains(Int($0.weekDay)) }

        let weekendAvg = weekendEntries.isEmpty ? 0 : Double(weekendEntries.reduce(0) { $0 + Int($1.moodValue) + 1 }) / Double(weekendEntries.count)
        let weekdayAvg = weekdayEntries.isEmpty ? 0 : Double(weekdayEntries.reduce(0) { $0 + Int($1.moodValue) + 1 }) / Double(weekdayEntries.count)

        return (weekdayAverages, weekendAvg, weekdayAvg, bestDay, worstDay)
    }

    // MARK: - Trend Analysis

    private func calculateTrend(entries: [MoodEntryModel]) -> (direction: String, magnitude: Double) {
        guard entries.count >= 4 else {
            return ("stable", 0.0)
        }

        let halfCount = entries.count / 2
        let firstHalf = Array(entries.prefix(halfCount))
        let secondHalf = Array(entries.suffix(halfCount))

        // Use 1-5 scale
        let firstAvg = Double(firstHalf.reduce(0) { $0 + Int($1.moodValue) + 1 }) / Double(firstHalf.count)
        let secondAvg = Double(secondHalf.reduce(0) { $0 + Int($1.moodValue) + 1 }) / Double(secondHalf.count)

        let diff = secondAvg - firstAvg

        let direction: String
        if diff > 0.5 {
            direction = "improving"
        } else if diff < -0.5 {
            direction = "declining"
        } else {
            direction = "stable"
        }

        return (direction, abs(diff))
    }

    // MARK: - Streak Calculations

    private func calculateStreaks(entries: [MoodEntryModel]) -> (current: Int, longest: Int, longestPositive: Int, longestNegative: Int) {
        let sortedByDateDesc = entries.sorted { $0.forDate > $1.forDate }

        // Current logging streak
        var currentStreak = 0
        let today = calendar.startOfDay(for: Date())

        if let mostRecent = sortedByDateDesc.first?.forDate {
            let yesterday = calendar.date(byAdding: .day, value: -1, to: today)!

            if calendar.isDate(mostRecent, inSameDayAs: today) || calendar.isDate(mostRecent, inSameDayAs: yesterday) {
                currentStreak = 1
                var checkDate = calendar.date(byAdding: .day, value: -1, to: mostRecent)!

                for entry in sortedByDateDesc.dropFirst() {
                    if calendar.isDate(entry.forDate, inSameDayAs: checkDate) {
                        currentStreak += 1
                        checkDate = calendar.date(byAdding: .day, value: -1, to: checkDate)!
                    } else {
                        break
                    }
                }
            }
        }

        // Longest logging streak
        var longestStreak = 1
        var tempStreak = 1
        let sortedByDateAsc = entries.sorted { $0.forDate < $1.forDate }

        for i in 1..<sortedByDateAsc.count {
            let dayDiff = calendar.dateComponents([.day], from: sortedByDateAsc[i-1].forDate, to: sortedByDateAsc[i].forDate).day ?? 0
            if dayDiff == 1 {
                tempStreak += 1
                longestStreak = max(longestStreak, tempStreak)
            } else {
                tempStreak = 1
            }
        }

        // Positive mood streak (good/great)
        let longestPositive = calculateMoodStreak(entries: sortedByDateAsc, moods: [.good, .great])

        // Negative mood streak (bad/horrible)
        let longestNegative = calculateMoodStreak(entries: sortedByDateAsc, moods: [.bad, .horrible])

        return (currentStreak, longestStreak, longestPositive, longestNegative)
    }

    private func calculateMoodStreak(entries: [MoodEntryModel], moods: [Mood]) -> Int {
        guard !entries.isEmpty else { return 0 }

        // Sort by date to ensure proper ordering
        let sortedEntries = entries.sorted { $0.forDate < $1.forDate }

        var longest = 0
        var current = 0
        var previousDate: Date?

        for entry in sortedEntries {
            let entryDate = calendar.startOfDay(for: entry.forDate)

            // Check if this is a consecutive calendar day from the previous entry
            let isConsecutive: Bool
            if let prevDate = previousDate {
                let dayDiff = calendar.dateComponents([.day], from: prevDate, to: entryDate).day ?? 0
                isConsecutive = dayDiff == 1
            } else {
                isConsecutive = true // First entry starts a potential streak
            }

            if moods.contains(entry.mood) {
                if isConsecutive || previousDate == nil {
                    current += 1
                } else {
                    current = 1 // Reset to 1 (this entry starts new streak)
                }
                longest = max(longest, current)
            } else {
                current = 0
            }

            previousDate = entryDate
        }

        return longest
    }

    // MARK: - Variability

    private func calculateVariability(entries: [MoodEntryModel]) -> (swingCount: Int, stabilityScore: Double) {
        guard entries.count >= 2 else {
            return (0, 1.0)
        }

        var swings = 0
        for i in 1..<entries.count {
            let diff = abs(Int(entries[i].moodValue) - Int(entries[i-1].moodValue))
            if diff >= 2 {
                swings += 1
            }
        }

        let swingRate = Double(swings) / Double(entries.count - 1)
        let stabilityScore = 1.0 - min(swingRate, 1.0)

        return (swings, stabilityScore)
    }

    // MARK: - Recent Context

    private func calculateRecentContext(entries: [MoodEntryModel]) -> (average: Double, moods: [String]) {
        let recentEntries = entries.suffix(7)

        guard !recentEntries.isEmpty else {
            return (0, [])
        }

        // Use 1-5 scale
        let average = Double(recentEntries.reduce(0) { $0 + Int($1.moodValue) + 1 }) / Double(recentEntries.count)
        let moods = recentEntries.map { $0.mood.widgetDisplayName }

        return (average, moods)
    }

    // MARK: - Mood Types

    private func calculateMoodTypes(entries: [MoodEntryModel]) -> (hasAll: Bool, missing: [String]) {
        let allMoods: Set<Mood> = [.great, .good, .average, .bad, .horrible]
        let presentMoods = Set(entries.map { $0.mood })

        let missing = allMoods.subtracting(presentMoods).map { $0.widgetDisplayName }
        let hasAll = missing.isEmpty

        return (hasAll, missing)
    }

    // MARK: - Helpers

    private func formatDateRange(entries: [MoodEntryModel]) -> String {
        guard let first = entries.first, let last = entries.last else {
            return "No data"
        }

        let startDate = dateFormatter.string(from: first.forDate)
        let endDate = dateFormatter.string(from: last.forDate)

        return "\(startDate) - \(endDate)"
    }

    private func emptyDataSummary(periodName: String) -> MoodDataSummary {
        MoodDataSummary(
            periodName: periodName,
            totalEntries: 0,
            dateRange: "No data",
            moodCounts: [:],
            moodPercentages: [:],
            averageMoodScore: 0,
            weekdayAverages: [:],
            weekendAverage: 0,
            weekdayAverage: 0,
            bestDayOfWeek: "N/A",
            worstDayOfWeek: "N/A",
            recentTrend: "stable",
            trendMagnitude: 0,
            currentLoggingStreak: 0,
            longestLoggingStreak: 0,
            longestPositiveStreak: 0,
            longestNegativeStreak: 0,
            moodSwingCount: 0,
            moodStabilityScore: 1.0,
            last7DaysAverage: 0,
            last7DaysMoods: [],
            hasAllMoodTypes: false,
            missingMoodTypes: ["great", "good", "average", "bad", "horrible"],
            healthAverages: nil
        )
    }

    // MARK: - Prompt String Generation

    /// Generates a concise prompt string optimized for Apple's 4096 token context limit
    func toPromptString(_ summary: MoodDataSummary) -> String {
        // Compact format to stay under token limit
        var lines: [String] = []

        lines.append("Period: \(summary.periodName), \(summary.totalEntries) entries, avg \(String(format: "%.1f", summary.averageMoodScore))/5")

        // Mood distribution - compact
        let moodDist = summary.moodPercentages.sorted { $0.key < $1.key }
            .map { "\($0.key): \($0.value)%" }
            .joined(separator: ", ")
        lines.append("Moods: \(moodDist)")

        // Day patterns - only best/worst
        lines.append("Best day: \(summary.bestDayOfWeek), Worst: \(summary.worstDayOfWeek)")
        lines.append("Weekend avg: \(String(format: "%.1f", summary.weekendAverage)), Weekday avg: \(String(format: "%.1f", summary.weekdayAverage))")

        // Trends
        lines.append("Trend: \(summary.recentTrend), Last 7 days avg: \(String(format: "%.1f", summary.last7DaysAverage))")

        // Streaks - compact
        lines.append("Streaks - Current: \(summary.currentLoggingStreak)d, Longest: \(summary.longestLoggingStreak)d, Best positive: \(summary.longestPositiveStreak)d, Worst negative: \(summary.longestNegativeStreak)d")

        // Stability
        lines.append("Stability: \(String(format: "%.0f", summary.moodStabilityScore * 100))%, Mood swings: \(summary.moodSwingCount)")

        // Health data for AI analysis (if available)
        if let health = summary.healthAverages, health.hasData {
            lines.append("")
            lines.append("Apple Health data (\(health.daysWithHealthData) days with data):")

            // Activity metrics
            var activityMetrics: [String] = []
            if let steps = health.avgSteps {
                activityMetrics.append("Steps: \(steps.formatted())/day")
            }
            if let exercise = health.avgExerciseMinutes {
                activityMetrics.append("Exercise: \(exercise) min/day")
            }
            if let calories = health.avgActiveCalories {
                activityMetrics.append("Active cal: \(calories)/day")
            }
            if let distance = health.avgDistanceKm {
                activityMetrics.append("Distance: \(String(format: "%.1f", distance)) km/day")
            }
            if !activityMetrics.isEmpty {
                lines.append("Activity: \(activityMetrics.joined(separator: ", "))")
            }

            // Heart metrics
            var heartMetrics: [String] = []
            if let hr = health.avgHeartRate {
                heartMetrics.append("Avg HR: \(Int(hr)) bpm")
            }
            if let restingHR = health.avgRestingHeartRate {
                heartMetrics.append("Resting HR: \(Int(restingHR)) bpm")
            }
            if let hrv = health.avgHRV {
                heartMetrics.append("HRV: \(Int(hrv)) ms")
            }
            if !heartMetrics.isEmpty {
                lines.append("Heart: \(heartMetrics.joined(separator: ", "))")
            }

            // Recovery metrics
            var recoveryMetrics: [String] = []
            if let sleep = health.avgSleepHours {
                recoveryMetrics.append("Sleep: \(String(format: "%.1f", sleep)) hrs/night")
            }
            if let mindful = health.avgMindfulMinutes {
                recoveryMetrics.append("Mindfulness: \(mindful) min/day")
            }
            if !recoveryMetrics.isEmpty {
                lines.append("Recovery: \(recoveryMetrics.joined(separator: ", "))")
            }

            lines.append("Analyze how these health metrics may correlate with mood patterns.")
        }

        return lines.joined(separator: "\n")
    }
}
