//
//  ExtensionDataProvider.swift
//  Reflect
//
//  Unified data provider for Widget and Watch extensions.
//  - Watch: Uses CloudKit for automatic sync with iPhone
//  - Widget: Uses local App Group storage (widgets can't use CloudKit)
//
//  Add this file to: ReflectWidgetExtension, Reflect Watch App
//

import Foundation
import SwiftData
import WidgetKit
import os.log

/// Unified data provider for Widget and Watch extensions
/// - Watch: Uses CloudKit for automatic sync with iPhone (no WCSession needed for data)
/// - Widget: Uses local App Group storage (widgets can't use CloudKit)
@MainActor
final class ExtensionDataProvider {

    static let shared = ExtensionDataProvider()

    private static let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.88oakapps.reflect", category: "ExtensionDataProvider")

    private var _container: ModelContainer?

    private var container: ModelContainer {
        if let existing = _container {
            return existing
        }
        let newContainer = createContainer()
        _container = newContainer
        return newContainer
    }

    /// Creates the ModelContainer for extension data access
    private func createContainer() -> ModelContainer {
        let schema = Schema([MoodEntryModel.self])

        // Try to use shared app group container
        do {
            let storeURL = try getStoreURL()

            #if os(watchOS)
            // Watch uses CloudKit for automatic sync with iPhone
            let cloudKitContainerID: String
            #if DEBUG
            cloudKitContainerID = "iCloud.com.88oakapps.reflect.debug"
            #else
            cloudKitContainerID = "iCloud.com.88oakapps.reflect"
            #endif

            let configuration = ModelConfiguration(
                schema: schema,
                url: storeURL,
                cloudKitDatabase: .private(cloudKitContainerID)
            )
            Self.logger.info("Watch using CloudKit container: \(cloudKitContainerID)")
            #else
            // Widget uses local storage only (can't use CloudKit)
            let configuration = ModelConfiguration(
                schema: schema,
                url: storeURL,
                cloudKitDatabase: .none
            )
            #endif

            return try ModelContainer(for: schema, configurations: [configuration])
        } catch {
            Self.logger.warning("Falling back to in-memory storage: \(error.localizedDescription)")
            // Fall back to in-memory storage
            let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)
            do {
                return try ModelContainer(for: schema, configurations: [config])
            } catch {
                Self.logger.critical("Failed to create ModelContainer: \(error.localizedDescription)")
                preconditionFailure("Unable to create ModelContainer: \(error)")
            }
        }
    }

    private func getStoreURL() throws -> URL {
        let appGroupID = Constants.currentGroupShareId
        guard let containerURL = FileManager.default.containerURL(
            forSecurityApplicationGroupIdentifier: appGroupID
        ) else {
            throw NSError(domain: "ExtensionDataProvider", code: 1, userInfo: [NSLocalizedDescriptionKey: "App Group not available"])
        }
        #if DEBUG
        return containerURL.appendingPathComponent("Reflect-Debug.store")
        #else
        return containerURL.appendingPathComponent("Reflect.store")
        #endif
    }

    private var modelContext: ModelContext {
        container.mainContext
    }

    private init() {}

    // MARK: - Read Operations

    /// Get a single entry for a specific date
    func getEntry(byDate date: Date) -> MoodEntryModel? {
        let startDate = Calendar.current.startOfDay(for: date)
        guard let endDate = Calendar.current.date(byAdding: .day, value: 1, to: startDate) else {
            Self.logger.error("Failed to calculate end date for getEntry")
            return nil
        }

        var descriptor = FetchDescriptor<MoodEntryModel>(
            predicate: #Predicate { entry in
                entry.forDate >= startDate && entry.forDate < endDate
            },
            sortBy: [SortDescriptor(\.forDate, order: .forward)]
        )
        descriptor.fetchLimit = 1

        do {
            return try modelContext.fetch(descriptor).first
        } catch {
            Self.logger.error("Failed to fetch entry: \(error.localizedDescription)")
            return nil
        }
    }

    /// Get today's mood entry
    func getTodayEntry() -> MoodEntryModel? {
        getEntry(byDate: Date())
    }

    /// Get entries within a date range, optionally filtered by weekdays
    /// - Parameters:
    ///   - startDate: Start of the date range
    ///   - endDate: End of the date range
    ///   - includedDays: Weekdays to include (1=Sunday, 7=Saturday). Empty = all days.
    func getData(startDate: Date, endDate: Date, includedDays: [Int] = []) -> [MoodEntryModel] {
        let weekDays = includedDays.isEmpty ? [1, 2, 3, 4, 5, 6, 7] : includedDays

        let descriptor = FetchDescriptor<MoodEntryModel>(
            predicate: #Predicate { entry in
                entry.forDate >= startDate &&
                entry.forDate <= endDate &&
                weekDays.contains(entry.weekDay)
            },
            sortBy: [SortDescriptor(\.forDate, order: .forward)]
        )

        do {
            return try modelContext.fetch(descriptor)
        } catch {
            Self.logger.error("Failed to fetch entries: \(error.localizedDescription)")
            return []
        }
    }

    /// Get the earliest entry in the database
    var earliestEntry: MoodEntryModel? {
        var descriptor = FetchDescriptor<MoodEntryModel>(
            sortBy: [SortDescriptor(\.forDate, order: .forward)]
        )
        descriptor.fetchLimit = 1
        do {
            return try modelContext.fetch(descriptor).first
        } catch {
            Self.logger.error("Failed to fetch earliest entry: \(error.localizedDescription)")
            return nil
        }
    }

    /// Get the latest entry in the database
    var latestEntry: MoodEntryModel? {
        var descriptor = FetchDescriptor<MoodEntryModel>(
            sortBy: [SortDescriptor(\.forDate, order: .reverse)]
        )
        descriptor.fetchLimit = 1
        do {
            return try modelContext.fetch(descriptor).first
        } catch {
            Self.logger.error("Failed to fetch latest entry: \(error.localizedDescription)")
            return nil
        }
    }

    /// Get the current streak count
    /// - Parameter includedDays: Weekdays to include in streak calculation (empty = all days)
    func getCurrentStreak(includedDays: [Int] = []) -> Int {
        guard let yearAgo = Calendar.current.date(byAdding: .day, value: -365, to: Date()) else {
            Self.logger.error("Failed to calculate year-ago date for streak")
            return 0
        }

        let entries = getData(
            startDate: yearAgo,
            endDate: Date(),
            includedDays: includedDays
        ).sorted { $0.forDate > $1.forDate }

        var streak = 0
        var currentDate = Calendar.current.startOfDay(for: Date())

        for entry in entries {
            let entryDate = Calendar.current.startOfDay(for: entry.forDate)

            if entryDate == currentDate && entry.mood != .missing && entry.mood != .placeholder {
                streak += 1
                guard let previousDate = Calendar.current.date(byAdding: .day, value: -1, to: currentDate) else {
                    Self.logger.error("Failed to calculate previous date in streak")
                    break
                }
                currentDate = previousDate
            } else if entryDate < currentDate {
                break
            }
        }

        return streak
    }

    // MARK: - Write Operations

    /// Add a new mood entry
    /// - Parameters:
    ///   - mood: The mood to record
    ///   - date: The date for the entry
    ///   - entryType: The source of the entry (widget, watch, etc.)
    func add(mood: Mood, forDate date: Date, entryType: EntryType) {
        // Delete ALL existing entries for this date (handles duplicates)
        let existing = getAllEntries(byDate: date)
        for entry in existing {
            modelContext.delete(entry)
        }
        if !existing.isEmpty {
            do {
                try modelContext.save()
            } catch {
                Self.logger.error("Failed to save after deleting existing entries: \(error.localizedDescription)")
            }
        }

        let entry = MoodEntryModel(
            forDate: date,
            mood: mood,
            entryType: entryType
        )

        modelContext.insert(entry)

        do {
            try modelContext.save()
            Self.logger.info("Saved mood \(mood.rawValue) for \(date)")

            // Refresh all widgets/complications immediately
            WidgetCenter.shared.reloadAllTimelines()
        } catch {
            Self.logger.error("Failed to save mood: \(error.localizedDescription)")
        }
    }

    /// Get ALL entries for a specific date (not just the first one)
    func getAllEntries(byDate date: Date) -> [MoodEntryModel] {
        let startDate = Calendar.current.startOfDay(for: date)
        guard let endDate = Calendar.current.date(byAdding: .day, value: 1, to: startDate) else {
            Self.logger.error("Failed to calculate end date for getAllEntries")
            return []
        }

        let descriptor = FetchDescriptor<MoodEntryModel>(
            predicate: #Predicate { entry in
                entry.forDate >= startDate && entry.forDate < endDate
            },
            sortBy: [SortDescriptor(\.forDate, order: .forward)]
        )

        do {
            return try modelContext.fetch(descriptor)
        } catch {
            Self.logger.error("Failed to fetch all entries: \(error.localizedDescription)")
            return []
        }
    }

    /// Invalidate cached container (call when data might have changed)
    func invalidateCache() {
        _container = nil
    }
}
