import XCTest
@testable import Rosetta

final class VoiceRecordingParityMathTests: XCTestCase {

    func testParityConstants() {
        XCTAssertEqual(VoiceRecordingParityConstants.holdThreshold, 0.19, accuracy: 0.0001)
        XCTAssertEqual(VoiceRecordingParityConstants.cancelDistanceThreshold, -150)
        XCTAssertEqual(VoiceRecordingParityConstants.cancelHapticThreshold, -100)
        XCTAssertEqual(VoiceRecordingParityConstants.lockDistanceThreshold, -110)
        XCTAssertEqual(VoiceRecordingParityConstants.lockHapticThreshold, -60)
        XCTAssertEqual(VoiceRecordingParityConstants.velocityGate, -400)
        XCTAssertEqual(VoiceRecordingParityConstants.preHoldCancelDistance, 10)
        XCTAssertEqual(VoiceRecordingParityConstants.micHitInsetX, -10)
        XCTAssertEqual(VoiceRecordingParityConstants.locknessDivisor, 105)
        XCTAssertEqual(VoiceRecordingParityConstants.dragNormalizeDivisor, 300)
        XCTAssertEqual(VoiceRecordingParityConstants.cancelTransformThreshold, 8)
        XCTAssertEqual(VoiceRecordingParityConstants.sendAccessibilityHitSize, 120)
        XCTAssertEqual(VoiceRecordingParityConstants.minVoiceDuration, 0.5)
        XCTAssertEqual(VoiceRecordingParityConstants.minFreeDiskBytes, 8 * 1024 * 1024)
    }

    func testReleaseDecisionVelocityGate() {
        let cancel = VoiceRecordingParityMath.releaseDecision(
            velocityX: -500,
            velocityY: 0,
            distanceX: -10,
            distanceY: 0
        )
        XCTAssertEqual(cancel, .cancel)

        let lock = VoiceRecordingParityMath.releaseDecision(
            velocityX: 0,
            velocityY: -500,
            distanceX: 0,
            distanceY: -10
        )
        XCTAssertEqual(lock, .lock)
    }

    func testReleaseDecisionDistanceFallback() {
        let cancel = VoiceRecordingParityMath.releaseDecision(
            velocityX: 0,
            velocityY: 0,
            distanceX: -120,
            distanceY: 0
        )
        XCTAssertEqual(cancel, .cancel)

        let lock = VoiceRecordingParityMath.releaseDecision(
            velocityX: 0,
            velocityY: 0,
            distanceX: 0,
            distanceY: -80
        )
        XCTAssertEqual(lock, .lock)

        let finish = VoiceRecordingParityMath.releaseDecision(
            velocityX: 0,
            velocityY: 0,
            distanceX: -20,
            distanceY: -20
        )
        XCTAssertEqual(finish, .finish)
    }

    func testDominantAxis() {
        XCTAssertEqual(
            VoiceRecordingParityMath.dominantAxisDistances(distanceX: -110, distanceY: -40).0,
            -110
        )
        XCTAssertEqual(
            VoiceRecordingParityMath.dominantAxisDistances(distanceX: -110, distanceY: -40).1,
            0
        )

        XCTAssertEqual(
            VoiceRecordingParityMath.dominantAxisDistances(distanceX: -20, distanceY: -80).0,
            0
        )
        XCTAssertEqual(
            VoiceRecordingParityMath.dominantAxisDistances(distanceX: -20, distanceY: -80).1,
            -80
        )
    }

    func testLocknessNormalization() {
        XCTAssertEqual(VoiceRecordingParityMath.lockness(distanceY: 0), 0)
        XCTAssertEqual(VoiceRecordingParityMath.lockness(distanceY: -52.5), 0.5, accuracy: 0.0001)
        XCTAssertEqual(VoiceRecordingParityMath.lockness(distanceY: -500), 1)
    }

    func testNormalizedDrag() {
        XCTAssertEqual(VoiceRecordingParityMath.normalizedDrag(distance: 0), 0, accuracy: 0.0001)
        XCTAssertEqual(VoiceRecordingParityMath.normalizedDrag(distance: 150), 0.5, accuracy: 0.0001)
        XCTAssertEqual(VoiceRecordingParityMath.normalizedDrag(distance: -450), 1, accuracy: 0.0001)
    }

    func testCancelTransformThreshold() {
        XCTAssertFalse(VoiceRecordingParityMath.shouldApplyCancelTransform(-8))
        XCTAssertTrue(VoiceRecordingParityMath.shouldApplyCancelTransform(-8.1))
    }

    func testShortRecordingDiscardGuard() {
        XCTAssertTrue(VoiceRecordingParityMath.shouldDiscard(duration: 0.49))
        XCTAssertFalse(VoiceRecordingParityMath.shouldDiscard(duration: 0.5))
        XCTAssertFalse(VoiceRecordingParityMath.shouldDiscard(duration: 1.2))
    }

    func testMinTrimDurationFormula() {
        let minDuration = VoiceRecordingParityConstants.minTrimDuration(duration: 10, waveformWidth: 280)
        XCTAssertEqual(minDuration, 2.0, accuracy: 0.0001)

        let clamped = VoiceRecordingParityConstants.minTrimDuration(duration: 1, waveformWidth: 600)
        XCTAssertEqual(clamped, 1.0, accuracy: 0.0001)
    }

    func testClampTrimRange() {
        let range = VoiceRecordingParityMath.clampTrimRange((-3)...(12), duration: 8)
        XCTAssertEqual(range.lowerBound, 0)
        XCTAssertEqual(range.upperBound, 8)
    }

    func testWaveformSliceRange() {
        let range = VoiceRecordingParityMath.waveformSliceRange(
            sampleCount: 100,
            totalDuration: 10,
            trimRange: 2...7
        )
        XCTAssertEqual(range, 20..<70)
    }

    func testWaveformSliceRangeInvalidInputs() {
        XCTAssertNil(
            VoiceRecordingParityMath.waveformSliceRange(
                sampleCount: 0,
                totalDuration: 10,
                trimRange: 2...7
            )
        )
        XCTAssertNil(
            VoiceRecordingParityMath.waveformSliceRange(
                sampleCount: 100,
                totalDuration: 0,
                trimRange: 2...7
            )
        )
    }

}
