//
//  MoodLogger.swift
//  Reflect
//
//  Centralized mood logging service that handles all side effects
//

import Foundation
import WidgetKit
import os.log

/// Centralized service for logging moods with all associated side effects.
/// All mood entry points should use this service to ensure consistent behavior.
@MainActor
final class MoodLogger {
    static let shared = MoodLogger()

    private static let logger = Logger(subsystem: "com.88oakapps.reflect", category: "MoodLogger")

    /// Key for tracking the last date side effects were applied
    private static let lastSideEffectsDateKey = "lastSideEffectsAppliedDate"
    private static let sideEffectsDateFormatter = ISO8601DateFormatter()

    private init() {}

    /// Log a mood entry with all associated side effects.
    /// This is the single source of truth for mood logging in the app.
    ///
    /// - Parameters:
    ///   - mood: The mood to log
    ///   - date: The date for the mood entry
    ///   - entryType: The source of the mood entry (header, widget, siri, etc.)
    ///   - syncHealthKit: Whether to sync to HealthKit (default true, but widget can't access HealthKit)
    ///   - updateTips: Whether to update TipKit parameters (default true, but widget can't access TipKit)
    func logMood(
        _ mood: Mood,
        for date: Date,
        entryType: EntryType,
        syncHealthKit: Bool = true,
        updateTips: Bool = true
    ) {
        // 1. Add mood entry to data store
        DataController.shared.add(mood: mood, forDate: date, entryType: entryType)

        // Apply side effects and mark as complete
        applySideEffects(mood: mood, for: date, syncHealthKit: syncHealthKit, updateTips: updateTips)
    }

    /// Apply side effects for a mood entry without saving the entry itself.
    /// Used for catch-up when widget saved data but couldn't run side effects.
    ///
    /// - Parameters:
    ///   - mood: The mood that was logged
    ///   - date: The date of the mood entry
    ///   - syncHealthKit: Whether to sync to HealthKit
    ///   - updateTips: Whether to update TipKit parameters
    func applySideEffects(
        mood: Mood,
        for date: Date,
        syncHealthKit: Bool = true,
        updateTips: Bool = true
    ) {
        // Skip side effects for placeholder/missing moods
        guard mood != .missing && mood != .placeholder else { return }

        Self.logger.info("Applying side effects for mood \(mood.rawValue) on \(date)")

        // 1. Sync to HealthKit if enabled, requested, and user has full access
        if syncHealthKit {
            let healthKitEnabled = GroupUserDefaults.groupDefaults.bool(forKey: UserDefaultsStore.Keys.healthKitEnabled.rawValue)
            let hasAccess = !IAPManager.shared.shouldShowPaywall
            if healthKitEnabled && hasAccess {
                Task {
                    try? await HealthKitManager.shared.saveMood(mood, for: date)
                }
            }
        }

        // 2. Calculate current streak for Live Activity and TipKit
        let streak = calculateCurrentStreak()

        // 3. Update Live Activity
        LiveActivityManager.shared.updateActivity(streak: streak, mood: mood)
        LiveActivityScheduler.shared.invalidateCache()  // Clear stale hasRated cache
        LiveActivityScheduler.shared.scheduleForNextDay()

        // 4. Update tips parameters if requested
        if updateTips {
            ReflectTipsManager.shared.onMoodLogged()
            ReflectTipsManager.shared.updateStreak(streak)
        }

        // 5. Request app review at moments of delight
        ReviewRequestManager.shared.onMoodLogged(streak: streak)

        // 6. Reload widgets
        WidgetCenter.shared.reloadAllTimelines()

        // 7. Notify watch to refresh complications
        WatchConnectivityManager.shared.notifyWatchToReload()

        // 8. Mark side effects as applied for this date
        markSideEffectsApplied(for: date)
    }

    /// Delete a mood entry for a specific date with all associated cleanup.
    /// Replaces the entry with a .missing placeholder and cleans up HealthKit/Live Activity state.
    ///
    /// - Parameter date: The date of the entry to delete
    func deleteMood(forDate date: Date) {
        // 1. Delete all entries for this date and replace with missing placeholder
        DataController.shared.deleteAllEntries(forDate: date)
        DataController.shared.add(mood: .missing, forDate: date, entryType: .filledInMissing)

        Self.logger.info("Deleted mood entry for \(date)")

        // 2. Delete HealthKit entry if enabled and user has access
        let healthKitEnabled = GroupUserDefaults.groupDefaults.bool(forKey: UserDefaultsStore.Keys.healthKitEnabled.rawValue)
        let hasAccess = !IAPManager.shared.shouldShowPaywall
        if healthKitEnabled && hasAccess {
            Task {
                try? await HealthKitManager.shared.deleteMood(for: date)
            }
        }

        // 3. Recalculate streak and update Live Activity
        let streak = calculateCurrentStreak()
        LiveActivityManager.shared.updateActivity(streak: streak, mood: .missing)
        LiveActivityScheduler.shared.invalidateCache()
        LiveActivityScheduler.shared.scheduleBasedOnCurrentTime()

        // 4. Reload widgets
        WidgetCenter.shared.reloadAllTimelines()

        // 5. Notify watch to refresh
        WatchConnectivityManager.shared.notifyWatchToReload()
    }

    /// Delete all mood data with full cleanup of HealthKit and Live Activity state.
    /// Used when user clears all data from settings.
    func deleteAllData() {
        // 1. Clear all entries from the database
        DataController.shared.clearDB()

        Self.logger.info("Cleared all mood data")

        // 2. Delete all HealthKit entries if enabled and user has access
        let healthKitEnabled = GroupUserDefaults.groupDefaults.bool(forKey: UserDefaultsStore.Keys.healthKitEnabled.rawValue)
        let hasAccess = !IAPManager.shared.shouldShowPaywall
        if healthKitEnabled && hasAccess {
            Task {
                try? await HealthKitManager.shared.deleteAllMoods()
            }
        }

        // 3. End all Live Activities
        Task {
            await LiveActivityManager.shared.endAllActivities()
        }
        LiveActivityScheduler.shared.invalidateCache()

        // 4. Reload widgets
        WidgetCenter.shared.reloadAllTimelines()

        // 5. Notify watch to refresh
        WatchConnectivityManager.shared.notifyWatchToReload()
    }

    /// Update an existing mood entry with all associated side effects.
    /// Centralizes the update logic that was previously split between ViewModel and DataController.
    ///
    /// - Parameters:
    ///   - entryDate: The date of the entry to update
    ///   - mood: The new mood value
    /// - Returns: Whether the update was successful
    @discardableResult
    func updateMood(entryDate: Date, withMood mood: Mood) -> Bool {
        let success = DataController.shared.update(entryDate: entryDate, withMood: mood)
        guard success else {
            Self.logger.error("Failed to update mood entry for \(entryDate)")
            return false
        }

        // Skip side effects for placeholder/missing moods
        guard mood != .missing && mood != .placeholder else { return true }

        // Sync to HealthKit if enabled and user has full access
        let healthKitEnabled = GroupUserDefaults.groupDefaults.bool(forKey: UserDefaultsStore.Keys.healthKitEnabled.rawValue)
        let hasAccess = !IAPManager.shared.shouldShowPaywall
        if healthKitEnabled && hasAccess {
            Task {
                try? await HealthKitManager.shared.saveMood(mood, for: entryDate)
            }
        }

        // Reload widgets
        WidgetCenter.shared.reloadAllTimelines()

        // Recalculate streak and update Live Activity
        let streak = calculateCurrentStreak()
        LiveActivityManager.shared.updateActivity(streak: streak, mood: mood)
        LiveActivityScheduler.shared.invalidateCache()

        // Notify watch to refresh
        WatchConnectivityManager.shared.notifyWatchToReload()

        return true
    }

    /// Check for and process any pending side effects from widget/extension votes.
    /// Call this when the app becomes active to ensure all side effects are applied.
    func processPendingSideEffects() {
        let votingDate = ShowBasedOnVoteLogics.getCurrentVotingDate(onboardingData: UserDefaultsStore.getOnboarding())
        let dayStart = Calendar.current.startOfDay(for: votingDate)
        let dayEnd = Calendar.current.date(byAdding: .day, value: 1, to: dayStart)!

        // Check if there's an entry for the current voting date
        guard let entry = DataController.shared.getData(startDate: dayStart, endDate: dayEnd, includedDays: []).first,
              entry.mood != .missing && entry.mood != .placeholder else {
            Self.logger.debug("No valid mood entry for today, skipping side effects catch-up")
            return
        }

        // Check if side effects were already applied for this date
        if sideEffectsApplied(for: votingDate) {
            Self.logger.debug("Side effects already applied for \(votingDate)")
            return
        }

        // Apply the missing side effects
        Self.logger.info("Catching up side effects for widget/watch vote: \(entry.mood.rawValue)")
        applySideEffects(mood: entry.mood, for: entry.forDate)
    }

    /// Import mood entries in batch with a single round of side effects.
    /// Used for CSV import to avoid O(n) widget reloads, streak calcs, etc.
    func importMoods(_ entries: [(mood: Mood, date: Date, entryType: EntryType)]) {
        guard !entries.isEmpty else { return }

        DataController.shared.addBatch(entries: entries)

        // Single round of side effects
        WidgetCenter.shared.reloadAllTimelines()
        WatchConnectivityManager.shared.notifyWatchToReload()
        LiveActivityScheduler.shared.invalidateCache()
    }

    // MARK: - Side Effects Tracking

    /// Mark that side effects have been applied for a given date
    private func markSideEffectsApplied(for date: Date) {
        let dateString = Self.sideEffectsDateFormatter.string(from: Calendar.current.startOfDay(for: date))
        GroupUserDefaults.groupDefaults.set(dateString, forKey: Self.lastSideEffectsDateKey)
    }

    /// Check if side effects have been applied for a given date
    private func sideEffectsApplied(for date: Date) -> Bool {
        guard let lastDateString = GroupUserDefaults.groupDefaults.string(forKey: Self.lastSideEffectsDateKey),
              let lastDate = Self.sideEffectsDateFormatter.date(from: lastDateString) else {
            return false
        }

        let targetDay = Calendar.current.startOfDay(for: date)
        let lastDay = Calendar.current.startOfDay(for: lastDate)

        return targetDay == lastDay
    }

    /// Calculate the current mood streak using optimized batch query
    private func calculateCurrentStreak() -> Int {
        let votingDate = ShowBasedOnVoteLogics.getCurrentVotingDate(onboardingData: UserDefaultsStore.getOnboarding())
        return DataController.shared.calculateStreak(from: votingDate).streak
    }
}
