//
//  TravelSegmentTests.swift
//  SportsTimeTests
//
//  TDD specification tests for TravelSegment model.
//

import Testing
@testable import SportsTime

@Suite("TravelSegment")
struct TravelSegmentTests {

    // MARK: - Test Data

    private func makeSegment(
        distanceMeters: Double,
        durationSeconds: Double
    ) -> TravelSegment {
        TravelSegment(
            fromLocation: LocationInput(name: "A"),
            toLocation: LocationInput(name: "B"),
            travelMode: .drive,
            distanceMeters: distanceMeters,
            durationSeconds: durationSeconds
        )
    }

    // MARK: - Specification Tests: Unit Conversions

    @Test("distanceMiles: converts meters to miles correctly")
    func distanceMiles_conversion() {
        // 1 mile = 1609.344 meters
        // So 1609.344 meters should be ~1 mile
        let segment = makeSegment(distanceMeters: 1609.344, durationSeconds: 3600)

        #expect(abs(segment.distanceMiles - 1.0) < 0.001, "1609.344 meters should be ~1 mile")
    }

    @Test("distanceMiles: 100 miles")
    func distanceMiles_100miles() {
        let metersIn100Miles = 160934.4
        let segment = makeSegment(distanceMeters: metersIn100Miles, durationSeconds: 3600)

        #expect(abs(segment.distanceMiles - 100.0) < 0.01)
    }

    @Test("distanceMiles: zero meters is zero miles")
    func distanceMiles_zero() {
        let segment = makeSegment(distanceMeters: 0, durationSeconds: 3600)

        #expect(segment.distanceMiles == 0)
    }

    @Test("durationHours: converts seconds to hours correctly")
    func durationHours_conversion() {
        // 3600 seconds = 1 hour
        let segment = makeSegment(distanceMeters: 1000, durationSeconds: 3600)

        #expect(segment.durationHours == 1.0)
    }

    @Test("durationHours: 2.5 hours")
    func durationHours_twoAndHalf() {
        // 2.5 hours = 9000 seconds
        let segment = makeSegment(distanceMeters: 1000, durationSeconds: 9000)

        #expect(segment.durationHours == 2.5)
    }

    @Test("durationHours: zero seconds is zero hours")
    func durationHours_zero() {
        let segment = makeSegment(distanceMeters: 1000, durationSeconds: 0)

        #expect(segment.durationHours == 0)
    }

    // MARK: - Specification Tests: Aliases

    @Test("estimatedDrivingHours is alias for durationHours")
    func estimatedDrivingHours_alias() {
        let segment = makeSegment(distanceMeters: 1000, durationSeconds: 7200)

        #expect(segment.estimatedDrivingHours == segment.durationHours)
        #expect(segment.estimatedDrivingHours == 2.0)
    }

    @Test("estimatedDistanceMiles is alias for distanceMiles")
    func estimatedDistanceMiles_alias() {
        let segment = makeSegment(distanceMeters: 160934.4, durationSeconds: 3600)

        #expect(segment.estimatedDistanceMiles == segment.distanceMiles)
        #expect(abs(segment.estimatedDistanceMiles - 100.0) < 0.01)
    }

    // MARK: - Specification Tests: formattedDuration

    @Test("formattedDuration: shows hours and minutes when both present")
    func formattedDuration_hoursAndMinutes() {
        // 2h 30m = 9000 seconds
        let segment = makeSegment(distanceMeters: 1000, durationSeconds: 9000)

        #expect(segment.formattedDuration == "2h 30m")
    }

    @Test("formattedDuration: shows only hours when minutes are zero")
    func formattedDuration_onlyHours() {
        // 3h = 10800 seconds
        let segment = makeSegment(distanceMeters: 1000, durationSeconds: 10800)

        #expect(segment.formattedDuration == "3h")
    }

    @Test("formattedDuration: shows only minutes when hours are zero")
    func formattedDuration_onlyMinutes() {
        // 45m = 2700 seconds
        let segment = makeSegment(distanceMeters: 1000, durationSeconds: 2700)

        #expect(segment.formattedDuration == "45m")
    }

    @Test("formattedDuration: shows 0m for zero duration")
    func formattedDuration_zero() {
        let segment = makeSegment(distanceMeters: 1000, durationSeconds: 0)

        #expect(segment.formattedDuration == "0m")
    }

    // MARK: - Specification Tests: formattedDistance

    @Test("formattedDistance: shows miles rounded to integer")
    func formattedDistance_rounded() {
        // 100.7 miles
        let segment = makeSegment(distanceMeters: 162115.4, durationSeconds: 3600)

        #expect(segment.formattedDistance == "101 mi")
    }

    // MARK: - Invariant Tests

    @Test("Invariant: distanceMiles positive when meters positive")
    func invariant_distanceMilesPositive() {
        let testMeters: [Double] = [1, 100, 1000, 100000, 1000000]

        for meters in testMeters {
            let segment = makeSegment(distanceMeters: meters, durationSeconds: 3600)
            #expect(segment.distanceMiles > 0, "distanceMiles should be positive for \(meters) meters")
        }
    }

    @Test("Invariant: durationHours positive when seconds positive")
    func invariant_durationHoursPositive() {
        let testSeconds: [Double] = [1, 60, 3600, 7200, 36000]

        for seconds in testSeconds {
            let segment = makeSegment(distanceMeters: 1000, durationSeconds: seconds)
            #expect(segment.durationHours > 0, "durationHours should be positive for \(seconds) seconds")
        }
    }

    @Test("Invariant: conversion factors are consistent")
    func invariant_conversionFactorsConsistent() {
        // 0.000621371 miles per meter
        let meters: Double = 1000
        let segment = makeSegment(distanceMeters: meters, durationSeconds: 3600)

        let expectedMiles = meters * 0.000621371
        #expect(segment.distanceMiles == expectedMiles)
    }

    // MARK: - Property Tests

    @Test("Property: scenicScore defaults to 0.5")
    func property_scenicScoreDefault() {
        let segment = makeSegment(distanceMeters: 1000, durationSeconds: 3600)

        #expect(segment.scenicScore == 0.5)
    }

    @Test("Property: evChargingStops defaults to empty")
    func property_evChargingStopsDefault() {
        let segment = makeSegment(distanceMeters: 1000, durationSeconds: 3600)

        #expect(segment.evChargingStops.isEmpty)
    }

    @Test("Property: routePolyline defaults to nil")
    func property_routePolylineDefault() {
        let segment = makeSegment(distanceMeters: 1000, durationSeconds: 3600)

        #expect(segment.routePolyline == nil)
    }
}
