import SwiftData
import SharedModels
import Foundation

actor DataLoader {
    static func seedIfNeeded(container: ModelContainer) async {
        let context = ModelContext(container)

        let count: Int
        do {
            count = try context.fetchCount(FetchDescriptor<Verb>())
            print("[DataLoader] seedIfNeeded: existing verb count = \(count)")
        } catch {
            print("[DataLoader] ⚠️ seedIfNeeded fetchCount threw: \(error)")
            count = 0
        }
        if count > 0 { return }

        print("Seeding database...")

        // Try direct bundle lookup first, then subdirectory
        let url = Bundle.main.url(forResource: "conjuga_data", withExtension: "json")
            ?? Bundle.main.url(forResource: "conjuga_data", withExtension: "json", subdirectory: "Resources")
            ?? Bundle.main.bundleURL.appendingPathComponent("Resources/conjuga_data.json")

        guard let data = try? Data(contentsOf: url) else {
            print("ERROR: Could not load conjuga_data.json from bundle at \(url)")
            return
        }

        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
            print("ERROR: Could not parse conjuga_data")
            return
        }

        // Seed tense guides
        if let guides = json["tenseGuides"] as? [[String: Any]] {
            for g in guides {
                guard let tenseId = g["tenseId"] as? String,
                      let title = g["title"] as? String,
                      let body = g["body"] as? String else { continue }
                let guide = TenseGuide(tenseId: tenseId, title: title, body: body)
                context.insert(guide)
            }
        }

        // Seed verbs
        var verbMap: [Int: Verb] = [:]
        if let verbs = json["verbs"] as? [[String: Any]] {
            for v in verbs {
                guard let id = v["id"] as? Int,
                      let infinitive = v["infinitive"] as? String,
                      let english = v["english"] as? String,
                      let rank = v["rank"] as? Int,
                      let ending = v["ending"] as? String,
                      let reflexive = v["reflexive"] as? Int,
                      let level = v["level"] as? String else { continue }
                let verb = Verb(id: id, infinitive: infinitive, english: english, rank: rank, ending: ending, reflexive: reflexive, level: level)
                context.insert(verb)
                verbMap[id] = verb
            }
            print("Inserted \(verbs.count) verbs")
        }

        try? context.save()

        // Seed verb forms — bulk insert, no relationship assignment (use verbId for queries)
        let chunkSize = 20000
        if let forms = json["verbForms"] as? [[String: Any]] {
            for i in stride(from: 0, to: forms.count, by: chunkSize) {
                autoreleasepool {
                    let end = min(i + chunkSize, forms.count)
                    for j in i..<end {
                        let f = forms[j]
                        guard let verbId = f["verbId"] as? Int,
                              let tenseId = f["tenseId"] as? String,
                              let personIndex = f["personIndex"] as? Int,
                              let form = f["form"] as? String,
                              let regularity = f["regularity"] as? String else { continue }
                        let vf = VerbForm(verbId: verbId, tenseId: tenseId, personIndex: personIndex, form: form, regularity: regularity)
                        context.insert(vf)
                    }
                    try? context.save()
                }
            }
            print("Inserted \(forms.count) verb forms")
        }

        // Seed irregular spans — bulk insert
        if let spans = json["irregularSpans"] as? [[String: Any]] {
            for i in stride(from: 0, to: spans.count, by: chunkSize) {
                autoreleasepool {
                    let end = min(i + chunkSize, spans.count)
                    for j in i..<end {
                        let s = spans[j]
                        guard let verbId = s["verbId"] as? Int,
                              let tenseId = s["tenseId"] as? String,
                              let personIndex = s["personIndex"] as? Int,
                              let spanType = s["type"] as? Int,
                              let pattern = s["pattern"] as? Int,
                              let start = s["start"] as? Int,
                              let end = s["end"] as? Int else { continue }
                        let span = IrregularSpan(verbId: verbId, tenseId: tenseId, personIndex: personIndex, spanType: spanType, pattern: pattern, start: start, end: end)
                        context.insert(span)
                    }
                    try? context.save()
                }
            }
            print("Inserted \(spans.count) irregular spans")
        }

        do {
            try context.save()
        } catch {
            print("[DataLoader] 🔥 Final verb save error: \(error)")
        }
        print("Verb seeding complete")

        // Seed course data (uses the same mainContext so @Query sees it)
        seedCourseData(context: context)
    }

    /// Re-seed course data if the version has changed (e.g. examples were added).
    /// Call this on every launch — it checks a version key and only re-seeds when needed.
    static func refreshCourseDataIfNeeded(container: ModelContainer) async {
        let currentVersion = 5  // Bump this whenever course_data.json changes
        let key = "courseDataVersion"
        let shared = UserDefaults.standard

        if shared.integer(forKey: key) >= currentVersion { return }

        print("Course data version outdated — re-seeding...")
        let context = ModelContext(container)

        // Delete existing course data
        try? context.delete(model: VocabCard.self)
        try? context.delete(model: CourseDeck.self)
        try? context.save()

        // Re-seed
        seedCourseData(context: context)

        shared.set(currentVersion, forKey: key)
        print("Course data re-seeded to version \(currentVersion)")
    }

    static func migrateCourseProgressIfNeeded(
        localContainer: ModelContainer,
        cloudContainer: ModelContainer
    ) async {
        let migrationVersion = 2
        let key = "courseProgressMigrationVersion"
        let shared = UserDefaults.standard

        if shared.integer(forKey: key) >= migrationVersion { return }

        let localContext = ModelContext(localContainer)
        let cloudContext = ModelContext(cloudContainer)
        let descriptor = FetchDescriptor<VocabCard>()
        let allCards = (try? localContext.fetch(descriptor)) ?? []
        var migratedCount = 0

        for card in allCards where hasLegacyCourseProgress(card) {
            let reviewKey = CourseCardStore.reviewKey(for: card)
            let reviewCard = findOrCreateCourseReviewCard(
                id: reviewKey,
                deckId: card.deckId,
                front: card.front,
                back: card.back,
                context: cloudContext
            )

            if let reviewDate = reviewCard.lastReviewDate,
               let legacyDate = card.lastReviewDate,
               reviewDate >= legacyDate {
                continue
            }

            reviewCard.easeFactor = card.easeFactor
            reviewCard.interval = card.interval
            reviewCard.repetitions = card.repetitions
            reviewCard.dueDate = card.dueDate
            reviewCard.lastReviewDate = card.lastReviewDate
            migratedCount += 1
        }

        if migratedCount > 0 {
            try? cloudContext.save()
            print("Migrated \(migratedCount) course progress cards to cloud store")
        }

        shared.set(migrationVersion, forKey: key)
    }

    private static func seedCourseData(context: ModelContext) {
        let url = Bundle.main.url(forResource: "course_data", withExtension: "json")
            ?? Bundle.main.bundleURL.appendingPathComponent("course_data.json")

        guard let data = try? Data(contentsOf: url) else {
            print("No course_data.json found — skipping course seeding")
            return
        }

        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
            print("ERROR: Could not parse course_data.json")
            return
        }

        // Support both formats: {"courses": [...]} (new) and {"course": "...", "weeks": [...]} (old)
        var courseList: [[String: Any]] = []
        if let courses = json["courses"] as? [[String: Any]] {
            courseList = courses
        } else if json["weeks"] != nil {
            courseList = [json]
        }

        var deckCount = 0
        var cardCount = 0

        for courseData in courseList {
            guard let weeks = courseData["weeks"] as? [[String: Any]],
                  let courseName = courseData["course"] as? String else { continue }

            let courseSlug = courseName.lowercased()
                .replacingOccurrences(of: " ", with: "-")
                .replacingOccurrences(of: "|", with: "")

            for weekData in weeks {
                guard let weekNum = weekData["week"] as? Int,
                      let decks = weekData["decks"] as? [[String: Any]] else { continue }

                for (deckIndex, deckData) in decks.enumerated() {
                    guard let title = deckData["title"] as? String,
                          let cards = deckData["cards"] as? [Any] else { continue }

                    let isReversed = (deckData["isReversed"] as? Bool) ?? false
                    let deckId = "\(courseSlug)_w\(weekNum)_\(deckIndex)_\(isReversed ? "rev" : "fwd")"

                    let deck = CourseDeck(
                    id: deckId,
                    weekNumber: weekNum,
                    title: title,
                    cardCount: cards.count,
                    courseName: courseName,
                    isReversed: isReversed
                )
                context.insert(deck)
                deckCount += 1

                for rawCard in cards {
                    guard let cardDict = rawCard as? [String: Any],
                          let front = cardDict["front"] as? String,
                          let back = cardDict["back"] as? String else { continue }

                    // Parse example sentences
                    var exES: [String] = []
                    var exEN: [String] = []
                    var exBlanks: [String] = []
                    if let examples = cardDict["examples"] as? [[String: String]] {
                        for ex in examples {
                            if let es = ex["es"] {
                                exES.append(es)
                                exEN.append(ex["en"] ?? "")
                                exBlanks.append(ex["blank"] ?? "")
                            }
                        }
                    }

                    let card = VocabCard(front: front, back: back, deckId: deckId, examplesES: exES, examplesEN: exEN, examplesBlanks: exBlanks)
                    card.deck = deck
                    context.insert(card)
                    cardCount += 1
                }
            }

            try? context.save()
            }
        }

        print("Course seeding complete: \(deckCount) decks, \(cardCount) cards")
    }

    private static func hasLegacyCourseProgress(_ card: VocabCard) -> Bool {
        card.repetitions > 0 ||
        card.interval > 0 ||
        abs(card.easeFactor - 2.5) > 0.0001 ||
        card.lastReviewDate != nil
    }

    private static func findOrCreateCourseReviewCard(
        id: String,
        deckId: String,
        front: String,
        back: String,
        context: ModelContext
    ) -> CourseReviewCard {
        let descriptor = FetchDescriptor<CourseReviewCard>(
            predicate: #Predicate<CourseReviewCard> { $0.id == id }
        )

        if let existing = (try? context.fetch(descriptor))?.first {
            return existing
        }

        let reviewCard = CourseReviewCard(id: id, deckId: deckId, front: front, back: back)
        context.insert(reviewCard)
        return reviewCard
    }
}
