import XCTest
@testable import Rosetta

@MainActor
final class CallSoundAndTimeoutTests: XCTestCase {
    private let ownKey = "02-own-sound-test"
    private let peerKey = "02-peer-sound-test"

    override func setUp() {
        super.setUp()
        CallManager.shared.resetForSessionEnd()
        CallManager.shared.bindAccount(publicKey: ownKey)
    }

    override func tearDown() {
        CallManager.shared.resetForSessionEnd()
        super.tearDown()
    }

    // MARK: - Ring Timeout Tests

    func testRingTimeoutTaskCreatedOnOutgoingCall() {
        let result = CallManager.shared.startOutgoingCall(
            toPublicKey: peerKey,
            title: "Peer",
            username: "peer"
        )

        XCTAssertEqual(result, .started)
        XCTAssertNotNil(CallManager.shared.ringTimeoutTask, "Ring timeout should be set for outgoing call")
        XCTAssertEqual(CallManager.shared.uiState.phase, .outgoing)
    }

    func testRingTimeoutTaskCreatedOnIncomingCall() {
        let packet = PacketSignalPeer(
            src: peerKey,
            dst: ownKey,
            sharedPublic: "",
            signalType: .call,
            roomId: ""
        )

        CallManager.shared.testHandleSignalPacket(packet)

        XCTAssertNotNil(CallManager.shared.ringTimeoutTask, "Ring timeout should be set for incoming call")
        XCTAssertEqual(CallManager.shared.uiState.phase, .incoming)
    }

    func testRingTimeoutCancelledOnAcceptIncoming() {
        // Set up incoming call
        let packet = PacketSignalPeer(
            src: peerKey,
            dst: ownKey,
            sharedPublic: "",
            signalType: .call,
            roomId: ""
        )
        CallManager.shared.testHandleSignalPacket(packet)
        XCTAssertNotNil(CallManager.shared.ringTimeoutTask)

        // Accept
        _ = CallManager.shared.acceptIncomingCall()

        XCTAssertNil(CallManager.shared.ringTimeoutTask, "Ring timeout should be cancelled on accept")
    }

    func testRingTimeoutCancelledOnDeclineIncoming() {
        let packet = PacketSignalPeer(
            src: peerKey,
            dst: ownKey,
            sharedPublic: "",
            signalType: .call,
            roomId: ""
        )
        CallManager.shared.testHandleSignalPacket(packet)
        XCTAssertNotNil(CallManager.shared.ringTimeoutTask)

        CallManager.shared.declineIncomingCall()

        XCTAssertNil(CallManager.shared.ringTimeoutTask, "Ring timeout should be cancelled on decline")
        XCTAssertEqual(CallManager.shared.uiState.phase, .idle)
    }

    func testRingTimeoutCancelledOnEndCall() {
        _ = CallManager.shared.startOutgoingCall(
            toPublicKey: peerKey,
            title: "Peer",
            username: "peer"
        )
        XCTAssertNotNil(CallManager.shared.ringTimeoutTask)

        CallManager.shared.endCall()

        XCTAssertNil(CallManager.shared.ringTimeoutTask, "Ring timeout should be cancelled on endCall")
    }

    func testRingTimeoutCancelledOnBusySignal() {
        _ = CallManager.shared.startOutgoingCall(
            toPublicKey: peerKey,
            title: "Peer",
            username: "peer"
        )
        XCTAssertNotNil(CallManager.shared.ringTimeoutTask)

        let busyPacket = PacketSignalPeer(
            src: "",
            dst: "",
            sharedPublic: "",
            signalType: .endCallBecauseBusy,
            roomId: ""
        )
        CallManager.shared.testHandleSignalPacket(busyPacket)

        XCTAssertNil(CallManager.shared.ringTimeoutTask, "Ring timeout should be cancelled on busy signal")
        XCTAssertEqual(CallManager.shared.uiState.phase, .idle)
    }

    func testRingTimeoutCancelledOnPeerEndCall() {
        let incomingPacket = PacketSignalPeer(
            src: peerKey,
            dst: ownKey,
            sharedPublic: "",
            signalType: .call,
            roomId: ""
        )
        CallManager.shared.testHandleSignalPacket(incomingPacket)
        XCTAssertNotNil(CallManager.shared.ringTimeoutTask)

        let endPacket = PacketSignalPeer(
            src: "",
            dst: "",
            sharedPublic: "",
            signalType: .endCall,
            roomId: ""
        )
        CallManager.shared.testHandleSignalPacket(endPacket)

        XCTAssertNil(CallManager.shared.ringTimeoutTask, "Ring timeout should be cancelled when peer ends call")
        XCTAssertEqual(CallManager.shared.uiState.phase, .idle)
    }

    // MARK: - Sound Flow Tests (verify phase transitions trigger correct states)

    func testOutgoingCallSetsCorrectPhaseForSounds() {
        let result = CallManager.shared.startOutgoingCall(
            toPublicKey: peerKey,
            title: "Peer",
            username: "peer"
        )

        XCTAssertEqual(result, .started)
        XCTAssertEqual(CallManager.shared.uiState.phase, .outgoing)
        // playCalling() should have been called (verified by phase)
    }

    func testIncomingCallSetsCorrectPhaseForSounds() {
        let packet = PacketSignalPeer(
            src: peerKey,
            dst: ownKey,
            sharedPublic: "",
            signalType: .call,
            roomId: ""
        )
        CallManager.shared.testHandleSignalPacket(packet)

        XCTAssertEqual(CallManager.shared.uiState.phase, .incoming)
        // playRingtone() should have been called (verified by phase)
    }

    func testCallActiveTransitionClearsTimeout() {
        CallManager.shared.testSetUiState(
            CallUiState(
                phase: .webRtcExchange,
                peerPublicKey: peerKey,
                statusText: "Connecting..."
            )
        )
        // Manually set timeout to verify it gets cleared
        CallManager.shared.startRingTimeout()
        XCTAssertNotNil(CallManager.shared.ringTimeoutTask)

        CallManager.shared.setCallActiveIfNeeded()

        XCTAssertEqual(CallManager.shared.uiState.phase, .active)
        XCTAssertNil(CallManager.shared.ringTimeoutTask, "Ring timeout should be cancelled when call becomes active")
    }

    // MARK: - Duplicate Call Prevention

    func testCannotStartSecondCallWhileInCall() {
        _ = CallManager.shared.startOutgoingCall(
            toPublicKey: peerKey,
            title: "Peer",
            username: "peer"
        )

        let result = CallManager.shared.startOutgoingCall(
            toPublicKey: "02-another-peer",
            title: "Another",
            username: "another"
        )

        XCTAssertEqual(result, .alreadyInCall)
        XCTAssertEqual(CallManager.shared.uiState.peerPublicKey, peerKey, "Original call should not be replaced")
    }

    func testCannotAcceptWhenNotIncoming() {
        _ = CallManager.shared.startOutgoingCall(
            toPublicKey: peerKey,
            title: "Peer",
            username: "peer"
        )

        let result = CallManager.shared.acceptIncomingCall()

        XCTAssertEqual(result, .notIncoming)
    }

    // MARK: - CallsViewModel Filter Tests

    func testCallLogDirectionMapping() {
        // Outgoing with duration > 0 = outgoing
        XCTAssertFalse(CallLogDirection.outgoing.isMissed)
        XCTAssertTrue(CallLogDirection.outgoing.isOutgoing)
        XCTAssertFalse(CallLogDirection.outgoing.isIncoming)

        // Incoming with duration > 0 = incoming
        XCTAssertFalse(CallLogDirection.incoming.isMissed)
        XCTAssertFalse(CallLogDirection.incoming.isOutgoing)
        XCTAssertTrue(CallLogDirection.incoming.isIncoming)

        // Duration 0 + incoming = missed
        XCTAssertTrue(CallLogDirection.missed.isMissed)
        XCTAssertTrue(CallLogDirection.missed.isIncoming)

        // Duration 0 + outgoing = rejected
        XCTAssertFalse(CallLogDirection.rejected.isMissed)
        XCTAssertTrue(CallLogDirection.rejected.isOutgoing)
    }
}
