import Foundation
import SharedModels
import SwiftData

enum StartupCoordinator {
    /// First-launch work that must complete before the UI can be shown.
    /// Both calls are self-gating: they return immediately if the work is already done.
    @MainActor
    static func bootstrap(localContainer: ModelContainer) async {
        await DataLoader.seedIfNeeded(container: localContainer)
        await DataLoader.refreshCourseDataIfNeeded(container: localContainer)
        await DataLoader.refreshTextbookDataIfNeeded(container: localContainer)
        await DataLoader.refreshBooksDataIfNeeded(container: localContainer)
        await DataLoader.refreshLexemesIfNeeded(container: localContainer)
    }

    /// Recurring maintenance: legacy migrations, identity repair, cloud dedup.
    /// Safe to run in the background after the UI is visible.
    @MainActor
    static func runMaintenance(localContainer: ModelContainer, cloudContainer: ModelContainer) async {
        await DataLoader.migrateCourseProgressIfNeeded(
            localContainer: localContainer,
            cloudContainer: cloudContainer
        )

        let context = cloudContainer.mainContext
        let progress = ReviewStore.fetchOrCreateUserProgress(context: context)
        progress.migrateLegacyStorageIfNeeded()
        if progress.enabledTenseIDs.isEmpty {
            progress.enabledTenseIDs = ReviewStore.defaultEnabledTenses()
        }

        migrateLegacyPayloads(context: context)
        repairIdentityFields(context: context)
        dedupeCloudState(context: context)

        try? context.save()
    }

    private static func migrateLegacyPayloads(context: ModelContext) {
        let progressList = (try? context.fetch(FetchDescriptor<UserProgress>())) ?? []
        for progress in progressList {
            progress.migrateLegacyStorageIfNeeded()
            if progress.enabledTenseIDs.isEmpty {
                progress.enabledTenseIDs = ReviewStore.defaultEnabledTenses()
            }
            if progress.id.isEmpty {
                progress.id = ReviewStore.progressID
            }
        }

        let testResults = (try? context.fetch(FetchDescriptor<TestResult>())) ?? []
        for result in testResults {
            result.migrateLegacyStorageIfNeeded()
        }
    }

    private static func repairIdentityFields(context: ModelContext) {
        let reviewCards = (try? context.fetch(FetchDescriptor<ReviewCard>())) ?? []
        for card in reviewCards {
            card.refreshIdentityIfNeeded()
        }

        let dailyLogs = (try? context.fetch(FetchDescriptor<DailyLog>())) ?? []
        for log in dailyLogs {
            log.refreshIdentityIfNeeded()
        }
    }

    private static func dedupeCloudState(context: ModelContext) {
        dedupeUserProgress(context: context)
        dedupeReviewCards(context: context)
        dedupeCourseReviewCards(context: context)
        dedupeDailyLogs(context: context)
    }

    private static func dedupeUserProgress(context: ModelContext) {
        let all = (try? context.fetch(FetchDescriptor<UserProgress>())) ?? []
        guard all.count > 1 else { return }

        let ranked = all.sorted { score($0) > score($1) }
        guard let canonical = ranked.first else { return }
        canonical.id = ReviewStore.progressID
        canonical.migrateLegacyStorageIfNeeded()

        let mergedTenses = Set(all.flatMap(\.enabledTenseIDs))
        let mergedBadges = Set(all.flatMap(\.unlockedBadgeIDs))
        canonical.enabledTenseIDs = mergedTenses.isEmpty ? ReviewStore.defaultEnabledTenses() : mergedTenses.sorted()
        canonical.unlockedBadgeIDs = mergedBadges.sorted()
        canonical.totalReviewed = all.map(\.totalReviewed).max() ?? canonical.totalReviewed
        canonical.longestStreak = all.map(\.longestStreak).max() ?? canonical.longestStreak
        canonical.currentStreak = all.map(\.currentStreak).max() ?? canonical.currentStreak
        canonical.dailyGoal = ranked.first(where: { $0.dailyGoal != 50 })?.dailyGoal ?? canonical.dailyGoal
        canonical.selectedLevel = ranked.first(where: { $0.selectedLevel != VerbLevel.basic.rawValue })?.selectedLevel ?? canonical.selectedLevel
        canonical.showVosotros = !all.contains(where: { !$0.showVosotros })
        canonical.autoFillStem = all.contains(where: \.autoFillStem)

        let latestTodayDate = all
            .map(\.todayDate)
            .filter { !$0.isEmpty }
            .max() ?? canonical.todayDate
        canonical.todayDate = latestTodayDate
        canonical.todayCount = all
            .filter { $0.todayDate == latestTodayDate }
            .map(\.todayCount)
            .max() ?? canonical.todayCount

        for other in all where other !== canonical {
            context.delete(other)
        }
    }

    private static func dedupeReviewCards(context: ModelContext) {
        let all = (try? context.fetch(FetchDescriptor<ReviewCard>())) ?? []
        let groups = Dictionary(grouping: all, by: { ReviewCard.makeKey(verbId: $0.verbId, tenseId: $0.tenseId, personIndex: $0.personIndex) })

        for (key, cards) in groups {
            guard cards.count > 1 else {
                cards.first?.key = key
                continue
            }

            guard let canonical = canonicalReviewCard(from: cards) else { continue }
            canonical.key = key

            for other in cards where other !== canonical {
                context.delete(other)
            }
        }
    }

    private static func dedupeCourseReviewCards(context: ModelContext) {
        let all = (try? context.fetch(FetchDescriptor<CourseReviewCard>())) ?? []
        let groups = Dictionary(grouping: all, by: \.id)

        for (id, cards) in groups where cards.count > 1 {
            guard let canonical = cards.max(by: { reviewScore($0) < reviewScore($1) }) else { continue }
            canonical.id = id

            for other in cards where other !== canonical {
                context.delete(other)
            }
        }
    }

    private static func dedupeDailyLogs(context: ModelContext) {
        let all = (try? context.fetch(FetchDescriptor<DailyLog>())) ?? []
        let groups = Dictionary(grouping: all, by: \.dateString)

        for (dateString, logs) in groups {
            guard let canonical = logs.first else { continue }
            canonical.id = DailyLog.makeID(dateString)

            if logs.count == 1 { continue }

            canonical.reviewCount = logs.reduce(0) { $0 + $1.reviewCount }
            canonical.correctCount = logs.reduce(0) { $0 + $1.correctCount }

            for other in logs.dropFirst() {
                context.delete(other)
            }
        }
    }

    private static func score(_ progress: UserProgress) -> Int {
        (progress.totalReviewed * 10_000) +
        (progress.longestStreak * 100) +
        progress.currentStreak
    }

    private static func canonicalReviewCard(from cards: [ReviewCard]) -> ReviewCard? {
        let latest = cards.max {
            let lhsDate = $0.lastReviewDate ?? .distantPast
            let rhsDate = $1.lastReviewDate ?? .distantPast
            if lhsDate != rhsDate { return lhsDate < rhsDate }
            if $0.repetitions != $1.repetitions { return $0.repetitions < $1.repetitions }
            return $0.interval < $1.interval
        }

        guard let canonical = latest else { return nil }
        canonical.refreshIdentityIfNeeded()
        return canonical
    }

    private static func reviewScore(_ card: CourseReviewCard) -> Int {
        let timestamp = Int(card.lastReviewDate?.timeIntervalSince1970 ?? 0)
        return (timestamp * 10_000) + (card.repetitions * 100) + card.interval
    }
}
