//
//  CableTests.swift
//  CableTests
//
//  Created by Stefan Lange-Hegermann on 11.09.25.
//

import Testing
@testable import Cable

struct CableTests {

    // MARK: - Core Formula Verification

    /// Formula: A = (2 × I × L × ρ) / V_drop
    /// With ρ = 0.017 Ω·mm²/m, max voltage drop = 5%
    @Test func metricWireSizingUsesNearestStandardSize() async throws {
        // 10m, 5A, 12V → minCS = (2×5×10×0.017)/(12×0.05) = 2.833mm² → rounds up to 4.0mm²
        let crossSection = ElectricalCalculations.recommendedCrossSection(
            length: 10,
            current: 5,
            voltage: 12,
            unitSystem: .metric
        )
        #expect(crossSection == 4.0)

        // V_drop = (2×5×10×0.017)/4.0 = 0.425V
        let voltageDrop = ElectricalCalculations.voltageDrop(
            length: 10,
            current: 5,
            voltage: 12,
            unitSystem: .metric
        )
        #expect(abs(voltageDrop - 0.425) < 0.001)

        // 0.425/12 × 100 = 3.5417%
        let dropPercentage = ElectricalCalculations.voltageDropPercentage(
            length: 10,
            current: 5,
            voltage: 12,
            unitSystem: .metric
        )
        #expect(abs(dropPercentage - 3.5417) < 0.001)

        // P_loss = I × V_drop = 5 × 0.425 = 2.125W
        let powerLoss = ElectricalCalculations.powerLoss(
            length: 10,
            current: 5,
            voltage: 12,
            unitSystem: .metric
        )
        #expect(abs(powerLoss - 2.125) < 0.001)
    }

    /// Imperial test: length is always in meters (25 ft = 7.62 m), unitSystem controls AWG output
    @Test func imperialWireSizingMatchesExpectedGauge() async throws {
        let lengthInMeters = 25.0 * 0.3048 // 25 ft = 7.62 m
        let awg = ElectricalCalculations.recommendedCrossSection(
            length: lengthInMeters,
            current: 15,
            voltage: 120,
            unitSystem: .imperial
        )
        // minCS = (2×15×7.62×0.017)/(120×0.05) = 0.648mm² → AWG 18 (0.823mm²)
        #expect(awg == 18.0)

        let voltageDrop = ElectricalCalculations.voltageDrop(
            length: lengthInMeters,
            current: 15,
            voltage: 120,
            unitSystem: .imperial
        )
        // (2×15×7.62×0.017)/0.823 = 4.722V
        #expect(abs(voltageDrop - 4.722) < 0.01)

        let dropPercentage = ElectricalCalculations.voltageDropPercentage(
            length: lengthInMeters,
            current: 15,
            voltage: 120,
            unitSystem: .imperial
        )
        // 4.722/120 × 100 = 3.935%
        #expect(abs(dropPercentage - 3.935) < 0.01)

        let powerLoss = ElectricalCalculations.powerLoss(
            length: lengthInMeters,
            current: 15,
            voltage: 120,
            unitSystem: .imperial
        )
        // 15 × 4.722 = 70.83W
        #expect(abs(powerLoss - 70.83) < 0.05)
    }

    // MARK: - Fuse Sizing

    @Test func recommendedFuseRoundsUpToNearestStandardSize() async throws {
        // 7.2A × 1.25 = 9.0 → next fuse ≥ 9 = 10A
        #expect(ElectricalCalculations.recommendedFuse(forCurrent: 7.2) == 10.0)
        // 59A × 1.25 = 73.75 → ceil = 74 → next fuse ≥ 74 = 80A
        #expect(ElectricalCalculations.recommendedFuse(forCurrent: 59.0) == 80.0)
    }

    @Test func fuseAt125PercentOfCurrent() async throws {
        // 10A × 1.25 = 12.5 → ceil = 13 → next fuse = 15A
        #expect(ElectricalCalculations.recommendedFuse(forCurrent: 10.0) == 15.0)
        // 20A × 1.25 = 25 → next fuse = 25A
        #expect(ElectricalCalculations.recommendedFuse(forCurrent: 20.0) == 25.0)
        // 1A × 1.25 = 1.25 → ceil = 2 → next fuse = 2A
        #expect(ElectricalCalculations.recommendedFuse(forCurrent: 1.0) == 2.0)
        // 4A × 1.25 = 5 → next fuse = 5A
        #expect(ElectricalCalculations.recommendedFuse(forCurrent: 4.0) == 5.0)
        // 100A × 1.25 = 125 → next fuse = 125A
        #expect(ElectricalCalculations.recommendedFuse(forCurrent: 100.0) == 125.0)
    }

    @Test func fuseSelectsHalfAmpereSizes() async throws {
        // 5A × 1.25 = 6.25 → ceil = 7 → next fuse ≥ 7 = 7.5A
        #expect(ElectricalCalculations.recommendedFuse(forCurrent: 5.0) == 7.5)
        // 6A × 1.25 = 7.5 → ceil = 8 → next fuse ≥ 8 = 10A
        #expect(ElectricalCalculations.recommendedFuse(forCurrent: 6.0) == 10.0)
    }

    @Test func fuseForZeroCurrent() async throws {
        #expect(ElectricalCalculations.recommendedFuse(forCurrent: 0) == 1.0)
    }

    // MARK: - Metric Cross-Section Boundaries

    @Test func metricCrossSectionSelectsSmallestAdequateSize() async throws {
        // Very small load: 1m, 0.5A, 12V
        // minCS = (2×0.5×1×0.017)/(12×0.05) = 0.0283mm² → rounds up to 0.75mm² (smallest standard)
        let cs = ElectricalCalculations.recommendedCrossSection(
            length: 1, current: 0.5, voltage: 12, unitSystem: .metric
        )
        #expect(cs == 0.75)
    }

    @Test func metricCrossSectionForHighCurrent() async throws {
        // 15m, 80A, 12V
        // minCS = (2×80×15×0.017)/(12×0.05) = 40.8/0.6 = 68mm² → rounds up to 70mm²
        let cs = ElectricalCalculations.recommendedCrossSection(
            length: 15, current: 80, voltage: 12, unitSystem: .metric
        )
        #expect(cs == 70.0)
    }

    @Test func metricCrossSectionFor24VSystem() async throws {
        // 10m, 20A, 24V
        // minCS = (2×20×10×0.017)/(24×0.05) = 6.8/1.2 = 5.667mm² → rounds up to 6.0mm²
        let cs = ElectricalCalculations.recommendedCrossSection(
            length: 10, current: 20, voltage: 24, unitSystem: .metric
        )
        #expect(cs == 6.0)
    }

    @Test func metricCrossSectionFor48VSystem() async throws {
        // 10m, 20A, 48V
        // minCS = (2×20×10×0.017)/(48×0.05) = 6.8/2.4 = 2.833mm² → rounds up to 4.0mm²
        let cs = ElectricalCalculations.recommendedCrossSection(
            length: 10, current: 20, voltage: 48, unitSystem: .metric
        )
        #expect(cs == 4.0)
    }

    // MARK: - Imperial AWG Selection (including 1/0 through 4/0)

    @Test func imperialAWGLargeGauges() async throws {
        // Very high current should select large AWG sizes (represented as negative ints)
        // 10m, 100A, 12V → minCS = (2×100×10×0.017)/(12×0.05) = 34/0.6 = 56.67mm²
        // AWG: first awgCS ≥ 56.67 → 67.4 = 2/0 (represented as -2)
        let awg = ElectricalCalculations.recommendedCrossSection(
            length: 10, current: 100, voltage: 12, unitSystem: .imperial
        )
        #expect(awg == -2.0)

        // Verify voltage drop uses the correct cross-section (67.4mm² for 2/0)
        let drop = ElectricalCalculations.voltageDrop(
            length: 10, current: 100, voltage: 12,
            unitSystem: .imperial, crossSection: awg
        )
        // (2×100×10×0.017)/67.4 = 34/67.4 = 0.5045V
        #expect(abs(drop - 0.5045) < 0.01)
    }

    @Test func imperialAWG4over0ForExtremeCurrent() async throws {
        // 5m, 200A, 12V → minCS = (2×200×5×0.017)/(12×0.05) = 34/0.6 = 56.67mm²
        // Wait: (2×200×5×0.017)/0.6 = 34/0.6 = 56.67 → 67.4 (2/0)
        // 10m, 200A, 12V → minCS = (2×200×10×0.017)/(12×0.05) = 68/0.6 = 113.33mm²
        // AWG: first awgCS ≥ 113.33 → none! → returns last AWG = -4 (4/0, 107mm²)
        let awg = ElectricalCalculations.recommendedCrossSection(
            length: 10, current: 200, voltage: 12, unitSystem: .imperial
        )
        #expect(awg == -4.0)
    }

    @Test func formatAWGDisplaysCorrectNotation() async throws {
        #expect(ElectricalCalculations.formatAWG(14) == "14")
        #expect(ElectricalCalculations.formatAWG(10) == "10")
        #expect(ElectricalCalculations.formatAWG(1) == "1")
        #expect(ElectricalCalculations.formatAWG(-1) == "1/0")
        #expect(ElectricalCalculations.formatAWG(-2) == "2/0")
        #expect(ElectricalCalculations.formatAWG(-3) == "3/0")
        #expect(ElectricalCalculations.formatAWG(-4) == "4/0")
    }

    @Test func imperialAWGForBoatLoad() async throws {
        // Typical boat scenario: 5m (≈16ft), 10A, 12V
        // minCS = (2×10×5×0.017)/(12×0.05) = 1.7/0.6 = 2.833mm²
        // AWG: first >= 2.833 → 3.31mm² = 12 AWG
        let awg = ElectricalCalculations.recommendedCrossSection(
            length: 5, current: 10, voltage: 12, unitSystem: .imperial
        )
        #expect(awg == 12.0)
    }

    @Test func imperialAWGForHighCurrentLoad() async throws {
        // 3m, 50A, 12V
        // minCS = (2×50×3×0.017)/(12×0.05) = 5.1/0.6 = 8.5mm²
        // AWG: first >= 8.5 → 13.3mm² = 6 AWG
        let awg = ElectricalCalculations.recommendedCrossSection(
            length: 3, current: 50, voltage: 12, unitSystem: .imperial
        )
        #expect(awg == 6.0)
    }

    // MARK: - Voltage Drop with Explicit Cross-Section

    @Test func voltageDropWithExplicitCrossSection_metric() async throws {
        // 5m, 10A, 12V, 2.5mm²
        // V_drop = (2×10×5×0.017)/2.5 = 1.7/2.5 = 0.68V
        let drop = ElectricalCalculations.voltageDrop(
            length: 5, current: 10, voltage: 12,
            unitSystem: .metric, crossSection: 2.5
        )
        #expect(abs(drop - 0.68) < 0.001)

        let pct = ElectricalCalculations.voltageDropPercentage(
            length: 5, current: 10, voltage: 12,
            unitSystem: .metric, crossSection: 2.5
        )
        // 0.68/12 × 100 = 5.667%
        #expect(abs(pct - 5.667) < 0.01)
    }

    @Test func voltageDropWithExplicitCrossSection_imperial() async throws {
        // 3m, 15A, 12V, AWG 10 (= 5.26mm²)
        // V_drop = (2×15×3×0.017)/5.26 = 1.53/5.26 = 0.2909V
        let drop = ElectricalCalculations.voltageDrop(
            length: 3, current: 15, voltage: 12,
            unitSystem: .imperial, crossSection: 10 // AWG 10
        )
        #expect(abs(drop - 0.2909) < 0.01)
    }

    // MARK: - Power Loss Verification

    @Test func powerLossEqualsCurrentTimesVoltageDrop() async throws {
        let length = 8.0
        let current = 12.0
        let voltage = 24.0

        let drop = ElectricalCalculations.voltageDrop(
            length: length, current: current, voltage: voltage, unitSystem: .metric
        )
        let loss = ElectricalCalculations.powerLoss(
            length: length, current: current, voltage: voltage, unitSystem: .metric
        )
        // P_loss = I × V_drop
        #expect(abs(loss - current * drop) < 0.001)
    }

    // MARK: - Edge Cases

    @Test func zeroLengthProducesZeroDrop() async throws {
        let drop = ElectricalCalculations.voltageDrop(
            length: 0, current: 10, voltage: 12, unitSystem: .metric
        )
        #expect(drop == 0)

        let loss = ElectricalCalculations.powerLoss(
            length: 0, current: 10, voltage: 12, unitSystem: .metric
        )
        #expect(loss == 0)
    }

    @Test func zeroCurrentProducesZeroDrop() async throws {
        let drop = ElectricalCalculations.voltageDrop(
            length: 10, current: 0, voltage: 12, unitSystem: .metric
        )
        #expect(drop == 0)
    }

    @Test func zeroVoltageReturnsZeroPercentage() async throws {
        let pct = ElectricalCalculations.voltageDropPercentage(
            length: 10, current: 5, voltage: 0, unitSystem: .metric
        )
        #expect(pct == 0)
    }

    @Test func zeroVoltageReturnsZeroCrossSection() async throws {
        // With 0V, maxVoltageDrop = 0, guardAgainstZero returns 0 → smallest standard
        let cs = ElectricalCalculations.recommendedCrossSection(
            length: 10, current: 5, voltage: 0, unitSystem: .metric
        )
        #expect(cs == 0.75)
    }

    // MARK: - Metric and Imperial Consistency

    @Test func metricAndImperialGiveSamePhysicalVoltageDrop() async throws {
        // Same physical setup: 10m cable, 15A, 12V
        // Metric: recommended cross-section in mm²
        // Imperial: recommended cross-section in AWG
        let lengthMeters = 10.0

        let metricCS = ElectricalCalculations.recommendedCrossSection(
            length: lengthMeters, current: 15, voltage: 12, unitSystem: .metric
        )
        let imperialAWG = ElectricalCalculations.recommendedCrossSection(
            length: lengthMeters, current: 15, voltage: 12, unitSystem: .imperial
        )

        let metricDrop = ElectricalCalculations.voltageDrop(
            length: lengthMeters, current: 15, voltage: 12,
            unitSystem: .metric, crossSection: metricCS
        )
        let imperialDrop = ElectricalCalculations.voltageDrop(
            length: lengthMeters, current: 15, voltage: 12,
            unitSystem: .imperial, crossSection: imperialAWG
        )

        // Both should be within 5% voltage drop constraint
        #expect(metricDrop / 12 <= 0.05)
        #expect(imperialDrop / 12 <= 0.05)

        // Both drops should be positive
        #expect(metricDrop > 0)
        #expect(imperialDrop > 0)
    }

    // MARK: - Unit Convention: Length Always in Meters

    @Test func lengthParameterIsAlwaysMeters() async throws {
        // Passing the same meter value with both unit systems should give
        // the same underlying calculation, only differing in output format.
        let lengthMeters = 7.62 // = 25 feet

        let metricMinCS = ElectricalCalculations.recommendedCrossSection(
            length: lengthMeters, current: 15, voltage: 120, unitSystem: .metric
        )
        let imperialAWG = ElectricalCalculations.recommendedCrossSection(
            length: lengthMeters, current: 15, voltage: 120, unitSystem: .imperial
        )

        // Minimum raw cross-section: (2×15×7.62×0.017)/(120×0.05) = 0.648mm²
        // Metric: rounds to 0.75mm² (smallest standard ≥ 0.648)
        #expect(metricMinCS == 0.75)
        // Imperial: AWG 18 (0.823mm² ≥ 0.648)
        #expect(imperialAWG == 18.0)
    }

    // MARK: - Voltage Drop Constraint

    @Test func recommendedCrossSectionKeepsDropBelow5Percent() async throws {
        // Test several scenarios to verify the 5% constraint
        let scenarios: [(length: Double, current: Double, voltage: Double)] = [
            (1, 1, 12),       // minimal
            (5, 10, 12),      // moderate boat load
            (10, 20, 24),     // 24V system
            (15, 50, 12),     // high current
            (3, 100, 48),     // very high current, 48V
            (20, 5, 12),      // long run, low current
        ]

        for s in scenarios {
            let cs = ElectricalCalculations.recommendedCrossSection(
                length: s.length, current: s.current, voltage: s.voltage, unitSystem: .metric
            )
            let dropPct = ElectricalCalculations.voltageDropPercentage(
                length: s.length, current: s.current, voltage: s.voltage,
                unitSystem: .metric, crossSection: cs
            )
            #expect(dropPct <= 5.0, "Drop \(dropPct)% exceeds 5% for \(s.length)m, \(s.current)A, \(s.voltage)V with \(cs)mm²")
        }
    }

    // MARK: - Formula Cross-Check: V_drop = I²R path

    @Test func voltageDropMatchesOhmsLaw() async throws {
        let length = 6.0
        let current = 8.0
        let crossSection = 4.0 // mm²
        let resistivity = 0.017

        // R = (2 × L × ρ) / A = (2 × 6 × 0.017) / 4 = 0.051 Ω
        let resistance = (2 * length * resistivity) / crossSection
        // V = I × R = 8 × 0.051 = 0.408V
        let expectedDrop = current * resistance
        // P = I² × R = 64 × 0.051 = 3.264W
        let expectedPowerLoss = current * current * resistance

        let actualDrop = ElectricalCalculations.voltageDrop(
            length: length, current: current, voltage: 12,
            unitSystem: .metric, crossSection: crossSection
        )
        let actualLoss = ElectricalCalculations.powerLoss(
            length: length, current: current, voltage: 12,
            unitSystem: .metric, crossSection: crossSection
        )

        #expect(abs(actualDrop - expectedDrop) < 0.001)
        #expect(abs(actualLoss - expectedPowerLoss) < 0.001)
    }
}
