import Foundation
import Testing
@testable import MusicShared

struct RemoteProtocolTests {
    private let encoder: JSONEncoder = {
        let e = JSONEncoder()
        e.outputFormatting = [.sortedKeys]
        return e
    }()
    private let decoder = JSONDecoder()

    // MARK: - Helpers

    /// Encode then decode a value, returning the decoded copy.
    private func roundTrip<T: Codable>(_ value: T) throws -> T {
        let data = try encoder.encode(value)
        return try decoder.decode(T.self, from: data)
    }

    // MARK: - RemoteCommand round-trip tests
    // Each test encodes a RemoteCommand case to JSON and decodes it back,
    // verifying the decoded value equals the original.

    @Test func remoteCommandRoundTrip_play() throws {
        let cmd = RemoteCommand.play(trackId: 42, queueIds: [42, 43, 44, 45])
        #expect(try roundTrip(cmd) == cmd)
    }

    @Test func remoteCommandRoundTrip_pause() throws {
        let cmd = RemoteCommand.pause
        #expect(try roundTrip(cmd) == cmd)
    }

    @Test func remoteCommandRoundTrip_resume() throws {
        let cmd = RemoteCommand.resume
        #expect(try roundTrip(cmd) == cmd)
    }

    @Test func remoteCommandRoundTrip_next() throws {
        let cmd = RemoteCommand.next
        #expect(try roundTrip(cmd) == cmd)
    }

    @Test func remoteCommandRoundTrip_previous() throws {
        let cmd = RemoteCommand.previous
        #expect(try roundTrip(cmd) == cmd)
    }

    @Test func remoteCommandRoundTrip_seek() throws {
        let cmd = RemoteCommand.seek(position: 123.456)
        #expect(try roundTrip(cmd) == cmd)
    }

    @Test func remoteCommandRoundTrip_setVolume() throws {
        let cmd = RemoteCommand.setVolume(level: 0.75)
        #expect(try roundTrip(cmd) == cmd)
    }

    @Test func remoteCommandRoundTrip_toggleShuffle() throws {
        let cmd = RemoteCommand.toggleShuffle
        #expect(try roundTrip(cmd) == cmd)
    }

    @Test func remoteCommandRoundTrip_refreshDB() throws {
        let cmd = RemoteCommand.refreshDB
        #expect(try roundTrip(cmd) == cmd)
    }

    // MARK: - HostEvent round-trip tests
    // Each test encodes a HostEvent case to JSON and decodes it back,
    // verifying the decoded value equals the original.

    @Test func hostEventRoundTrip_playbackState() throws {
        let payload = PlaybackStatePayload(
            trackId: 7,
            isPlaying: true,
            currentTime: 42.5,
            duration: 210.0,
            volume: 0.8,
            isShuffled: false
        )
        let event = HostEvent.playbackState(payload)
        #expect(try roundTrip(event) == event)
    }

    @Test func hostEventRoundTrip_dbReady() throws {
        let event = HostEvent.dbReady
        #expect(try roundTrip(event) == event)
    }

    @Test func hostEventRoundTrip_error() throws {
        let event = HostEvent.error(message: "Something went wrong")
        #expect(try roundTrip(event) == event)
    }

    // MARK: - HandshakeMessage round-trip test
    // Verifies HandshakeMessage survives JSON encoding and decoding.

    @Test func handshakeMessageRoundTrip() throws {
        let msg = HandshakeMessage(protocolVersion: RemoteProtocolVersion, appVersion: "1.2.3")
        #expect(try roundTrip(msg) == msg)
    }

    // MARK: - Wire format decode tests
    // Verify that hand-crafted JSON strings matching the expected wire format
    // decode correctly, ensuring the Codable implementation matches the spec.

    @Test func wireFormatDecode_playCommand() throws {
        let json = """
        {"type":"play","payload":{"trackId":42,"queueIds":[42,43,44,45]}}
        """
        let decoded = try decoder.decode(RemoteCommand.self, from: Data(json.utf8))
        #expect(decoded == .play(trackId: 42, queueIds: [42, 43, 44, 45]))
    }

    @Test func wireFormatDecode_playbackStateEvent() throws {
        let json = """
        {"type":"playbackState","payload":{"trackId":7,"isPlaying":true,"currentTime":42.5,"duration":210.0,"volume":0.8,"isShuffled":false}}
        """
        let decoded = try decoder.decode(HostEvent.self, from: Data(json.utf8))
        let expected = HostEvent.playbackState(PlaybackStatePayload(
            trackId: 7,
            isPlaying: true,
            currentTime: 42.5,
            duration: 210.0,
            volume: 0.8,
            isShuffled: false
        ))
        #expect(decoded == expected)
    }
}
