import Testing
import Foundation
import SwiftData
@testable import nahbar

// MARK: - RatingCategory Tests

@Suite("RatingCategory – Enum")
struct RatingCategoryTests {

    @Test("Alle 4 Kategorien vorhanden")
    func allCasesCount() {
        #expect(RatingCategory.allCases.count == 4)
    }

    @Test("rawValues sind nicht leer")
    func rawValuesNotEmpty() {
        for cat in RatingCategory.allCases {
            #expect(!cat.rawValue.isEmpty)
        }
    }

    @Test("icons sind nicht leer")
    func iconsNotEmpty() {
        for cat in RatingCategory.allCases {
            #expect(!cat.icon.isEmpty)
        }
    }

    @Test("Nachwirkung ist isAftermath-Kategorie")
    func nachwirkungIsAftermathCategory() {
        let aftermathCategories = RatingQuestion.aftermath.map { $0.category }
        #expect(aftermathCategories.allSatisfy { $0 == .nachwirkung })
    }
}

// MARK: - VisitStatus Tests

@Suite("VisitStatus – Enum")
struct VisitStatusTests {

    @Test("rawValues können round-trip-parsed werden")
    func rawValueRoundTrip() {
        let statuses: [VisitStatus] = [.immediateCompleted, .awaitingAftermath, .completed]
        for status in statuses {
            let parsed = VisitStatus(rawValue: status.rawValue)
            #expect(parsed == status)
        }
    }

    @Test("awaitingAftermath rawValue ist 'warte_nachwirkung'")
    func awaitingAftermathRawValue() {
        #expect(VisitStatus.awaitingAftermath.rawValue == "warte_nachwirkung")
    }
}

// MARK: - RatingQuestion Tests

@Suite("RatingQuestion – statische Fragen")
struct RatingQuestionTests {

    @Test("Genau 9 Fragen insgesamt")
    func totalQuestionCount() {
        #expect(RatingQuestion.all.count == 9)
    }

    @Test("Genau 5 Sofort-Fragen")
    func immediateQuestionCount() {
        #expect(RatingQuestion.immediate.count == 5)
    }

    @Test("Genau 4 Nachwirkungs-Fragen")
    func aftermathQuestionCount() {
        #expect(RatingQuestion.aftermath.count == 4)
    }

    @Test("immediate (5) + aftermath (4) = all (9)")
    func immediatePlusAftermathEqualsAll() {
        #expect(RatingQuestion.immediate.count + RatingQuestion.aftermath.count == RatingQuestion.all.count)
        #expect(RatingQuestion.all.count == 9)
    }

    @Test("Alle Fragen haben nicht-leere Texte und Pole")
    func allQuestionsHaveContent() {
        for q in RatingQuestion.all {
            #expect(!q.text.isEmpty)
            #expect(!q.negativePole.isEmpty)
            #expect(!q.positivePole.isEmpty)
        }
    }

    @Test("Sofort-Fragen decken 3 Kategorien ab")
    func immediateQuestionsSpan3Categories() {
        let categories = Set(RatingQuestion.immediate.map { $0.category })
        #expect(categories.count == 3)
        #expect(categories.contains(.selbst))
        #expect(categories.contains(.beziehung))
        #expect(categories.contains(.gespraech))
    }

    @Test("Nachwirkungs-Fragen haben alle isAftermath == true")
    func aftermathFlagsCorrect() {
        for q in RatingQuestion.aftermath {
            #expect(q.isAftermath == true)
        }
        for q in RatingQuestion.immediate {
            #expect(q.isAftermath == false)
        }
    }
}

// MARK: - Rating Tests

@Suite("Rating – Bewertungs-Logik")
struct RatingTests {

    @Test("value nil bedeutet übersprungen")
    func nilValueMeansSkipped() {
        let r = Rating(category: .selbst, questionIndex: 0, value: nil, isAftermath: false)
        #expect(r.value == nil)
    }

    @Test("value -2 ist gültig")
    func minusTwo() {
        let r = Rating(category: .selbst, questionIndex: 0, value: -2, isAftermath: false)
        #expect(r.value == -2)
    }

    @Test("value +2 ist gültig")
    func plusTwo() {
        let r = Rating(category: .selbst, questionIndex: 0, value: 2, isAftermath: false)
        #expect(r.value == 2)
    }

    @Test("category round-trip via rawValue")
    func categoryRoundTrip() {
        let r = Rating(category: .beziehung, questionIndex: 1, value: 1, isAftermath: false)
        #expect(r.category == .beziehung)
        #expect(r.categoryRaw == RatingCategory.beziehung.rawValue)
    }
}

// MARK: - AftermathDelayOption Tests

@Suite("AftermathDelayOption – Einstellungen")
struct AftermathDelayOptionTests {

    @Test("Alle 3 Optionen vorhanden")
    func allCasesCount() {
        #expect(AftermathDelayOption.allCases.count == 3)
    }

    @Test("36h ist der Standard")
    func defaultIs36h() {
        let defaults = UserDefaults(suiteName: "nahbar.test.aftermathDelay")!
        defaults.removeObject(forKey: "aftermathDelayOption")
        // Simuliert fehlenden Wert → Fallback auf .hours36
        let raw = defaults.string(forKey: "aftermathDelayOption") ?? AftermathDelayOption.hours36.rawValue
        let opt = AftermathDelayOption(rawValue: raw)
        #expect(opt == .hours36)
    }

    @Test("Sekunden sind korrekt")
    func secondsAreCorrect() {
        #expect(AftermathDelayOption.hours24.seconds == 24 * 3600)
        #expect(AftermathDelayOption.hours36.seconds == 36 * 3600)
        #expect(AftermathDelayOption.hours48.seconds == 48 * 3600)
    }

    @Test("rawValue round-trip")
    func rawValueRoundTrip() {
        for opt in AftermathDelayOption.allCases {
            #expect(AftermathDelayOption(rawValue: opt.rawValue) == opt)
        }
    }
}

// MARK: - AppLanguage Tests

@Suite("AppLanguage – KI-Spracheinstellung")
struct AppLanguageTests {

    @Test("Genau 2 Sprachen vorhanden")
    func allCasesCount() {
        #expect(AppLanguage.allCases.count == 2)
    }

    @Test("rawValue round-trip")
    func rawValueRoundTrip() {
        for lang in AppLanguage.allCases {
            #expect(AppLanguage(rawValue: lang.rawValue) == lang)
        }
    }

    @Test("german.rawValue ist 'de', english.rawValue ist 'en'")
    func rawValues() {
        #expect(AppLanguage.german.rawValue == "de")
        #expect(AppLanguage.english.rawValue == "en")
    }

    @Test("displayName ist nicht leer")
    func displayNamesNotEmpty() {
        for lang in AppLanguage.allCases {
            #expect(!lang.displayName.isEmpty)
        }
    }

    @Test("systemPrompt ist nicht leer")
    func systemPromptsNotEmpty() {
        for lang in AppLanguage.allCases {
            #expect(!lang.systemPrompt.isEmpty)
        }
    }

    // Regressionswächter: Parse-Tokens dürfen nie aus den Instructions verschwinden,
    // sonst bricht parseResult() im AIAnalysisService still.
    @Test("analysisInstruction enthält alle 3 Parse-Tokens")
    func analysisInstructionContainsParseTokens() {
        for lang in AppLanguage.allCases {
            #expect(lang.analysisInstruction.contains("MUSTER:"),
                    "MUSTER: fehlt in analysisInstruction (\(lang.rawValue))")
            #expect(lang.analysisInstruction.contains("BEZIEHUNG:"),
                    "BEZIEHUNG: fehlt in analysisInstruction (\(lang.rawValue))")
            #expect(lang.analysisInstruction.contains("EMPFEHLUNG:"),
                    "EMPFEHLUNG: fehlt in analysisInstruction (\(lang.rawValue))")
        }
    }

    @Test("giftInstruction enthält alle 3 IDEE-Parse-Tokens")
    func giftInstructionContainsParseTokens() {
        for lang in AppLanguage.allCases {
            #expect(lang.giftInstruction.contains("IDEE 1:"),
                    "IDEE 1: fehlt in giftInstruction (\(lang.rawValue))")
            #expect(lang.giftInstruction.contains("IDEE 2:"),
                    "IDEE 2: fehlt in giftInstruction (\(lang.rawValue))")
            #expect(lang.giftInstruction.contains("IDEE 3:"),
                    "IDEE 3: fehlt in giftInstruction (\(lang.rawValue))")
        }
    }

    @Test("current gibt einen der unterstützten Werte zurück")
    func currentIsValid() {
        // AppLanguage.current liest Locale.current – Ergebnis ist immer ein gültiger Case
        let lang = AppLanguage.current
        #expect(AppLanguage.allCases.contains(lang))
    }

    @Test("unbekannte Sprachcodes fallen auf .german zurück")
    func unknownCodeFallsBackToGerman() {
        let lang = AppLanguage(rawValue: "zz") ?? .german
        #expect(lang == .german)
    }
}

// MARK: - Lokalisierungs-Regressionswächter

// Fragen-Texte, Pol-Labels und Kategorie-rawValues werden per LocalizedStringKey()
// als Schlüssel in Localizable.xcstrings nachgeschlagen. Jede Änderung eines Texts
// würde die englische Übersetzung still brechen – diese Tests schützen dagegen.

@Suite("VisitRating – Lokalisierungs-Schlüssel")
struct VisitRatingLocalizationKeyTests {

    @Test("Sofort-Fragen-Texte sind stabile Schlüssel")
    func immediateQuestionTextsAreStable() {
        let q = RatingQuestion.immediate
        #expect(q[0].text == "Wie hast du dich während des Treffens gefühlt?")
        #expect(q[1].text == "Wie ist dein Energielevel nach dem Treffen?")
        #expect(q[2].text == "Fühlt sich die Beziehung gestärkt an?")
        #expect(q[3].text == "War das Treffen ausgeglichen (Geben/Nehmen)?")
        #expect(q[4].text == "Wie tiefgehend waren die Gespräche?")
    }

    @Test("Nachwirkungs-Fragen-Texte sind stabile Schlüssel")
    func aftermathQuestionTextsAreStable() {
        let q = RatingQuestion.aftermath
        #expect(q[0].text == "Möchtest du die Person bald wiedersehen?")
        #expect(q[1].text == "Wie denkst du jetzt über das Treffen?")
        #expect(q[2].text == "Hat sich deine Sicht auf die Person verändert?")
        #expect(q[3].text == "Würdest du ein ähnliches Treffen wiederholen?")
    }

    @Test("Pol-Labels der Sofort-Fragen sind stabile Schlüssel")
    func immediatePoleLabelsAreStable() {
        let q = RatingQuestion.immediate
        #expect(q[0].negativePole == "Unwohl");        #expect(q[0].positivePole == "Sehr wohl")
        #expect(q[1].negativePole == "Erschöpft");     #expect(q[1].positivePole == "Energiegeladen")
        #expect(q[2].negativePole == "Distanzierter"); #expect(q[2].positivePole == "Viel näher")
        #expect(q[3].negativePole == "Sehr einseitig"); #expect(q[3].positivePole == "Perfekt ausgeglichen")
        #expect(q[4].negativePole == "Nur Smalltalk"); #expect(q[4].positivePole == "Sehr tiefgründig")
    }

    @Test("Pol-Labels der Nachwirkungs-Fragen sind stabile Schlüssel")
    func aftermathPoleLabelsAreStable() {
        let q = RatingQuestion.aftermath
        #expect(q[0].negativePole == "Eher nicht");        #expect(q[0].positivePole == "Unbedingt")
        #expect(q[1].negativePole == "Eher negativ");      #expect(q[1].positivePole == "Sehr positiv")
        #expect(q[2].negativePole == "Zum Schlechteren");  #expect(q[2].positivePole == "Zum Besseren")
        #expect(q[3].negativePole == "Eher nicht");        #expect(q[3].positivePole == "Sofort wieder")
    }

    @Test("Kategorie-rawValues sind stabile Schlüssel")
    func categoryRawValuesAreStable() {
        #expect(RatingCategory.selbst.rawValue      == "Selbst")
        #expect(RatingCategory.beziehung.rawValue   == "Beziehung")
        #expect(RatingCategory.gespraech.rawValue   == "Gespräch")
        #expect(RatingCategory.nachwirkung.rawValue == "Nachwirkung")
    }
}

// MARK: - AftermathNotificationManager – Konstanten

@Suite("AftermathNotificationManager – Konstanten")
struct AftermathNotificationManagerTests {

    @Test("categoryID ist unveränderlich")
    func categoryID() {
        #expect(AftermathNotificationManager.categoryID == "AFTERMATH_RATING")
    }

    @Test("actionID ist unveränderlich")
    func actionID() {
        #expect(AftermathNotificationManager.actionID == "RATE_NOW")
    }

    @Test("momentIDKey ist unveränderlich")
    func momentIDKey() {
        #expect(AftermathNotificationManager.momentIDKey == "momentID")
    }

    @Test("personNameKey ist unveränderlich")
    func personNameKey() {
        #expect(AftermathNotificationManager.personNameKey == "personName")
    }
}

// MARK: - Schema-Regressionswächter (V4)

@Suite("Schema – Regressionswächter V4")
struct SchemaV4RegressionTests {

    @Test("NahbarSchemaV4 hat Version 4.0.0")
    func schemaV4HasCorrectVersion() {
        #expect(NahbarSchemaV4.versionIdentifier.major == 4)
        #expect(NahbarSchemaV4.versionIdentifier.minor == 0)
        #expect(NahbarSchemaV4.versionIdentifier.patch == 0)
    }
}

// MARK: - Schema-Regressionswächter (V5)

@Suite("Schema – Regressionswächter V5")
struct SchemaV5RegressionTests {

    @Test("NahbarSchemaV5 hat Version 5.0.0")
    func schemaV5HasCorrectVersion() {
        #expect(NahbarSchemaV5.versionIdentifier.major == 5)
        #expect(NahbarSchemaV5.versionIdentifier.minor == 0)
        #expect(NahbarSchemaV5.versionIdentifier.patch == 0)
    }

    @Test("Migrationsplan enthält genau 9 Schemas (V1–V9)")
    func migrationPlanHasNineSchemas() {
        #expect(NahbarMigrationPlan.schemas.count == 9)
    }

    @Test("Migrationsplan enthält genau 8 Stages (V1→V2 bis V8→V9)")
    func migrationPlanHasEightStages() {
        #expect(NahbarMigrationPlan.stages.count == 8)
    }
}
