import Foundation
import SwiftData

struct ReviewStore {
    static let progressID = "main"

    static func defaultEnabledTenses() -> [String] {
        TenseID.defaultPracticeIDs
    }

    static func fetchUserProgress(context: ModelContext) -> UserProgress? {
        let descriptor = FetchDescriptor<UserProgress>(
            predicate: #Predicate<UserProgress> { $0.id == progressID }
        )
        if let progress = (try? context.fetch(descriptor))?.first {
            progress.migrateLegacyStorageIfNeeded()
            return progress
        }

        let fallback = (try? context.fetch(FetchDescriptor<UserProgress>()))?.first
        fallback?.migrateLegacyStorageIfNeeded()
        return fallback
    }

    @discardableResult
    static func fetchOrCreateUserProgress(context: ModelContext) -> UserProgress {
        if let progress = fetchUserProgress(context: context) {
            progress.id = progressID
            if progress.enabledTenseIDs.isEmpty {
                progress.enabledTenseIDs = defaultEnabledTenses()
            }
            return progress
        }

        let progress = UserProgress()
        progress.id = progressID
        progress.enabledTenseIDs = defaultEnabledTenses()
        context.insert(progress)
        return progress
    }

    @discardableResult
    static func fetchOrCreateReviewCard(
        verbId: Int,
        tenseId: String,
        personIndex: Int,
        context: ModelContext
    ) -> ReviewCard {
        let key = ReviewCard.makeKey(verbId: verbId, tenseId: tenseId, personIndex: personIndex)
        let keyedDescriptor = FetchDescriptor<ReviewCard>(
            predicate: #Predicate<ReviewCard> { $0.key == key }
        )
        if let existing = (try? context.fetch(keyedDescriptor))?.first {
            existing.refreshIdentityIfNeeded()
            return existing
        }

        let legacyDescriptor = FetchDescriptor<ReviewCard>(
            predicate: #Predicate<ReviewCard> { card in
                card.verbId == verbId &&
                card.tenseId == tenseId &&
                card.personIndex == personIndex
            }
        )
        if let existing = (try? context.fetch(legacyDescriptor))?.first {
            existing.refreshIdentityIfNeeded()
            return existing
        }

        let newCard = ReviewCard(verbId: verbId, tenseId: tenseId, personIndex: personIndex)
        context.insert(newCard)
        return newCard
    }

    /// Bumps the streak / "showed up today" bookkeeping without touching
    /// review-specific counters. Call from any user-initiated learning action
    /// — sending a chat message, doing an exercise, watching a curated video,
    /// looking up a word in lyrics, etc. Safe to call multiple times per day;
    /// only the first call on a fresh date moves the streak.
    @discardableResult
    static func recordActivity(context: ModelContext, date: Date = Date()) -> UserProgress {
        let progress = fetchOrCreateUserProgress(context: context)
        let todayString = DailyLog.dateString(from: date)

        if progress.todayDate != todayString {
            if !progress.todayDate.isEmpty {
                if isConsecutiveDay(previous: progress.todayDate, current: todayString) {
                    progress.currentStreak += 1
                } else {
                    progress.currentStreak = 1
                }
            } else {
                progress.currentStreak = 1
            }

            progress.todayDate = todayString
            progress.todayCount = 0
        }

        progress.longestStreak = max(progress.longestStreak, progress.currentStreak)
        try? context.save()
        return progress
    }

    @discardableResult
    static func updateProgress(
        reviewIncrement: Int,
        correctIncrement: Int,
        context: ModelContext,
        date: Date = Date()
    ) -> UserProgress {
        // Bump streak / today-date first so review-specific counters land on
        // the correct day if this is the user's first action after midnight.
        let progress = recordActivity(context: context, date: date)
        let todayString = DailyLog.dateString(from: date)

        progress.todayCount += reviewIncrement
        progress.totalReviewed += reviewIncrement

        let log = fetchOrCreateDailyLog(dateString: todayString, context: context)
        log.reviewCount += reviewIncrement
        log.correctCount += correctIncrement

        return progress
    }

    static func recordReview(
        verbId: Int,
        tenseId: String,
        personIndex: Int,
        quality: ReviewQuality,
        context: ModelContext,
        referenceContext: ModelContext
    ) -> [Badge] {
        let card = fetchOrCreateReviewCard(
            verbId: verbId,
            tenseId: tenseId,
            personIndex: personIndex,
            context: context
        )
        applyReview(quality: quality, to: card)

        let progress = updateProgress(
            reviewIncrement: 1,
            correctIncrement: quality.rawValue >= 3 ? 1 : 0,
            context: context
        )
        let badges = AchievementService.checkAchievements(
            progress: progress,
            reviewContext: context,
            referenceContext: referenceContext
        )
        try? context.save()
        return badges
    }

    static func recordFullTableReview(
        verbId: Int,
        tenseId: String,
        results: [Int: Bool],
        context: ModelContext,
        referenceContext: ModelContext
    ) -> [Badge] {
        for (personIndex, isCorrect) in results {
            let card = fetchOrCreateReviewCard(
                verbId: verbId,
                tenseId: tenseId,
                personIndex: personIndex,
                context: context
            )
            applyReview(quality: isCorrect ? .good : .again, to: card)
        }

        let allCorrect = results.values.allSatisfy { $0 }
        let progress = updateProgress(
            reviewIncrement: 1,
            correctIncrement: allCorrect ? 1 : 0,
            context: context
        )
        let badges = AchievementService.checkAchievements(
            progress: progress,
            reviewContext: context,
            referenceContext: referenceContext
        )
        try? context.save()
        return badges
    }

    static func fetchOrCreateDailyLog(dateString: String, context: ModelContext) -> DailyLog {
        let descriptor = FetchDescriptor<DailyLog>(
            predicate: #Predicate<DailyLog> { $0.id == dateString || $0.dateString == dateString }
        )
        if let existing = (try? context.fetch(descriptor))?.first {
            existing.refreshIdentityIfNeeded()
            return existing
        }

        let log = DailyLog(dateString: dateString)
        context.insert(log)
        return log
    }

    static func applyReview(quality: ReviewQuality, to card: ReviewCard) {
        let result = SRSEngine.review(
            quality: quality,
            currentEase: card.easeFactor,
            currentInterval: card.interval,
            currentReps: card.repetitions
        )

        card.easeFactor = result.easeFactor
        card.interval = result.interval
        card.repetitions = result.repetitions
        card.dueDate = SRSEngine.nextDueDate(interval: result.interval)
        card.lastReviewDate = Date()
        card.refreshIdentityIfNeeded()
    }

    private static func isConsecutiveDay(previous: String, current: String) -> Bool {
        guard let prevDate = DailyLog.date(from: previous),
              let currDate = DailyLog.date(from: current) else { return false }

        let diff = Calendar.current.dateComponents([.day], from: prevDate, to: currDate)
        return diff.day == 1
    }
}
