//
//  TripPreferencesTests.swift
//  SportsTimeTests
//
//  TDD specification tests for TripPreferences and related enums.
//

import Testing
import Foundation
import CoreLocation
@testable import SportsTime

@Suite("TripPreferences")
struct TripPreferencesTests {

    // MARK: - Specification Tests: totalDriverHoursPerDay

    @Test("totalDriverHoursPerDay: uses default 8 hours when maxDrivingHoursPerDriver is nil")
    func totalDriverHoursPerDay_default() {
        let prefs = TripPreferences(
            numberOfDrivers: 1,
            maxDrivingHoursPerDriver: nil
        )

        #expect(prefs.totalDriverHoursPerDay == 8.0)
    }

    @Test("totalDriverHoursPerDay: multiplies by number of drivers")
    func totalDriverHoursPerDay_multipleDrivers() {
        let prefs = TripPreferences(
            numberOfDrivers: 2,
            maxDrivingHoursPerDriver: nil
        )

        #expect(prefs.totalDriverHoursPerDay == 16.0) // 8 * 2
    }

    @Test("totalDriverHoursPerDay: uses custom maxDrivingHoursPerDriver")
    func totalDriverHoursPerDay_custom() {
        let prefs = TripPreferences(
            numberOfDrivers: 2,
            maxDrivingHoursPerDriver: 6.0
        )

        #expect(prefs.totalDriverHoursPerDay == 12.0) // 6 * 2
    }

    // MARK: - Specification Tests: effectiveTripDuration

    @Test("effectiveTripDuration: uses tripDuration when set")
    func effectiveTripDuration_explicit() {
        let prefs = TripPreferences(
            startDate: Date(),
            endDate: Date().addingTimeInterval(86400 * 14),
            tripDuration: 5
        )

        #expect(prefs.effectiveTripDuration == 5)
    }

    @Test("effectiveTripDuration: calculates from date range when tripDuration is nil")
    func effectiveTripDuration_calculated() {
        let calendar = Calendar.current
        let startDate = calendar.date(from: DateComponents(year: 2026, month: 6, day: 15))!
        let endDate = calendar.date(from: DateComponents(year: 2026, month: 6, day: 22))!

        let prefs = TripPreferences(
            startDate: startDate,
            endDate: endDate,
            tripDuration: nil
        )

        #expect(prefs.effectiveTripDuration == 7) // 7 days between 15th and 22nd
    }

    @Test("effectiveTripDuration: minimum is 1")
    func effectiveTripDuration_minimum() {
        let date = Date()
        let prefs = TripPreferences(
            startDate: date,
            endDate: date,
            tripDuration: nil
        )

        #expect(prefs.effectiveTripDuration >= 1)
    }

    // MARK: - Invariant Tests

    @Test("Invariant: totalDriverHoursPerDay > 0")
    func invariant_totalDriverHoursPositive() {
        // With 1 driver and default
        let prefs1 = TripPreferences(numberOfDrivers: 1)
        #expect(prefs1.totalDriverHoursPerDay > 0)

        // With multiple drivers
        let prefs2 = TripPreferences(numberOfDrivers: 3, maxDrivingHoursPerDriver: 4)
        #expect(prefs2.totalDriverHoursPerDay > 0)
    }

    @Test("Invariant: effectiveTripDuration >= 1")
    func invariant_effectiveTripDurationMinimum() {
        let testCases: [Int?] = [nil, 1, 5, 10]

        for duration in testCases {
            let prefs = TripPreferences(tripDuration: duration)
            #expect(prefs.effectiveTripDuration >= 1)
        }
    }
}

// MARK: - LeisureLevel Tests

@Suite("LeisureLevel")
struct LeisureLevelTests {

    @Test("restDaysPerWeek: packed = 0.5")
    func restDaysPerWeek_packed() {
        #expect(LeisureLevel.packed.restDaysPerWeek == 0.5)
    }

    @Test("restDaysPerWeek: moderate = 1.5")
    func restDaysPerWeek_moderate() {
        #expect(LeisureLevel.moderate.restDaysPerWeek == 1.5)
    }

    @Test("restDaysPerWeek: relaxed = 2.5")
    func restDaysPerWeek_relaxed() {
        #expect(LeisureLevel.relaxed.restDaysPerWeek == 2.5)
    }

    @Test("maxGamesPerWeek: packed = 7")
    func maxGamesPerWeek_packed() {
        #expect(LeisureLevel.packed.maxGamesPerWeek == 7)
    }

    @Test("maxGamesPerWeek: moderate = 5")
    func maxGamesPerWeek_moderate() {
        #expect(LeisureLevel.moderate.maxGamesPerWeek == 5)
    }

    @Test("maxGamesPerWeek: relaxed = 3")
    func maxGamesPerWeek_relaxed() {
        #expect(LeisureLevel.relaxed.maxGamesPerWeek == 3)
    }

    @Test("Invariant: restDaysPerWeek increases from packed to relaxed")
    func invariant_restDaysOrdering() {
        #expect(LeisureLevel.packed.restDaysPerWeek < LeisureLevel.moderate.restDaysPerWeek)
        #expect(LeisureLevel.moderate.restDaysPerWeek < LeisureLevel.relaxed.restDaysPerWeek)
    }

    @Test("Invariant: maxGamesPerWeek decreases from packed to relaxed")
    func invariant_maxGamesOrdering() {
        #expect(LeisureLevel.packed.maxGamesPerWeek > LeisureLevel.moderate.maxGamesPerWeek)
        #expect(LeisureLevel.moderate.maxGamesPerWeek > LeisureLevel.relaxed.maxGamesPerWeek)
    }
}

// MARK: - RoutePreference Tests

@Suite("RoutePreference")
struct RoutePreferenceTests {

    @Test("scenicWeight: direct = 0.0")
    func scenicWeight_direct() {
        #expect(RoutePreference.direct.scenicWeight == 0.0)
    }

    @Test("scenicWeight: scenic = 1.0")
    func scenicWeight_scenic() {
        #expect(RoutePreference.scenic.scenicWeight == 1.0)
    }

    @Test("scenicWeight: balanced = 0.5")
    func scenicWeight_balanced() {
        #expect(RoutePreference.balanced.scenicWeight == 0.5)
    }

    @Test("Invariant: scenicWeight is in range [0, 1]")
    func invariant_scenicWeightRange() {
        for pref in RoutePreference.allCases {
            #expect(pref.scenicWeight >= 0.0)
            #expect(pref.scenicWeight <= 1.0)
        }
    }
}

// MARK: - LocationInput Tests

@Suite("LocationInput")
struct LocationInputTests {

    @Test("isResolved: true when coordinate is set")
    func isResolved_true() {
        let input = LocationInput(
            name: "New York",
            coordinate: .init(latitude: 40.7, longitude: -74.0)
        )

        #expect(input.isResolved == true)
    }

    @Test("isResolved: false when coordinate is nil")
    func isResolved_false() {
        let input = LocationInput(name: "New York", coordinate: nil)

        #expect(input.isResolved == false)
    }

    @Test("Invariant: isResolved equals (coordinate != nil)")
    func invariant_isResolvedConsistent() {
        let withCoord = LocationInput(name: "A", coordinate: .init(latitude: 0, longitude: 0))
        let withoutCoord = LocationInput(name: "B", coordinate: nil)

        #expect(withCoord.isResolved == (withCoord.coordinate != nil))
        #expect(withoutCoord.isResolved == (withoutCoord.coordinate != nil))
    }
}

// MARK: - PlanningMode Tests

@Suite("PlanningMode")
struct PlanningModeTests {

    @Test("Property: all cases have displayName")
    func property_allHaveDisplayName() {
        for mode in PlanningMode.allCases {
            #expect(!mode.displayName.isEmpty)
        }
    }

    @Test("Property: all cases have description")
    func property_allHaveDescription() {
        for mode in PlanningMode.allCases {
            #expect(!mode.description.isEmpty)
        }
    }

    @Test("Property: all cases have iconName")
    func property_allHaveIconName() {
        for mode in PlanningMode.allCases {
            #expect(!mode.iconName.isEmpty)
        }
    }

    @Test("Property: id equals rawValue")
    func property_idEqualsRawValue() {
        for mode in PlanningMode.allCases {
            #expect(mode.id == mode.rawValue)
        }
    }
}

// MARK: - TravelMode Tests

@Suite("TravelMode")
struct TravelModeTests {

    @Test("Property: all cases have displayName")
    func property_allHaveDisplayName() {
        for mode in TravelMode.allCases {
            #expect(!mode.displayName.isEmpty)
        }
    }

    @Test("Property: all cases have iconName")
    func property_allHaveIconName() {
        for mode in TravelMode.allCases {
            #expect(!mode.iconName.isEmpty)
        }
    }
}

// MARK: - LodgingType Tests

@Suite("LodgingType")
struct LodgingTypeTests {

    @Test("Property: all cases have displayName")
    func property_allHaveDisplayName() {
        for type in LodgingType.allCases {
            #expect(!type.displayName.isEmpty)
        }
    }

    @Test("Property: all cases have iconName")
    func property_allHaveIconName() {
        for type in LodgingType.allCases {
            #expect(!type.iconName.isEmpty)
        }
    }
}
