import Testing
@testable import SharedModels

@Suite("SentenceQuizEngine")
struct SentenceQuizEngineTests {

    // MARK: - hasValidSentence

    @Test("No examples returns false")
    func noExamples() {
        let card = VocabCard(front: "comer", back: "to eat", deckId: "d", examplesES: [], examplesEN: [])
        #expect(SentenceQuizEngine.hasValidSentence(for: card) == false)
    }

    @Test("Example containing target word returns true via substring fallback")
    func substringMatch() {
        let card = VocabCard(
            front: "manzana",
            back: "apple",
            deckId: "d",
            examplesES: ["Yo como una manzana roja."],
            examplesEN: ["I eat a red apple."]
        )
        #expect(SentenceQuizEngine.hasValidSentence(for: card))
    }

    @Test("Example whose stored blank appears returns true even if target word is missing")
    func storedBlankMatch() {
        let card = VocabCard(
            front: "comer",
            back: "to eat",
            deckId: "d",
            examplesES: ["Yo como manzanas todos los días."],
            examplesEN: ["I eat apples every day."],
            examplesBlanks: ["como"]
        )
        #expect(SentenceQuizEngine.hasValidSentence(for: card))
    }

    @Test("Example with neither stored blank nor substring match returns false for that example")
    func neitherMatches() {
        let card = VocabCard(
            front: "comer",
            back: "to eat",
            deckId: "d",
            examplesES: ["Ella prepara la cena."],
            examplesEN: ["She prepares dinner."]
        )
        #expect(SentenceQuizEngine.hasValidSentence(for: card) == false)
    }

    @Test("At least one resolvable example across many makes the card valid")
    func oneOfManyResolves() {
        let card = VocabCard(
            front: "comer",
            back: "to eat",
            deckId: "d",
            examplesES: [
                "Ella prepara la cena.",
                "Los niños van al parque.",
                "Quiero comer algo ahora."
            ],
            examplesEN: ["", "", ""]
        )
        #expect(SentenceQuizEngine.hasValidSentence(for: card))
        #expect(SentenceQuizEngine.resolvableIndices(for: card) == [2])
    }

    @Test("Phonetic glosses are rejected (too few words)")
    func phoneticGlossRejected() {
        let card = VocabCard(
            front: "discutir",
            back: "to discuss",
            deckId: "d",
            examplesES: [
                "discutir(dees-koo-teer)",
                "INTRANSITIVE VERB",
                "Los amigos van a discutir el tema."
            ],
            examplesEN: ["", "", "The friends are going to discuss the topic."]
        )
        // Only index 2 is a real sentence (≥4 words AND contains the target)
        #expect(SentenceQuizEngine.hasValidSentence(for: card))
        #expect(SentenceQuizEngine.resolvableIndices(for: card) == [2])
        // Phonetic entry at index 0 returns nil
        #expect(SentenceQuizEngine.buildQuestion(for: card, exampleIndex: 0) == nil)
    }

    // MARK: - buildQuestion (deterministic)

    @Test("Builds question from substring match, preserves original casing")
    func buildFromSubstring() {
        let card = VocabCard(
            front: "manzana",
            back: "apple",
            deckId: "d",
            examplesES: ["Yo como una manzana roja."],
            examplesEN: ["I eat a red apple."]
        )
        let question = SentenceQuizEngine.buildQuestion(for: card, exampleIndex: 0)
        #expect(question != nil)
        #expect(question?.sentenceES == "Yo como una manzana roja.")
        #expect(question?.sentenceEN == "I eat a red apple.")
        #expect(question?.blankWord == "manzana")
        #expect(question?.displayTemplate == "Yo como una _____ roja.")
        #expect(question?.exampleIndex == 0)
    }

    @Test("Builds question from stored blank when provided")
    func buildFromStoredBlank() {
        let card = VocabCard(
            front: "comer",
            back: "to eat",
            deckId: "d",
            examplesES: ["Yo como manzanas todos los días."],
            examplesEN: ["I eat apples every day."],
            examplesBlanks: ["como"]
        )
        let question = SentenceQuizEngine.buildQuestion(for: card, exampleIndex: 0)
        #expect(question?.blankWord == "como")
        #expect(question?.displayTemplate == "Yo _____ manzanas todos los días.")
    }

    @Test("Stored blank takes precedence over substring match")
    func storedBlankWins() {
        // Card teaches "manzana" (would substring-match), but the stored blank is the verb "como"
        let card = VocabCard(
            front: "manzana",
            back: "apple",
            deckId: "d",
            examplesES: ["Yo como una manzana."],
            examplesEN: ["I eat an apple."],
            examplesBlanks: ["como"]
        )
        let question = SentenceQuizEngine.buildQuestion(for: card, exampleIndex: 0)
        #expect(question?.blankWord == "como")
        #expect(question?.displayTemplate == "Yo _____ una manzana.")
    }

    @Test("Falls back to substring match when stored blank is empty")
    func fallbackWhenStoredBlankEmpty() {
        let card = VocabCard(
            front: "manzana",
            back: "apple",
            deckId: "d",
            examplesES: ["Yo como una manzana."],
            examplesEN: ["I eat an apple."],
            examplesBlanks: [""]
        )
        let question = SentenceQuizEngine.buildQuestion(for: card, exampleIndex: 0)
        #expect(question?.blankWord == "manzana")
    }

    @Test("Falls back to substring match when stored blank doesn't actually appear in the sentence")
    func fallbackWhenStoredBlankMissing() {
        let card = VocabCard(
            front: "manzana",
            back: "apple",
            deckId: "d",
            examplesES: ["Yo como una manzana."],
            examplesEN: ["I eat an apple."],
            examplesBlanks: ["nonexistent"]
        )
        let question = SentenceQuizEngine.buildQuestion(for: card, exampleIndex: 0)
        #expect(question?.blankWord == "manzana")
    }

    @Test("Preserves original capitalization when blanking (substring is case-insensitive)")
    func preservesCapitalization() {
        let card = VocabCard(
            front: "hola",
            back: "hello",
            deckId: "d",
            examplesES: ["Hola amiga, ¿cómo estás hoy?"],
            examplesEN: ["Hello friend, how are you today?"]
        )
        let question = SentenceQuizEngine.buildQuestion(for: card, exampleIndex: 0)
        #expect(question?.blankWord == "Hola")
        #expect(question?.displayTemplate == "_____ amiga, ¿cómo estás hoy?")
    }

    @Test("Blanks phrase cards when target front contains spaces")
    func phraseCardBlank() {
        let card = VocabCard(
            front: "¿cómo estás?",
            back: "how are you?",
            deckId: "d",
            examplesES: ["Hola amiga, ¿cómo estás? Estoy bien."],
            examplesEN: ["Hi friend, how are you? I am well."]
        )
        let question = SentenceQuizEngine.buildQuestion(for: card, exampleIndex: 0)
        #expect(question?.blankWord == "¿cómo estás?")
        #expect(question?.displayTemplate == "Hola amiga, _____ Estoy bien.")
    }

    @Test("Returns nil when the example has no resolvable blank")
    func unresolvableExampleReturnsNil() {
        let card = VocabCard(
            front: "comer",
            back: "to eat",
            deckId: "d",
            examplesES: ["Ella prepara la cena."],
            examplesEN: ["She prepares dinner."]
        )
        let question = SentenceQuizEngine.buildQuestion(for: card, exampleIndex: 0)
        #expect(question == nil)
    }

    @Test("Returns nil when example index is out of range")
    func outOfRangeIndexReturnsNil() {
        let card = VocabCard(
            front: "comer",
            back: "to eat",
            deckId: "d",
            examplesES: ["Yo como."],
            examplesEN: [""]
        )
        #expect(SentenceQuizEngine.buildQuestion(for: card, exampleIndex: 5) == nil)
        #expect(SentenceQuizEngine.buildQuestion(for: card, exampleIndex: -1) == nil)
    }

    // MARK: - buildQuestion (random)

    @Test("Random buildQuestion always picks a resolvable example")
    func randomPickIsResolvable() {
        let card = VocabCard(
            front: "comer",
            back: "to eat",
            deckId: "d",
            examplesES: [
                "Ella prepara la cena.",              // unresolvable (no "comer")
                "Los niños van al parque.",           // unresolvable
                "Quiero comer algo rico ahora.",      // resolvable (substring, ≥4 words)
                "El perro come su comida diaria."     // unresolvable — "come" but not "comer"
            ],
            examplesEN: ["", "", "", ""]
        )
        // Only index 2 is resolvable (contains "comer" literally and has ≥4 words)
        for _ in 0..<25 {
            let q = SentenceQuizEngine.buildQuestion(for: card)
            #expect(q?.exampleIndex == 2)
        }
    }

    @Test("Random buildQuestion returns nil when no examples resolve")
    func randomNilWhenNothingResolves() {
        let card = VocabCard(
            front: "comer",
            back: "to eat",
            deckId: "d",
            examplesES: ["Ella prepara la cena."],
            examplesEN: [""]
        )
        #expect(SentenceQuizEngine.buildQuestion(for: card) == nil)
    }

    // MARK: - Array alignment edge cases

    @Test("examplesBlanks shorter than examplesES is handled gracefully")
    func blanksArrayShorterThanExamples() {
        let card = VocabCard(
            front: "comer",
            back: "to eat",
            deckId: "d",
            examplesES: ["Yo como mucho pan.", "Tú comes en casa."],
            examplesEN: ["I eat a lot of bread.", "You eat at home."],
            examplesBlanks: ["como"] // only covers index 0
        )
        // Index 0: stored blank match
        let q0 = SentenceQuizEngine.buildQuestion(for: card, exampleIndex: 0)
        #expect(q0?.blankWord == "como")
        // Index 1: no stored blank, "comer" doesn't appear literally → unresolvable
        let q1 = SentenceQuizEngine.buildQuestion(for: card, exampleIndex: 1)
        #expect(q1 == nil)
    }

    @Test("Display template uses the engine's blank marker constant")
    func blankMarkerConstant() {
        let card = VocabCard(
            front: "perro",
            back: "dog",
            deckId: "d",
            examplesES: ["El perro ladra todo el día."],
            examplesEN: ["The dog barks all day."]
        )
        let q = SentenceQuizEngine.buildQuestion(for: card, exampleIndex: 0)
        #expect(q?.displayTemplate.contains(SentenceQuizEngine.blankMarker) == true)
    }
}
