import Foundation
import Testing
import NucleicProtocol

@testable import NucleicTailnet

/// `FDFrameChannel` is the byte layer of the Tailnet transport, but it's fd-generic — so an
/// ordinary socketpair stands in for the tsnet conn and exercises the real DispatchIO
/// framing paths on both ends.
@Suite struct FDFrameChannelTests {
    private func socketPair() -> (Int32, Int32) {
        var fds: [Int32] = [0, 0]
        precondition(socketpair(AF_UNIX, SOCK_STREAM, 0, &fds) == 0)
        return (fds[0], fds[1])
    }

    /// First frame from a channel's stream, bounded so a broken channel fails instead of
    /// hanging the suite.
    private func nextFrame(_ channel: FDFrameChannel, within seconds: Double = 5) async -> Data? {
        await withTaskGroup(of: Data?.self) { group in
            group.addTask {
                for await frame in channel.frames() { return frame }
                return nil
            }
            group.addTask {
                try? await Task.sleep(for: .seconds(seconds))
                return nil
            }
            let first = await group.next()!
            group.cancelAll()
            return first
        }
    }

    @Test func framesFlowBothWays() async {
        let (a, b) = socketPair()
        let left = FDFrameChannel(fd: a, peerDescription: "left")
        let right = FDFrameChannel(fd: b, peerDescription: "right")

        left.send(Data("hello".utf8))
        #expect(await nextFrame(right) == Data("hello".utf8))

        right.send(Data("world".utf8))
        #expect(await nextFrame(left) == Data("world".utf8))

        left.close()
        right.close()
    }

    @Test func framesArriveWholeAndInOrder() async {
        let (a, b) = socketPair()
        let left = FDFrameChannel(fd: a, peerDescription: "left")
        let right = FDFrameChannel(fd: b, peerDescription: "right")

        let sent = (0..<20).map { Data("frame-\($0) \(String(repeating: "x", count: $0 * 97))".utf8) }
        for frame in sent { left.send(frame) }

        var received: [Data] = []
        for await frame in right.frames() {
            received.append(frame)
            if received.count == sent.count { break }
        }
        #expect(received == sent)

        left.close()
        right.close()
    }

    @Test func peerCloseFinishesStream() async {
        let (a, b) = socketPair()
        let left = FDFrameChannel(fd: a, peerDescription: "left")
        let right = FDFrameChannel(fd: b, peerDescription: "right")

        left.close()
        #expect(await nextFrame(right) == nil)
        right.close()
    }
}
