//
//  MoodStreakActivity.swift
//  Reflect
//
//  Live Activity for mood streak tracking on Lock Screen and Dynamic Island
//

import ActivityKit
import SwiftUI
import WidgetKit

// MARK: - Activity Attributes
// Note: This must be defined in both the main app and widget extension

struct MoodStreakAttributes: ActivityAttributes {
    public struct ContentState: Codable, Hashable {
        var currentStreak: Int
        var lastMoodLogged: String
        var lastMoodColor: String // Hex color string
        var hasLoggedToday: Bool
        var votingWindowEnd: Date
    }

    var startDate: Date
}

// MARK: - Live Activity Manager

@MainActor
class LiveActivityManager: ObservableObject {
    static let shared = LiveActivityManager()

    @Published var currentActivity: Activity<MoodStreakAttributes>?

    private init() {}

    // Start a mood streak Live Activity
    func startStreakActivity(streak: Int, lastMood: Mood?, hasLoggedToday: Bool) {
        guard ActivityAuthorizationInfo().areActivitiesEnabled else {
            print("Live Activities not enabled")
            return
        }

        Task {
            // End any existing activity first
            await endAllActivities()

            // Now start the new activity
            await startActivityInternal(streak: streak, lastMood: lastMood, hasLoggedToday: hasLoggedToday)
        }
    }

    private func startActivityInternal(streak: Int, lastMood: Mood?, hasLoggedToday: Bool) async {
        let moodTint = UserDefaultsStore.moodTintable()
        let moodName = lastMood?.widgetDisplayName ?? "None"
        let moodColor = lastMood != nil ? (moodTint.color(forMood: lastMood!).toHex() ?? "#888888") : "#888888"

        // Calculate voting window end (end of current day)
        let votingDate = ShowBasedOnVoteLogics.getCurrentVotingDate(onboardingData: UserDefaultsStore.getOnboarding())
        let votingWindowEnd = Calendar.current.date(bySettingHour: 23, minute: 59, second: 59, of: votingDate) ?? Date()

        let attributes = MoodStreakAttributes(startDate: Date())
        let initialState = MoodStreakAttributes.ContentState(
            currentStreak: streak,
            lastMoodLogged: moodName,
            lastMoodColor: moodColor,
            hasLoggedToday: hasLoggedToday,
            votingWindowEnd: votingWindowEnd
        )

        do {
            let activity = try Activity.request(
                attributes: attributes,
                content: .init(state: initialState, staleDate: nil),
                pushType: nil
            )
            currentActivity = activity
        } catch {
            print("Error starting Live Activity: \(error)")
        }
    }

    // Update the Live Activity after mood is logged
    func updateActivity(streak: Int, mood: Mood) {
        guard let activity = currentActivity else { return }

        let moodTint = UserDefaultsStore.moodTintable()

        let votingDate = ShowBasedOnVoteLogics.getCurrentVotingDate(onboardingData: UserDefaultsStore.getOnboarding())
        let votingWindowEnd = Calendar.current.date(bySettingHour: 23, minute: 59, second: 59, of: votingDate) ?? Date()

        let updatedState = MoodStreakAttributes.ContentState(
            currentStreak: streak,
            lastMoodLogged: mood.widgetDisplayName,
            lastMoodColor: moodTint.color(forMood: mood).toHex() ?? "#888888",
            hasLoggedToday: true,
            votingWindowEnd: votingWindowEnd
        )

        Task {
            await activity.update(.init(state: updatedState, staleDate: nil))
        }
    }

    // End all Live Activities
    func endAllActivities() async {
        for activity in Activity<MoodStreakAttributes>.activities {
            await activity.end(nil, dismissalPolicy: .immediate)
        }
        currentActivity = nil
    }

    // End activity at midnight
    func scheduleActivityEnd() {
        guard let activity = currentActivity,
              let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: Date()) else { return }

        let midnight = Calendar.current.startOfDay(for: tomorrow)

        Task {
            await activity.end(nil, dismissalPolicy: .after(midnight))
        }
    }
}

// MARK: - Live Activity Scheduler

/// Handles automatic scheduling of Live Activities based on user's rating time
@MainActor
class LiveActivityScheduler: ObservableObject {
    static let shared = LiveActivityScheduler()

    private var startTimer: Timer?
    private var endTimer: Timer?

    private init() {}

    /// Get the user's configured rating time
    func getUserRatingTime() -> Date? {
        guard let data = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.savedOnboardingData.rawValue) as? Data,
              let onboardingData = try? JSONDecoder().decode(OnboardingData.self, from: data) else {
            return nil
        }
        return onboardingData.date
    }

    /// Calculate the start time (at rating time)
    func getStartTime(for date: Date = Date()) -> Date? {
        guard let ratingTime = getUserRatingTime() else { return nil }

        let calendar = Calendar.current
        let ratingComponents = calendar.dateComponents([.hour, .minute], from: ratingTime)

        var startComponents = calendar.dateComponents([.year, .month, .day], from: date)
        startComponents.hour = ratingComponents.hour
        startComponents.minute = ratingComponents.minute

        // Start at rating time
        return calendar.date(from: startComponents)
    }

    /// Calculate the end time (5 hours after rating time)
    func getEndTime(for date: Date = Date()) -> Date? {
        guard let ratingTime = getUserRatingTime() else { return nil }

        let calendar = Calendar.current
        let ratingComponents = calendar.dateComponents([.hour, .minute], from: ratingTime)

        var endComponents = calendar.dateComponents([.year, .month, .day], from: date)
        endComponents.hour = ratingComponents.hour
        endComponents.minute = ratingComponents.minute

        guard let ratingDateTime = calendar.date(from: endComponents) else { return nil }

        // End 5 hours after rating time
        return calendar.date(byAdding: .hour, value: 5, to: ratingDateTime)
    }

    /// Cached streak data to avoid redundant calculations
    private var cachedStreakData: (streak: Int, todaysMood: Mood?, votingDate: Date)?

    /// Get streak data using efficient batch query (cached per voting date)
    private func getStreakData() -> (streak: Int, todaysMood: Mood?) {
        let votingDate = ShowBasedOnVoteLogics.getCurrentVotingDate(onboardingData: UserDefaultsStore.getOnboarding())

        // Return cached data if still valid for current voting date
        if let cached = cachedStreakData,
           Calendar.current.isDate(cached.votingDate, inSameDayAs: votingDate) {
            return (cached.streak, cached.todaysMood)
        }

        // Calculate and cache
        #if WIDGET_EXTENSION
        // Widget extension uses its own data provider
        let calendar = Calendar.current
        let dayStart = calendar.startOfDay(for: votingDate)
        guard let yearAgo = calendar.date(byAdding: .day, value: -365, to: dayStart) else {
            return (0, nil)
        }

        let entries = WidgetDataProvider.shared.getData(startDate: yearAgo, endDate: votingDate, includedDays: [])
            .filter { $0.mood != .missing && $0.mood != .placeholder }

        guard !entries.isEmpty else { return (0, nil) }

        let datesWithEntries = Set(entries.map { calendar.startOfDay(for: $0.forDate) })
        let todaysEntry = entries.first { calendar.isDate($0.forDate, inSameDayAs: votingDate) }
        let todaysMood = todaysEntry?.mood

        var streak = 0
        var checkDate = votingDate
        if !datesWithEntries.contains(dayStart) {
            checkDate = calendar.date(byAdding: .day, value: -1, to: checkDate) ?? checkDate
        }

        while true {
            let checkDayStart = calendar.startOfDay(for: checkDate)
            if datesWithEntries.contains(checkDayStart) {
                streak += 1
                checkDate = calendar.date(byAdding: .day, value: -1, to: checkDate) ?? checkDate
            } else {
                break
            }
        }

        cachedStreakData = (streak, todaysMood, votingDate)
        return (streak, todaysMood)
        #else
        let result = DataController.shared.calculateStreak(from: votingDate)
        cachedStreakData = (result.streak, result.todaysMood, votingDate)
        return result
        #endif
    }

    /// Check if user has rated today
    func hasRatedToday() -> Bool {
        return getStreakData().todaysMood != nil
    }

    /// Calculate current streak
    func calculateStreak() -> Int {
        return getStreakData().streak
    }

    /// Get today's mood if logged
    func getTodaysMood() -> Mood? {
        return getStreakData().todaysMood
    }

    /// Invalidate cached streak data (call when mood is logged)
    func invalidateCache() {
        cachedStreakData = nil
    }

    /// Schedule Live Activity based on current time and rating time
    func scheduleBasedOnCurrentTime() {
        invalidateTimers()

        guard ActivityAuthorizationInfo().areActivitiesEnabled else {
            print("[LiveActivity] Live Activities not enabled by user")
            return
        }

        let now = Date()
        guard let startTime = getStartTime(),
              let endTime = getEndTime() else {
            print("[LiveActivity] No rating time configured - skipping")
            return
        }

        let hasRated = hasRatedToday()
        print("[LiveActivity] Schedule check - now: \(now), start: \(startTime), end: \(endTime), hasRated: \(hasRated)")

        // If user has already rated today, don't show activity - schedule for next day
        if hasRated {
            print("[LiveActivity] User already rated today - scheduling for next day")
            scheduleForNextDay()
            return
        }

        // Check if we're within the activity window (rating time to 5 hrs after)
        if now >= startTime && now <= endTime {
            // Start activity immediately
            print("[LiveActivity] Within window - starting activity now")
            let streak = calculateStreak()
            LiveActivityManager.shared.startStreakActivity(streak: streak, lastMood: getTodaysMood(), hasLoggedToday: false)

            // Schedule end
            scheduleEnd(at: endTime)
        } else if now < startTime {
            // Schedule start for later today
            print("[LiveActivity] Before window - scheduling start for \(startTime)")
            scheduleStart(at: startTime)
            scheduleEnd(at: endTime)
        } else {
            // Past the window for today, schedule for tomorrow
            print("[LiveActivity] Past window - scheduling for tomorrow")
            scheduleForNextDay()
        }
    }

    /// Schedule Live Activity to start at a specific time
    private func scheduleStart(at date: Date) {
        let interval = date.timeIntervalSince(Date())
        guard interval > 0 else { return }

        startTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { [weak self] _ in
            Task { @MainActor in
                guard let self = self else { return }

                // Check if user rated in the meantime
                if !self.hasRatedToday() {
                    let streak = self.calculateStreak()
                    LiveActivityManager.shared.startStreakActivity(streak: streak, lastMood: self.getTodaysMood(), hasLoggedToday: false)
                }
            }
        }
    }

    /// Schedule Live Activity to end at a specific time
    private func scheduleEnd(at date: Date) {
        let interval = date.timeIntervalSince(Date())
        guard interval > 0 else { return }

        endTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { _ in
            Task { @MainActor in
                await LiveActivityManager.shared.endAllActivities()
                LiveActivityScheduler.shared.scheduleForNextDay()
            }
        }
    }

    /// Schedule for the next day (called after user rates or after window closes)
    func scheduleForNextDay() {
        invalidateTimers()

        // End current activity if exists
        Task {
            await LiveActivityManager.shared.endAllActivities()
        }

        guard let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: Date()),
              let startTime = getStartTime(for: tomorrow) else {
            return
        }

        let interval = startTime.timeIntervalSince(Date())
        guard interval > 0 else { return }

        startTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { [weak self] _ in
            Task { @MainActor in
                self?.scheduleBasedOnCurrentTime()
            }
        }
    }

    /// Called when user updates their rating time in settings
    func onRatingTimeUpdated() {
        // Reschedule based on new time
        scheduleBasedOnCurrentTime()
    }

    /// Invalidate all timers
    private func invalidateTimers() {
        startTimer?.invalidate()
        startTimer = nil
        endTimer?.invalidate()
        endTimer = nil
    }
}

