import Foundation
import Testing

@testable import Trio

@Suite("Trio Alerts: GlucoseAlertsStore seed + backfill", .serialized) struct GlucoseAlertsStoreTests {
    private static func makeStore(seed: [GlucoseAlert]? = nil) -> GlucoseAlertsStore {
        let suiteName = "GlucoseAlertsStoreTests.\(UUID().uuidString)"
        let defaults = UserDefaults(suiteName: suiteName)!
        defaults.removePersistentDomain(forName: suiteName)
        let alertsKey = "alerts.\(suiteName)"
        let configKey = "config.\(suiteName)"
        if let seed {
            let data = try? JSONEncoder().encode(seed)
            defaults.set(data, forKey: alertsKey)
        }
        return GlucoseAlertsStore(defaults: defaults, alertsKey: alertsKey, configKey: configKey)
    }

    @Test("Fresh store seeds one alert per type") func freshSeedAllTypes() {
        let store = Self.makeStore()
        let types = Set(store.alerts.map(\.type))
        for type in GlucoseAlertType.allCases {
            #expect(types.contains(type), "Missing seed for \(type)")
        }
        #expect(store.alerts.count == GlucoseAlertType.allCases.count)
    }

    @Test("Legacy load (no carbsRequired) backfills the new type") func legacyLoadBackfillsCarbsRequired() {
        let legacy: [GlucoseAlert] = [
            GlucoseAlert(type: .urgentLow),
            GlucoseAlert(type: .low),
            GlucoseAlert(type: .forecastedLow),
            GlucoseAlert(type: .high)
        ]
        let store = Self.makeStore(seed: legacy)
        let types = Set(store.alerts.map(\.type))
        #expect(types.contains(.carbsRequired))
        #expect(store.alerts.count == GlucoseAlertType.allCases.count)
    }

    @Test("Backfill preserves user customizations on existing entries") func backfillPreservesCustomizations() {
        var custom = GlucoseAlert(type: .low)
        custom.thresholdMgDL = 65
        custom.soundFilename = "custom_sound.caf"
        custom.isEnabled = false
        let store = Self.makeStore(seed: [custom])
        let restored = store.alerts.first { $0.type == .low }!
        #expect(restored.thresholdMgDL == 65)
        #expect(restored.soundFilename == "custom_sound.caf")
        #expect(restored.isEnabled == false)
        // And new types still got appended.
        #expect(store.alerts.contains { $0.type == .carbsRequired })
    }

    @Test("Full load doesn't double up on existing types") func fullLoadNoDuplicates() {
        let full = GlucoseAlertType.allCases.map { GlucoseAlert(type: $0) }
        let store = Self.makeStore(seed: full)
        let countsByType = Dictionary(grouping: store.alerts, by: \.type).mapValues(\.count)
        for type in GlucoseAlertType.allCases {
            #expect(countsByType[type] == 1, "Duplicated \(type) on load")
        }
    }

    // MARK: - availableActiveOptions for new alarms

    private static func storeWithOnly(_ alerts: [GlucoseAlert]) -> GlucoseAlertsStore {
        let store = Self.makeStore()
        store.alerts = alerts
        return store
    }

    @Test("Single .always covers everything: no options available") func alwaysCoversAll() {
        let existing = GlucoseAlert(type: .low) // default .always
        let store = Self.storeWithOnly([existing])
        #expect(store.availableActiveOptions(forNewAlarmOfType: .low).isEmpty)
    }

    @Test("Only .day taken → only .night available") func dayTakenOffersNight() {
        var existing = GlucoseAlert(type: .low)
        existing.activeOption = .day
        let store = Self.storeWithOnly([existing])
        #expect(store.availableActiveOptions(forNewAlarmOfType: .low) == [.night])
    }

    @Test("Only .night taken → only .day available") func nightTakenOffersDay() {
        var existing = GlucoseAlert(type: .high)
        existing.activeOption = .night
        let store = Self.storeWithOnly([existing])
        #expect(store.availableActiveOptions(forNewAlarmOfType: .high) == [.day])
    }

    @Test(".day + .night both taken → none available") func dayAndNightTakenLocked() {
        var day = GlucoseAlert(type: .forecastedLow)
        day.activeOption = .day
        var night = GlucoseAlert(type: .forecastedLow)
        night.activeOption = .night
        let store = Self.storeWithOnly([day, night])
        #expect(store.availableActiveOptions(forNewAlarmOfType: .forecastedLow).isEmpty)
    }

    @Test("No alarm of this type → all three options available") func emptyOffersAll() {
        let store = Self.storeWithOnly([])
        #expect(store.availableActiveOptions(forNewAlarmOfType: .carbsRequired) == [.always, .day, .night])
    }

    @Test("Gating is per-type — other types don't block") func gatingIsPerType() {
        let lowAlways = GlucoseAlert(type: .low) // .always blocks Low
        let store = Self.storeWithOnly([lowAlways])
        #expect(store.availableActiveOptions(forNewAlarmOfType: .low).isEmpty)
        #expect(store.availableActiveOptions(forNewAlarmOfType: .high) == [.always, .day, .night])
    }

    @Test("Editing an existing .day alarm: .always becomes pickable again") func editingExcludesSelf() {
        var existing = GlucoseAlert(type: .urgentLow)
        existing.activeOption = .day
        let store = Self.storeWithOnly([existing])
        let available = store.availableActiveOptions(forType: .urgentLow, excludingAlertID: existing.id)
        #expect(available == [.always, .day, .night])
    }

    @Test("Editing one of two split alarms: only own window available") func editingOneOfTwoSplit() {
        var day = GlucoseAlert(type: .low)
        day.activeOption = .day
        var night = GlucoseAlert(type: .low)
        night.activeOption = .night
        let store = Self.storeWithOnly([day, night])
        // Editing the .day alarm: .night is taken by the other, .always would conflict with .night.
        let editingDay = store.availableActiveOptions(forType: .low, excludingAlertID: day.id)
        #expect(editingDay == [.day])
    }

    /// Pins the user-reported regression: fresh install seeds every type with
    /// `.always`, so the Add picker must show every type as unavailable.
    @Test("Fresh-seeded store has no available windows for any type") func freshSeedHasNoneAvailable() {
        let store = Self.makeStore()
        for type in GlucoseAlertType.allCases {
            #expect(
                store.availableActiveOptions(forNewAlarmOfType: type).isEmpty,
                "Expected \(type) to be fully covered by the default .always seed"
            )
        }
    }
}
