import Testing
import Foundation
import Combine
import CoreBluetooth
import BitFoundation
@testable import bitchat

private final class DefaultDelegateProbe: BitchatDelegate {
    func didReceiveMessage(_ message: BitchatMessage) {}
    func didConnectToPeer(_ peerID: PeerID) {}
    func didDisconnectFromPeer(_ peerID: PeerID) {}
    func didUpdatePeerList(_ peers: [PeerID]) {}
    func didUpdateBluetoothState(_ state: CBManagerState) {}
}

private final class DefaultTransportProbe: Transport {
    weak var delegate: BitchatDelegate?
    weak var eventDelegate: TransportEventDelegate?
    weak var peerEventsDelegate: TransportPeerEventsDelegate?

    let subject = CurrentValueSubject<[TransportPeerSnapshot], Never>([])
    let myPeerID = PeerID(str: "0011223344556677")
    var myNickname = "Tester"
    private(set) var sentMessages: [(content: String, mentions: [String])] = []

    func currentPeerSnapshots() -> [TransportPeerSnapshot] { subject.value }
    func setNickname(_ nickname: String) { myNickname = nickname }
    func startServices() {}
    func stopServices() {}
    func emergencyDisconnectAll() {}
    func isPeerConnected(_ peerID: PeerID) -> Bool { false }
    func isPeerReachable(_ peerID: PeerID) -> Bool { false }
    func peerNickname(peerID: PeerID) -> String? { nil }
    func getPeerNicknames() -> [PeerID: String] { [:] }
    func getFingerprint(for peerID: PeerID) -> String? { nil }
    func getNoiseSessionState(for peerID: PeerID) -> LazyHandshakeState { .none }
    func triggerHandshake(with peerID: PeerID) {}
    func sendMessage(_ content: String, mentions: [String]) { sentMessages.append((content, mentions)) }
    func sendPrivateMessage(_ content: String, to peerID: PeerID, recipientNickname: String, messageID: String) {}
    func sendReadReceipt(_ receipt: ReadReceipt, to peerID: PeerID) {}
    func sendFavoriteNotification(to peerID: PeerID, isFavorite: Bool) {}
    func sendBroadcastAnnounce() {}
    func sendDeliveryAck(for messageID: String, to peerID: PeerID) {}
}

struct ProtocolContractTests {
    @Test
    func commandInfo_exposesAliasesPlaceholdersAndGeoVariants() {
        // Aliases must match what CommandProcessor actually accepts —
        // the suggestion panel is the only command-discovery surface.
        #expect(CommandInfo.message.id == "msg")
        #expect(CommandInfo.message.alias == "/msg")
        #expect(CommandInfo.message.placeholder != nil)
        #expect(CommandInfo.clear.placeholder == nil)
        #expect(CommandInfo.favorite.description.isEmpty == false)
        #expect(CommandInfo.all(isGeoPublic: false, isGeoDM: false).contains(.help))
        // Favorites are rejected by the processor in geohash contexts, so
        // they are suggested only in mesh.
        #expect(CommandInfo.all(isGeoPublic: false, isGeoDM: false).contains(.favorite))
        #expect(CommandInfo.all(isGeoPublic: true, isGeoDM: false).contains(.favorite) == false)
        #expect(CommandInfo.all(isGeoPublic: false, isGeoDM: true).contains(.unfavorite) == false)
    }

    @Test
    func protocolEnums_andDelegateDefaults_haveStableContracts() {
        let delegate = DefaultDelegateProbe()
        let peerID = PeerID(str: "8899aabbccddeeff")

        #expect(MessageType.requestSync.description == "requestSync")
        #expect(NoisePayloadType.verifyResponse.description == "verifyResponse")
        #expect(DeliveryStatus.sending.displayText == "Sending...")
        #expect(DeliveryStatus.sent.displayText == "Sent")
        #expect(DeliveryStatus.delivered(to: "Alice", at: Date()).displayText == "Delivered to Alice")
        #expect(DeliveryStatus.read(by: "Bob", at: Date()).displayText == "Read by Bob")
        #expect(DeliveryStatus.failed(reason: "oops").displayText == "Failed: oops")
        #expect(DeliveryStatus.partiallyDelivered(reached: 1, total: 3).displayText == "Delivered to 1/3")
        #expect(delegate.isFavorite(fingerprint: "fp") == false)

        delegate.didUpdateMessageDeliveryStatus("msg-1", status: .sent)
        delegate.didReceiveNoisePayload(from: peerID, type: .privateMessage, payload: Data(), timestamp: Date())
        delegate.didReceivePublicMessage(from: peerID, nickname: "Alice", content: "hi", timestamp: Date(), messageID: "msg-1")
    }

    @Test
    func transportDefaults_forwardOrNoOp() {
        let probe = DefaultTransportProbe()
        let peerID = PeerID(str: "0123456789abcdef")
        let filePacket = BitchatFilePacket(
            fileName: "voice.m4a",
            fileSize: 4,
            mimeType: "audio/mp4",
            content: Data([1, 2, 3, 4])
        )

        probe.sendMessage("hello", mentions: ["@alice"], messageID: "msg-1", timestamp: Date())
        probe.sendVerifyChallenge(to: peerID, noiseKeyHex: "abcd", nonceA: Data([0x01]))
        probe.sendVerifyResponse(to: peerID, noiseKeyHex: "abcd", nonceA: Data([0x02]))
        probe.sendFileBroadcast(filePacket, transferId: "tx-1")
        probe.sendFilePrivate(filePacket, to: peerID, transferId: "tx-2")
        probe.cancelTransfer("tx-3")
        probe.declinePendingFile(id: "pending")

        #expect(probe.sentMessages.count == 1)
        #expect(probe.sentMessages.first?.content == "hello")
        #expect(probe.acceptPendingFile(id: "pending") == nil)
        // Secure delivery defaults to prompt delivery (itself defaulting to
        // reachability) for transports without a forgeable link layer.
        #expect(probe.canDeliverSecurely(to: peerID) == false)
    }

    @Test
    func previewMessage_exposesStableSampleShape() {
        let preview = BitchatMessage.preview

        #expect(preview.sender == "John Doe")
        #expect(preview.content == "Hello")
        #expect(preview.deliveryStatus == .sent)
        #expect(preview.isPrivate == false)
    }
}
