import Testing
import Foundation
@testable import nahbar

// MARK: - SubscriptionTier Tests

@Suite("SubscriptionTier – Enum")
struct SubscriptionTierTests {

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

    @Test("Pro productID ist 'profeatures'")
    func proProductID() {
        #expect(SubscriptionTier.pro.productID == "profeatures")
    }

    @Test("Max productID ist 'maxfeatures'")
    func maxProductID() {
        #expect(SubscriptionTier.max.productID == "maxfeatures")
    }

    @Test("Pro displayName ist 'Pro'")
    func proDisplayName() {
        #expect(SubscriptionTier.pro.displayName == "Pro")
    }

    @Test("Max displayName ist 'Max'")
    func maxDisplayName() {
        #expect(SubscriptionTier.max.displayName == "Max")
    }

    @Test("productIDs sind einzigartig")
    func productIDsAreUnique() {
        let ids = SubscriptionTier.allCases.map { $0.productID }
        #expect(Set(ids).count == SubscriptionTier.allCases.count)
    }

    @Test("displayNames sind einzigartig")
    func displayNamesAreUnique() {
        let names = SubscriptionTier.allCases.map { $0.displayName }
        #expect(Set(names).count == SubscriptionTier.allCases.count)
    }

    @Test("productIDs sind nicht leer")
    func productIDsNotEmpty() {
        for tier in SubscriptionTier.allCases {
            #expect(!tier.productID.isEmpty)
        }
    }
}

// MARK: - AI Free Query Counter Tests

// Tests laufen serialisiert, da alle auf UserDefaults.standard mit gleichem Key schreiben
@Suite("AIAnalysisService – Gratis-Abfragen", .serialized)
struct AIFreeQueryTests {

    init() {
        AIAnalysisService.shared.freeQueriesUsed = 0
    }

    @Test("Limit ist genau 3")
    func limitIsThree() {
        #expect(AIAnalysisService.freeQueryLimit == 3)
    }

    @Test("Initial: Abfragen verfügbar, remaining = 3")
    func initialState() {
        AIAnalysisService.shared.freeQueriesUsed = 0
        #expect(AIAnalysisService.shared.hasFreeQueriesLeft == true)
        #expect(AIAnalysisService.shared.freeQueriesRemaining == 3)
    }

    @Test("consumeFreeQuery erhöht Zähler um 1")
    func consumeIncrementsCounter() {
        AIAnalysisService.shared.freeQueriesUsed = 0
        AIAnalysisService.shared.consumeFreeQuery()
        #expect(AIAnalysisService.shared.freeQueriesUsed == 1)
        #expect(AIAnalysisService.shared.freeQueriesRemaining == 2)
    }

    @Test("Nach 3 Verbrauchungen: hasFreeQueriesLeft == false")
    func afterThreeConsumed() {
        AIAnalysisService.shared.freeQueriesUsed = 0
        AIAnalysisService.shared.consumeFreeQuery()
        AIAnalysisService.shared.consumeFreeQuery()
        AIAnalysisService.shared.consumeFreeQuery()
        #expect(AIAnalysisService.shared.hasFreeQueriesLeft == false)
        #expect(AIAnalysisService.shared.freeQueriesRemaining == 0)
    }

    @Test("Zähler über Limit: remaining bleibt 0, nicht negativ")
    func remainingNotNegative() {
        AIAnalysisService.shared.freeQueriesUsed = 10
        #expect(AIAnalysisService.shared.freeQueriesRemaining == 0)
        #expect(AIAnalysisService.shared.hasFreeQueriesLeft == false)
    }

    @Test("Zähler = limit - 1: noch genau 1 Abfrage verfügbar")
    func oneQueryLeft() {
        AIAnalysisService.shared.freeQueriesUsed = AIAnalysisService.freeQueryLimit - 1
        #expect(AIAnalysisService.shared.hasFreeQueriesLeft == true)
        #expect(AIAnalysisService.shared.freeQueriesRemaining == 1)
    }

    @Test("Zähler = limit: keine Abfragen mehr verfügbar")
    func atLimit() {
        AIAnalysisService.shared.freeQueriesUsed = AIAnalysisService.freeQueryLimit
        #expect(AIAnalysisService.shared.hasFreeQueriesLeft == false)
    }

    @Test("freeQueriesUsed + freeQueriesRemaining = limit (solange unter limit)")
    func usedPlusRemainingEqualsLimit() {
        for used in 0...AIAnalysisService.freeQueryLimit {
            AIAnalysisService.shared.freeQueriesUsed = used
            let remaining = AIAnalysisService.shared.freeQueriesRemaining
            #expect(used + remaining == AIAnalysisService.freeQueryLimit)
        }
    }
}

// MARK: - AppGroup Pro-Status Tests

// Tests laufen serialisiert, da alle dieselbe UserDefaults-Suite nutzen
@Suite("AppGroup – Pro-Status", .serialized)
struct AppGroupProStatusTests {

    private let testDefaults = UserDefaults(suiteName: "nahbar.test.proStatus")!

    init() {
        testDefaults.removeObject(forKey: "isPro")
        testDefaults.synchronize()
    }

    @Test("Pro-Status initial false wenn nicht gesetzt")
    func proStatusInitiallyFalse() {
        testDefaults.removeObject(forKey: "isPro")
        #expect(testDefaults.bool(forKey: "isPro") == false)
    }

    @Test("Pro-Status round-trip: true")
    func proStatusRoundTripTrue() {
        testDefaults.set(true, forKey: "isPro")
        #expect(testDefaults.bool(forKey: "isPro") == true)
    }

    @Test("Pro-Status round-trip: false")
    func proStatusRoundTripFalse() {
        testDefaults.set(true, forKey: "isPro")
        testDefaults.set(false, forKey: "isPro")
        #expect(testDefaults.bool(forKey: "isPro") == false)
    }

    @Test("Pro-Status nach removeObject ist false")
    func proStatusAfterRemoveIsFalse() {
        testDefaults.set(true, forKey: "isPro")
        testDefaults.removeObject(forKey: "isPro")
        #expect(testDefaults.bool(forKey: "isPro") == false)
    }
}

// MARK: - FeatureGate Consistency Tests

/// Stellt sicher, dass die Invariante "Max implies Pro" unter allen Transaktions-Kombinationen gilt.
@Suite("FeatureGate – Konsistenz")
struct FeatureGateConsistencyTests {

    @Test("free: isPro und isMax beide false")
    func freeHasNoAccess() {
        let gate = FeatureGate.from(foundPro: false, foundMax: false)
        #expect(gate.isPro == false)
        #expect(gate.isMax == false)
    }

    @Test("Pro-only: isPro true, isMax false")
    func proOnlySetsPro() {
        let gate = FeatureGate.from(foundPro: true, foundMax: false)
        #expect(gate.isPro == true)
        #expect(gate.isMax == false)
    }

    @Test("Max-only: isMax true setzt isPro automatisch true (Max implies Pro)")
    func maxOnlyImpliesPro() {
        let gate = FeatureGate.from(foundPro: false, foundMax: true)
        #expect(gate.isMax == true)
        #expect(gate.isPro == true, "Max-Abonnenten müssen auch Pro-Features erhalten")
    }

    @Test("Pro + Max: beide true")
    func proAndMaxBothTrue() {
        let gate = FeatureGate.from(foundPro: true, foundMax: true)
        #expect(gate.isPro == true)
        #expect(gate.isMax == true)
    }

    @Test("isConsistent gilt für alle 4 Transaktions-Kombinationen")
    func consistencyHoldsForAllCombinations() {
        let combinations = [(false, false), (true, false), (false, true), (true, true)]
        for (foundPro, foundMax) in combinations {
            let gate = FeatureGate.from(foundPro: foundPro, foundMax: foundMax)
            #expect(gate.isConsistent, "Invariante verletzt für foundPro=\(foundPro), foundMax=\(foundMax)")
        }
    }

    @Test("FeatureGate.free entspricht from(false, false)")
    func freeStaticMatchesFromFactory() {
        #expect(FeatureGate.free == FeatureGate.from(foundPro: false, foundMax: false))
    }
}

// MARK: - FeatureGate Access Tests

/// Verifiziert, dass jedes Feature genau für den richtigen Tier freigeschaltet wird.
@Suite("FeatureGate – Feature-Zugriff")
struct FeatureGateAccessTests {

    // MARK: Free-User

    @Test("Free-User: keine Pro-Features")
    func freeUserHasNoProFeatures() {
        let gate = FeatureGate.free
        #expect(!gate.unlimitedContacts)
        #expect(!gate.premiumThemes)
        #expect(!gate.shareExtension)
    }

    @Test("Free-User: keine Max-Features")
    func freeUserHasNoMaxFeatures() {
        let gate = FeatureGate.free
        #expect(!gate.aiAnalysis)
        #expect(!gate.giftSuggestions)
        #expect(!gate.conversationTopics)
        #expect(!gate.unlimitedAIQueries)
    }

    // MARK: Pro-only

    @Test("Pro-User: alle Pro-Features aktiv")
    func proUserHasAllProFeatures() {
        let gate = FeatureGate.from(foundPro: true, foundMax: false)
        #expect(gate.unlimitedContacts)
        #expect(gate.premiumThemes)
        #expect(gate.shareExtension)
    }

    @Test("Pro-User: keine Max-Features")
    func proUserHasNoMaxFeatures() {
        let gate = FeatureGate.from(foundPro: true, foundMax: false)
        #expect(!gate.aiAnalysis)
        #expect(!gate.giftSuggestions)
        #expect(!gate.conversationTopics)
        #expect(!gate.unlimitedAIQueries)
    }

    // MARK: Max-User

    @Test("Max-User: alle Pro-Features aktiv (Max implies Pro)")
    func maxUserHasAllProFeatures() {
        let gate = FeatureGate.from(foundPro: false, foundMax: true)
        #expect(gate.unlimitedContacts, "Max-Abonnenten müssen unbegrenzte Kontakte haben")
        #expect(gate.premiumThemes, "Max-Abonnenten müssen Premium-Themes haben")
        #expect(gate.shareExtension, "Max-Abonnenten müssen die Share-Extension nutzen können")
    }

    @Test("Max-User: alle Max-Features aktiv")
    func maxUserHasAllMaxFeatures() {
        let gate = FeatureGate.from(foundPro: false, foundMax: true)
        #expect(gate.aiAnalysis, "Max-Abonnenten müssen KI-Analyse nutzen können")
        #expect(gate.giftSuggestions, "Max-Abonnenten müssen Geschenkideen nutzen können")
        #expect(gate.conversationTopics, "Max-Abonnenten müssen Gesprächsthemen nutzen können")
        #expect(gate.unlimitedAIQueries, "Max-Abonnenten müssen unbegrenzte KI-Abfragen haben")
    }
}

// MARK: - FeatureGate canUseAI Tests

/// Verifiziert die canUseAI-Logik, die in AddMomentView, TodayView und LogbuchView identisch ist:
///   store.isMax || AIAnalysisService.shared.hasFreeQueriesLeft
@Suite("FeatureGate – canUseAI")
struct FeatureGateCanUseAITests {

    @Test("Max + keine Gratis-Abfragen: KI verfügbar (unbegrenzt)")
    func maxUserAlwaysCanUseAI() {
        #expect(FeatureGate.canUseAI(isMax: true, hasFreeQueriesLeft: false))
    }

    @Test("Max + Gratis-Abfragen vorhanden: KI verfügbar")
    func maxUserWithQueriesCanUseAI() {
        #expect(FeatureGate.canUseAI(isMax: true, hasFreeQueriesLeft: true))
    }

    @Test("Nicht-Max + Gratis-Abfragen vorhanden: KI verfügbar")
    func nonMaxWithQueriesCanUseAI() {
        #expect(FeatureGate.canUseAI(isMax: false, hasFreeQueriesLeft: true))
    }

    @Test("Nicht-Max + keine Gratis-Abfragen: KI gesperrt")
    func nonMaxWithoutQueriesCannotUseAI() {
        #expect(!FeatureGate.canUseAI(isMax: false, hasFreeQueriesLeft: false))
    }
}

// MARK: - FeatureGate shouldConsumeFreeQuery Tests

/// Verifiziert die Verbrauchs-Logik:
///   if !store.isMax { AIAnalysisService.shared.consumeFreeQuery() }
@Suite("FeatureGate – shouldConsumeFreeQuery")
struct FeatureGateFreeQueryConsumptionTests {

    @Test("Max-Abonnent: Gratis-Abfrage wird NICHT verbraucht")
    func maxUserDoesNotConsumeFreeQuery() {
        #expect(!FeatureGate.shouldConsumeFreeQuery(isMax: true))
    }

    @Test("Nicht-Max-Abonnent: Gratis-Abfrage wird verbraucht")
    func nonMaxUserConsumesQuery() {
        #expect(FeatureGate.shouldConsumeFreeQuery(isMax: false))
    }
}

// MARK: - Paywall-Targeting Tests

/// Dokumentiert die Logik aus SettingsView:
///   PaywallView(targeting: store.isPro ? .max : .pro)
/// Stellt sicher, dass der Einstiegs-Tab beim Öffnen des Paywalls korrekt ist.
@Suite("Paywall – Ziel-Tier basierend auf isPro")
struct PaywallTargetingTests {

    /// Repliziert die einzeilige Entscheidungslogik aus SettingsView.
    private func target(isPro: Bool) -> SubscriptionTier {
        isPro ? .max : .pro
    }

    @Test("Kein Abo → Paywall öffnet Pro-Tab")
    func noSubscriptionTargetsPro() {
        #expect(target(isPro: false) == .pro)
    }

    @Test("Pro-only → Paywall öffnet Max-Tab (Upgrade-Pfad)")
    func proOnlyTargetsMax() {
        #expect(target(isPro: true) == .max)
    }

    @Test("Ziel-Tiers sind unterschiedlich")
    func targetsAreDistinct() {
        #expect(target(isPro: false) != target(isPro: true))
    }
}

// MARK: - InterestTagHelper Tests

@Suite("InterestTagHelper – parse")
struct InterestTagHelperParseTests {

    @Test("Leerer String → leeres Array")
    func emptyStringReturnsEmpty() {
        #expect(InterestTagHelper.parse("") == [])
    }

    @Test("Einzelner Tag ohne Komma")
    func singleTag() {
        #expect(InterestTagHelper.parse("Fußball") == ["Fußball"])
    }

    @Test("Mehrere Tags kommasepariert")
    func multipleTags() {
        let result = InterestTagHelper.parse("Fußball, Musik, Lesen")
        #expect(result == ["Fußball", "Musik", "Lesen"])
    }

    @Test("Whitespace wird getrimmt")
    func whitespaceTrimmed() {
        let result = InterestTagHelper.parse("  Kino  ,  Sport  ")
        #expect(result == ["Kino", "Sport"])
    }

    @Test("Leere Segmente werden gefiltert")
    func emptySegmentsFiltered() {
        let result = InterestTagHelper.parse("Kino,,Musik,")
        #expect(result == ["Kino", "Musik"])
    }
}

@Suite("InterestTagHelper – join")
struct InterestTagHelperJoinTests {

    @Test("Leeres Array → leerer String")
    func emptyArrayReturnsEmpty() {
        #expect(InterestTagHelper.join([]) == "")
    }

    @Test("Einzelner Tag bleibt unverändert")
    func singleTagUnchanged() {
        #expect(InterestTagHelper.join(["Fußball"]) == "Fußball")
    }

    @Test("Tags werden alphabetisch sortiert")
    func tagsSortedAlphabetically() {
        let result = InterestTagHelper.join(["Musik", "Fußball", "Lesen"])
        #expect(result == "Fußball, Lesen, Musik")
    }

    @Test("Sortierung ist case-insensitive")
    func sortingCaseInsensitive() {
        let result = InterestTagHelper.join(["bier", "Apfel", "Chips"])
        #expect(result == "Apfel, bier, Chips")
    }
}

@Suite("InterestTagHelper – addTag")
struct InterestTagHelperAddTagTests {

    @Test("Tag zu leerem String hinzufügen")
    func addToEmpty() {
        #expect(InterestTagHelper.addTag("Kino", to: "") == "Kino")
    }

    @Test("Tag zu bestehendem String – alphabetisch einsortiert")
    func addSortsAlphabetically() {
        let result = InterestTagHelper.addTag("Musik", to: "Fußball")
        #expect(result == "Fußball, Musik")
    }

    @Test("Duplikat wird ignoriert")
    func duplicateIgnored() {
        let result = InterestTagHelper.addTag("Kino", to: "Kino, Sport")
        #expect(result == "Kino, Sport")
    }

    @Test("Duplikat ignoriert (Groß-/Kleinschreibung)")
    func duplicateCaseInsensitive() {
        let result = InterestTagHelper.addTag("kino", to: "Kino")
        #expect(result == "Kino")
    }

    @Test("Leerer String wird ignoriert")
    func emptyTagIgnored() {
        let result = InterestTagHelper.addTag("", to: "Kino")
        #expect(result == "Kino")
    }
}

@Suite("InterestTagHelper – removeTag")
struct InterestTagHelperRemoveTagTests {

    @Test("Tag aus String entfernen")
    func removeExistingTag() {
        let result = InterestTagHelper.removeTag("Musik", from: "Fußball, Musik, Sport")
        #expect(result == "Fußball, Sport")
    }

    @Test("Nicht vorhandener Tag → unveränderter String")
    func removeNonExistentTag() {
        let result = InterestTagHelper.removeTag("Kino", from: "Fußball, Sport")
        #expect(result == "Fußball, Sport")
    }

    @Test("Letzten Tag entfernen → leerer String")
    func removeLastTagReturnsEmpty() {
        let result = InterestTagHelper.removeTag("Kino", from: "Kino")
        #expect(result == "")
    }
}

@Suite("InterestTagHelper – allSuggestions")
struct InterestTagHelperSuggestionsTests {

    @Test("Keine Personen + leere Vorlieben → leere Vorschläge")
    func emptyInputsReturnEmpty() {
        let result = InterestTagHelper.allSuggestions(from: [], likes: "", dislikes: "")
        #expect(result.isEmpty)
    }

    @Test("Vorschläge aus likes und dislikes kombiniert und sortiert")
    func combinesLikesAndDislikes() {
        let result = InterestTagHelper.allSuggestions(from: [], likes: "Musik, Kino", dislikes: "Sport")
        #expect(result == ["Kino", "Musik", "Sport"])
    }

    @Test("Duplikate werden dedupliziert")
    func deduplicates() {
        let result = InterestTagHelper.allSuggestions(from: [], likes: "Kino, Musik", dislikes: "Kino")
        #expect(!result.contains { _ in result.filter { $0 == "Kino" }.count > 1 })
        #expect(result.filter { $0 == "Kino" }.count == 1)
    }
}
