//
//  DataControllerGET.swift
//  Reflect
//
//  SwiftData READ operations.
//

import SwiftData
import Foundation
import os.log

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

extension DataController {
    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 {
            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 {
            logger.error("Failed to fetch entry: \(error.localizedDescription)")
            return nil
        }
    }

    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 {
            logger.error("Failed to fetch entries: \(error.localizedDescription)")
            return []
        }
    }

    /// Calculate the current mood streak efficiently using a single batch query.
    /// Returns a tuple of (streak count, last logged mood if today has entry).
    func calculateStreak(from votingDate: Date) -> (streak: Int, todaysMood: Mood?) {
        let calendar = Calendar.current
        let dayStart = calendar.startOfDay(for: votingDate)

        // Fetch last 365 days of entries in a single query
        guard let yearAgo = calendar.date(byAdding: .day, value: -365, to: dayStart) else {
            return (0, nil)
        }

        let endOfVotingDay = calendar.date(byAdding: .day, value: 1, to: dayStart) ?? votingDate
        let entries = getData(startDate: yearAgo, endDate: endOfVotingDay, includedDays: [])
            .filter { $0.mood != .missing && $0.mood != .placeholder }

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

        // Build a Set of dates that have valid entries for O(1) lookup
        let datesWithEntries = Set(entries.map { calendar.startOfDay(for: $0.forDate) })

        // Check for today's entry
        let todaysEntry = entries.first { calendar.isDate($0.forDate, inSameDayAs: votingDate) }
        let todaysMood = todaysEntry?.mood

        // Calculate streak walking backwards
        var streak = 0
        var checkDate = votingDate

        // If no entry for current voting date, start counting from previous day
        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
            }
        }

        return (streak, todaysMood)
    }

    func splitIntoYearMonth(includedDays: [Int]) -> [Int: [Int: [MoodEntryModel]]] {
        // Single query to fetch all data - avoid N*12 queries
        let data = getData(
            startDate: Date(timeIntervalSince1970: 0),
            endDate: Date(),
            includedDays: includedDays
        )

        guard !data.isEmpty else { return [:] }

        let calendar = Calendar.current

        // Group entries by year and month in memory (single pass)
        var result = [Int: [Int: [MoodEntryModel]]]()

        for entry in data {
            let year = calendar.component(.year, from: entry.forDate)
            let month = calendar.component(.month, from: entry.forDate)

            if result[year] == nil {
                result[year] = [:]
            }
            if result[year]![month] == nil {
                result[year]![month] = []
            }
            result[year]![month]!.append(entry)
        }

        return result
    }
}
