import XCTest
@testable import Rosetta

final class CallPacketParityTests: XCTestCase {
    func testSignalPeerRoundTripForLegacyCallAcceptEndCallAndKeyExchange() throws {
        let call = PacketSignalPeer(
            src: "02caller",
            dst: "02callee",
            sharedPublic: "",
            signalType: .call,
            callId: "call-123",
            joinToken: "join-123",
            roomId: ""
        )
        let accept = PacketSignalPeer(
            src: "02callee",
            dst: "02caller",
            sharedPublic: "",
            signalType: .accept,
            callId: "call-123",
            joinToken: "join-123",
            roomId: ""
        )
        let keyExchange = PacketSignalPeer(
            src: "02callee",
            dst: "02caller",
            sharedPublic: "abcdef012345",
            signalType: .keyExchange,
            callId: "",
            joinToken: "",
            roomId: ""
        )
        let endCall = PacketSignalPeer(
            src: "02caller",
            dst: "02callee",
            sharedPublic: "",
            signalType: .endCall,
            callId: "call-123",
            joinToken: "join-123",
            roomId: ""
        )

        let decodedCall = try decodeSignal(call)
        XCTAssertEqual(decodedCall.signalType, .call)
        XCTAssertEqual(decodedCall.src, "02caller")
        XCTAssertEqual(decodedCall.dst, "02callee")
        XCTAssertEqual(decodedCall.sharedPublic, "")
        XCTAssertEqual(decodedCall.callId, "call-123")
        XCTAssertEqual(decodedCall.joinToken, "join-123")
        XCTAssertEqual(decodedCall.roomId, "")

        let decodedAccept = try decodeSignal(accept)
        XCTAssertEqual(decodedAccept.signalType, .accept)
        XCTAssertEqual(decodedAccept.callId, "call-123")
        XCTAssertEqual(decodedAccept.joinToken, "join-123")

        let decodedKeyExchange = try decodeSignal(keyExchange)
        XCTAssertEqual(decodedKeyExchange.signalType, .keyExchange)
        XCTAssertEqual(decodedKeyExchange.src, "02callee")
        XCTAssertEqual(decodedKeyExchange.dst, "02caller")
        XCTAssertEqual(decodedKeyExchange.sharedPublic, "abcdef012345")
        XCTAssertEqual(decodedKeyExchange.callId, "")
        XCTAssertEqual(decodedKeyExchange.joinToken, "")
        XCTAssertEqual(decodedKeyExchange.roomId, "")

        let decodedEndCall = try decodeSignal(endCall)
        XCTAssertEqual(decodedEndCall.signalType, .endCall)
        XCTAssertEqual(decodedEndCall.callId, "call-123")
        XCTAssertEqual(decodedEndCall.joinToken, "join-123")
    }

    func testSignalCodeFourRoundTripForLegacyActiveAndCreateRoomFallback() throws {
        let legacyActive = PacketSignalPeer(
            src: "02caller",
            dst: "02callee",
            sharedPublic: "",
            signalType: .createRoom,
            callId: "",
            joinToken: "",
            roomId: ""
        )
        let createRoom = PacketSignalPeer(
            src: "02caller",
            dst: "02callee",
            sharedPublic: "",
            signalType: .createRoom,
            callId: "",
            joinToken: "",
            roomId: "room-42"
        )

        let decodedLegacyActive = try decodeSignal(legacyActive)
        XCTAssertEqual(decodedLegacyActive.signalType, .createRoom)
        XCTAssertEqual(decodedLegacyActive.roomId, "")

        let decodedCreateRoom = try decodeSignal(createRoom)
        XCTAssertEqual(decodedCreateRoom.signalType, .createRoom)
        XCTAssertEqual(decodedCreateRoom.roomId, "room-42")
    }

    func testSignalPeerRoundTripForBusyPeerDisconnectedAndRingingTimeoutShortFormat() throws {
        let busy = PacketSignalPeer(
            src: "02should-not-be-sent",
            dst: "02should-not-be-sent",
            sharedPublic: "ignored",
            signalType: .endCallBecauseBusy,
            callId: "ignored",
            joinToken: "ignored",
            roomId: "ignored-room"
        )
        let disconnected = PacketSignalPeer(
            src: "02should-not-be-sent",
            dst: "02should-not-be-sent",
            sharedPublic: "ignored",
            signalType: .endCallBecausePeerDisconnected,
            callId: "ignored",
            joinToken: "ignored",
            roomId: "ignored-room"
        )
        let ringingTimeout = PacketSignalPeer(
            src: "02should-not-be-sent",
            dst: "02should-not-be-sent",
            sharedPublic: "ignored",
            signalType: .ringingTimeout,
            callId: "ignored",
            joinToken: "ignored",
            roomId: "ignored-room"
        )

        let decodedBusy = try decodeSignal(busy)
        XCTAssertEqual(decodedBusy.signalType, .endCallBecauseBusy)
        XCTAssertEqual(decodedBusy.src, "")
        XCTAssertEqual(decodedBusy.dst, "")
        XCTAssertEqual(decodedBusy.sharedPublic, "")
        XCTAssertEqual(decodedBusy.callId, "")
        XCTAssertEqual(decodedBusy.joinToken, "")
        XCTAssertEqual(decodedBusy.roomId, "")

        let decodedDisconnected = try decodeSignal(disconnected)
        XCTAssertEqual(decodedDisconnected.signalType, .endCallBecausePeerDisconnected)
        XCTAssertEqual(decodedDisconnected.src, "")
        XCTAssertEqual(decodedDisconnected.dst, "")
        XCTAssertEqual(decodedDisconnected.sharedPublic, "")
        XCTAssertEqual(decodedDisconnected.callId, "")
        XCTAssertEqual(decodedDisconnected.joinToken, "")
        XCTAssertEqual(decodedDisconnected.roomId, "")

        let decodedTimeout = try decodeSignal(ringingTimeout)
        XCTAssertEqual(decodedTimeout.signalType, .ringingTimeout)
        XCTAssertEqual(decodedTimeout.src, "")
        XCTAssertEqual(decodedTimeout.dst, "")
        XCTAssertEqual(decodedTimeout.sharedPublic, "")
        XCTAssertEqual(decodedTimeout.callId, "")
        XCTAssertEqual(decodedTimeout.joinToken, "")
        XCTAssertEqual(decodedTimeout.roomId, "")
    }

    func testWebRtcRoundTripForOfferAnswerAndIceCandidate() throws {
        let offer = PacketWebRTC(signalType: .offer, sdpOrCandidate: #"{"type":"offer","sdp":"v=0"}"#)
        let answer = PacketWebRTC(signalType: .answer, sdpOrCandidate: #"{"type":"answer","sdp":"v=0"}"#)
        let candidate = PacketWebRTC(
            signalType: .iceCandidate,
            sdpOrCandidate: #"{"candidate":"candidate:1 1 udp 2130706431 10.0.0.1 7777 typ host"}"#
        )

        let decodedOffer = try decodeWebRtc(offer)
        XCTAssertEqual(decodedOffer.signalType, .offer)
        XCTAssertEqual(decodedOffer.sdpOrCandidate, offer.sdpOrCandidate)

        let decodedAnswer = try decodeWebRtc(answer)
        XCTAssertEqual(decodedAnswer.signalType, .answer)
        XCTAssertEqual(decodedAnswer.sdpOrCandidate, answer.sdpOrCandidate)

        let decodedCandidate = try decodeWebRtc(candidate)
        XCTAssertEqual(decodedCandidate.signalType, .iceCandidate)
        XCTAssertEqual(decodedCandidate.sdpOrCandidate, candidate.sdpOrCandidate)
    }

    func testIceServersRoundTrip() throws {
        let packet = PacketIceServers(
            iceServers: [
                CallIceServer(
                    url: "turn:turn.rosetta.im?transport=udp",
                    username: "u1",
                    credential: "p1",
                    transport: "udp"
                ),
                CallIceServer(
                    url: "stun:stun.l.google.com:19302",
                    username: "",
                    credential: "",
                    transport: ""
                ),
            ]
        )

        let encoded = PacketRegistry.encode(packet)
        guard let decoded = PacketRegistry.decode(from: encoded),
              let decodedPacket = decoded.packet as? PacketIceServers
        else {
            XCTFail("Failed to decode PacketIceServers")
            return
        }

        XCTAssertEqual(decoded.packetId, 0x1C)
        XCTAssertEqual(decodedPacket.iceServers, packet.iceServers)
    }

    private func decodeSignal(_ packet: PacketSignalPeer) throws -> PacketSignalPeer {
        let encoded = PacketRegistry.encode(packet)
        guard let decoded = PacketRegistry.decode(from: encoded),
              let decodedPacket = decoded.packet as? PacketSignalPeer
        else {
            throw NSError(domain: "CallPacketParityTests", code: 1)
        }
        XCTAssertEqual(decoded.packetId, 0x1A)
        return decodedPacket
    }

    private func decodeWebRtc(_ packet: PacketWebRTC) throws -> PacketWebRTC {
        let encoded = PacketRegistry.encode(packet)
        guard let decoded = PacketRegistry.decode(from: encoded),
              let decodedPacket = decoded.packet as? PacketWebRTC
        else {
            throw NSError(domain: "CallPacketParityTests", code: 2)
        }
        XCTAssertEqual(decoded.packetId, 0x1B)
        return decodedPacket
    }
}
