import Foundation
import SharedModels
import SwiftData

struct PracticeSettings: Sendable {
    let selectedLevel: String
    let selectedLevels: Set<String>
    let enabledTenses: Set<String>
    let enabledIrregularCategories: Set<IrregularSpan.SpanCategory>
    let showVosotros: Bool
    let showReflexiveVerbsOnly: Bool
    let reflexiveBaseInfinitives: Set<String>

    init(progress: UserProgress?, reflexiveBaseInfinitives: Set<String> = []) {
        let resolvedTenses = progress?.enabledTenseIDs ?? []
        let resolvedLevels = progress?.selectedVerbLevels ?? []
        self.selectedLevel = progress?.selectedLevel ?? VerbLevel.basic.rawValue
        self.selectedLevels = Set(resolvedLevels.map(\.rawValue))
        self.enabledTenses = Set(resolvedTenses)
        self.enabledIrregularCategories = progress?.enabledIrregularCategories ?? []
        self.showVosotros = progress?.showVosotros ?? true
        self.showReflexiveVerbsOnly = progress?.showReflexiveVerbsOnly ?? false
        self.reflexiveBaseInfinitives = reflexiveBaseInfinitives
    }

    var selectionTenseIDs: [String] {
        enabledTenses.isEmpty ? TenseID.defaultPracticeIDs : Array(enabledTenses)
    }
}

struct PracticeCardLoad {
    let verb: Verb
    let form: VerbForm
    let spans: [IrregularSpan]
    let tenseInfo: TenseInfo?
    let person: String
}

struct FullTablePrompt {
    let verb: Verb
    let tenseInfo: TenseInfo
    let forms: [VerbForm]
}

struct PracticeSessionService {
    let localContext: ModelContext
    let cloudContext: ModelContext
    let reflexiveBaseInfinitives: Set<String>
    private let referenceStore: ReferenceStore

    init(
        localContext: ModelContext,
        cloudContext: ModelContext,
        reflexiveBaseInfinitives: Set<String> = []
    ) {
        self.localContext = localContext
        self.cloudContext = cloudContext
        self.reflexiveBaseInfinitives = reflexiveBaseInfinitives
        self.referenceStore = ReferenceStore(context: localContext)
    }

    func settings() -> PracticeSettings {
        PracticeSettings(
            progress: ReviewStore.fetchOrCreateUserProgress(context: cloudContext),
            reflexiveBaseInfinitives: reflexiveBaseInfinitives
        )
    }

    func nextCard(for focusMode: FocusMode, excludingVerbId lastVerbId: Int? = nil) -> PracticeCardLoad? {
        switch focusMode {
        case .weakVerbs:
            if let form = pickWeakForm() {
                return loadCard(from: form)
            }
        case .irregularity(let filter):
            if let form = pickIrregularForm(filter: filter) {
                return loadCard(from: form)
            }
        case .commonTenses:
            if let form = pickCommonTenseForm() {
                return loadCard(from: form)
            }
        case .none:
            break
        }

        if let dueCard = fetchDueCard(excluding: lastVerbId) {
            return loadCard(from: dueCard)
        }

        if let form = pickRandomForm() {
            return loadCard(from: form)
        }

        return nil
    }

    /// Builds a Full Table prompt. `previousTenseId` / `previousEnding` describe
    /// the prompt just shown; when possible the next prompt avoids repeating the
    /// same tense back-to-back and switches the verb's ending family
    /// (-ar ⇄ -er/-ir) so consecutive rounds feel varied. Both are best-effort:
    /// if honouring them would leave no eligible combo, the constraint is
    /// dropped rather than dead-ending.
    func randomFullTablePrompt(
        previousTenseId: String? = nil,
        previousEnding: String? = nil
    ) -> FullTablePrompt? {
        let settings = settings()
        // Full Table is testing the user's grasp of regular conjugation patterns,
        // not vocabulary recognition. Level filter is intentionally bypassed so
        // we draw from the entire verb pool — being able to conjugate `hablar`
        // regularly transfers to any other regular verb regardless of "level".
        // Irregular-category and tense filters still apply via downstream checks.
        let verbs = applyReflexiveFilter(
            to: referenceStore.fetchVerbs(),
            settings: settings
        )
        guard !verbs.isEmpty else { return nil }

        let candidateTenseIds = settings.selectionTenseIDs
        guard !candidateTenseIds.isEmpty else { return nil }

        let tenseChoices = tenseChoicesAvoidingRepeat(candidateTenseIds, previous: previousTenseId)
        let verbChoices = verbChoicesSwitchingFamily(verbs, previousEnding: previousEnding)
        let isConstrained = tenseChoices.count != candidateTenseIds.count
            || verbChoices.count != verbs.count

        // Best-effort: random-sample the constrained pool first so consecutive
        // prompts vary the tense and the -ar/-er-ir family.
        if isConstrained,
           let prompt = sampleFullTablePrompt(verbs: verbChoices, tenseIds: tenseChoices) {
            return prompt
        }

        // No constraint, or honouring it found nothing quickly — sample the
        // full pool.
        if let prompt = sampleFullTablePrompt(verbs: verbs, tenseIds: candidateTenseIds) {
            return prompt
        }

        // Guarantee: if any eligible (verb, tense) combo exists in the data we
        // return one. Only return nil when the user's settings genuinely produce
        // an empty pool (so the UI can show an error state instead of a blank).
        for verb in verbs.shuffled() {
            for tenseId in candidateTenseIds.shuffled() {
                guard let tenseInfo = TenseInfo.find(tenseId) else { continue }
                if let prompt = makePromptIfFullyRegular(verb: verb, tenseId: tenseId, tenseInfo: tenseInfo) {
                    return prompt
                }
            }
        }

        return nil
    }

    /// Random-sample up to 40 (verb, tense) pairs for a fully-regular combo.
    /// With ~1750 verbs and several hundred eligible combos this almost always
    /// succeeds within a handful of attempts.
    private func sampleFullTablePrompt(verbs: [Verb], tenseIds: [String]) -> FullTablePrompt? {
        guard !verbs.isEmpty, !tenseIds.isEmpty else { return nil }
        for _ in 0..<40 {
            guard let verb = verbs.randomElement(),
                  let tenseId = tenseIds.randomElement(),
                  let tenseInfo = TenseInfo.find(tenseId) else { continue }
            if let prompt = makePromptIfFullyRegular(verb: verb, tenseId: tenseId, tenseInfo: tenseInfo) {
                return prompt
            }
        }
        return nil
    }

    /// Tense ids with the previous one removed — only when more than one tense
    /// is selected and removing it still leaves a choice.
    private func tenseChoicesAvoidingRepeat(_ ids: [String], previous: String?) -> [String] {
        guard ids.count > 1, let previous else { return ids }
        let filtered = ids.filter { $0 != previous }
        return filtered.isEmpty ? ids : filtered
    }

    /// Verbs whose ending family (-ar vs -er/-ir) differs from the previous
    /// verb's — only when both families are actually present in the pool.
    private func verbChoicesSwitchingFamily(_ verbs: [Verb], previousEnding: String?) -> [Verb] {
        guard let previousEnding else { return verbs }
        let previousIsAr = (previousEnding == "ar")
        let switched = verbs.filter { ($0.ending == "ar") != previousIsAr }
        return switched.isEmpty ? verbs : switched
    }

    /// Returns a `FullTablePrompt` if this verb's forms in the given tense
    /// follow the regular pattern (per `FullTableEligibility`). Nil otherwise.
    private func makePromptIfFullyRegular(
        verb: Verb,
        tenseId: String,
        tenseInfo: TenseInfo
    ) -> FullTablePrompt? {
        let forms = referenceStore.fetchForms(verbId: verb.id, tenseId: tenseId)
        guard !forms.isEmpty else { return nil }
        // Forms must arrive in personIndex order so the regularity array lines
        // up. `fetchForms` already sorts them, but assert for safety.
        let sorted = forms.sorted { $0.personIndex < $1.personIndex }
        let regularities = sorted.map { $0.regularity }
        guard FullTableEligibility.isFullyRegular(regularities: regularities) else { return nil }
        return FullTablePrompt(verb: verb, tenseInfo: tenseInfo, forms: sorted)
    }

    func rate(verbId: Int, tenseId: String, personIndex: Int, quality: ReviewQuality) -> [Badge] {
        ReviewStore.recordReview(
            verbId: verbId,
            tenseId: tenseId,
            personIndex: personIndex,
            quality: quality,
            context: cloudContext,
            referenceContext: localContext
        )
    }

    func recordFullTableReview(verbId: Int, tenseId: String, results: [Int: Bool]) -> [Badge] {
        ReviewStore.recordFullTableReview(
            verbId: verbId,
            tenseId: tenseId,
            results: results,
            context: cloudContext,
            referenceContext: localContext
        )
    }

    func loadCard(from reviewCard: ReviewCard) -> PracticeCardLoad? {
        guard let verb = referenceStore.fetchVerb(id: reviewCard.verbId),
              let form = referenceStore.fetchForm(
                verbId: reviewCard.verbId,
                tenseId: reviewCard.tenseId,
                personIndex: reviewCard.personIndex
              ) else { return nil }
        return buildCardLoad(verb: verb, form: form)
    }

    func loadCard(from form: VerbForm) -> PracticeCardLoad? {
        guard let verb = referenceStore.fetchVerb(id: form.verbId) else { return nil }
        return buildCardLoad(verb: verb, form: form)
    }

    /// When the user has "Reflexive verbs only" enabled, restrict the allowed
    /// verb-id set to IDs whose infinitive is in the curated list.
    /// No-op otherwise.
    private func applyReflexiveFilter(to ids: Set<Int>, settings: PracticeSettings) -> Set<Int> {
        guard settings.showReflexiveVerbsOnly, !settings.reflexiveBaseInfinitives.isEmpty else {
            return ids
        }
        let matching = ids.filter { id in
            guard let verb = referenceStore.fetchVerb(id: id) else { return false }
            return settings.reflexiveBaseInfinitives.contains(verb.infinitive.lowercased())
        }
        return matching
    }

    private func applyReflexiveFilter(to verbs: [Verb], settings: PracticeSettings) -> [Verb] {
        guard settings.showReflexiveVerbsOnly, !settings.reflexiveBaseInfinitives.isEmpty else {
            return verbs
        }
        return verbs.filter { settings.reflexiveBaseInfinitives.contains($0.infinitive.lowercased()) }
    }

    private func buildCardLoad(verb: Verb, form: VerbForm) -> PracticeCardLoad {
        let spans = referenceStore.fetchSpans(
            verbId: form.verbId,
            tenseId: form.tenseId,
            personIndex: form.personIndex
        )
        let person = TenseInfo.persons.indices.contains(form.personIndex)
            ? TenseInfo.persons[form.personIndex]
            : ""

        return PracticeCardLoad(
            verb: verb,
            form: form,
            spans: spans,
            tenseInfo: TenseInfo.find(form.tenseId),
            person: person
        )
    }

    private func fetchDueCard(excluding lastVerbId: Int?) -> ReviewCard? {
        let settings = settings()
        let allowedVerbIds = applyReflexiveFilter(
            to: referenceStore.allowedVerbIDs(
                selectedLevels: settings.selectedLevels,
                irregularCategories: settings.enabledIrregularCategories
            ),
            settings: settings
        )
        let now = Date()
        var descriptor = FetchDescriptor<ReviewCard>(
            predicate: #Predicate<ReviewCard> { $0.dueDate <= now },
            sortBy: [SortDescriptor(\ReviewCard.dueDate)]
        )
        descriptor.fetchLimit = settings.enabledTenses.isEmpty ? 10 : 50
        let cards = (try? cloudContext.fetch(descriptor)) ?? []

        let eligible = cards.filter { card in
            allowedVerbIds.contains(card.verbId) &&
            (settings.enabledTenses.isEmpty || settings.enabledTenses.contains(card.tenseId)) &&
            (settings.showVosotros || card.personIndex != 4)
        }

        // Prefer a card from a different verb than the last one shown.
        // Fall back to the same verb only if it's the sole due card.
        if let lastVerbId {
            if let different = eligible.first(where: { $0.verbId != lastVerbId }) {
                return different
            }
        }
        return eligible.first
    }

    private func pickWeakForm() -> VerbForm? {
        let settings = settings()
        let allowedVerbIds = applyReflexiveFilter(
            to: referenceStore.allowedVerbIDs(
                selectedLevels: settings.selectedLevels,
                irregularCategories: settings.enabledIrregularCategories
            ),
            settings: settings
        )

        let descriptor = FetchDescriptor<ReviewCard>(
            predicate: #Predicate<ReviewCard> { $0.easeFactor < 2.0 && $0.repetitions > 0 },
            sortBy: [SortDescriptor(\ReviewCard.easeFactor)]
        )
        let cards = ((try? cloudContext.fetch(descriptor)) ?? []).filter { card in
            allowedVerbIds.contains(card.verbId) &&
            (settings.enabledTenses.isEmpty || settings.enabledTenses.contains(card.tenseId)) &&
            (settings.showVosotros || card.personIndex != 4)
        }

        guard let card = cards.randomElement() else { return nil }
        return referenceStore.fetchForm(
            verbId: card.verbId,
            tenseId: card.tenseId,
            personIndex: card.personIndex
        )
    }

    private func pickIrregularForm(filter: IrregularityFilter) -> VerbForm? {
        let settings = settings()
        // Focus mode explicitly selects one irregular category, so the user's
        // settings-level irregular filter is deliberately skipped here.
        let allowedVerbIds = applyReflexiveFilter(
            to: referenceStore.allowedVerbIDs(
                selectedLevels: settings.selectedLevels,
                irregularCategories: []
            ),
            settings: settings
        )
        let typeRange: ClosedRange<Int>

        switch filter {
        case .spelling:
            typeRange = 100...199
        case .stemChange:
            typeRange = 200...299
        case .uniqueIrregular:
            typeRange = 300...399
        }

        var descriptor = FetchDescriptor<IrregularSpan>(
            predicate: #Predicate<IrregularSpan> { span in
                span.spanType >= typeRange.lowerBound &&
                span.spanType <= typeRange.upperBound
            }
        )
        descriptor.fetchLimit = 500

        let spans = ((try? localContext.fetch(descriptor)) ?? []).filter { span in
            allowedVerbIds.contains(span.verbId) &&
            (settings.enabledTenses.isEmpty || settings.enabledTenses.contains(span.tenseId)) &&
            (settings.showVosotros || span.personIndex != 4)
        }

        guard let span = spans.randomElement() else { return nil }
        return referenceStore.fetchForm(
            verbId: span.verbId,
            tenseId: span.tenseId,
            personIndex: span.personIndex
        )
    }

    private func pickCommonTenseForm() -> VerbForm? {
        let settings = settings()
        let coreTenseIDs = TenseID.coreTenseIDs
        let verbs = applyReflexiveFilter(
            to: referenceStore.fetchVerbs(
                selectedLevels: settings.selectedLevels,
                irregularCategories: settings.enabledIrregularCategories
            ),
            settings: settings
        )
        guard let verb = verbs.randomElement() else { return nil }

        let forms = referenceStore.fetchVerbForms(verbId: verb.id).filter { form in
            coreTenseIDs.contains(form.tenseId) &&
            (settings.showVosotros || form.personIndex != 4)
        }

        return forms.randomElement()
    }

    private func pickRandomForm() -> VerbForm? {
        let settings = settings()
        let verbs = applyReflexiveFilter(
            to: referenceStore.fetchVerbs(
                selectedLevels: settings.selectedLevels,
                irregularCategories: settings.enabledIrregularCategories
            ),
            settings: settings
        )
        guard let verb = verbs.randomElement() else { return nil }

        let forms = referenceStore.fetchVerbForms(verbId: verb.id).filter { form in
            (settings.enabledTenses.isEmpty || settings.enabledTenses.contains(form.tenseId)) &&
            (settings.showVosotros || form.personIndex != 4)
        }

        return forms.randomElement()
    }
}
