import Foundation
import Testing

@testable import NucleicCore

/// CARBON_SHARDING §16 "Placement": pure-function table tests — pinned always included,
/// winners stable under irrelevant roster changes, minimal reshuffle on join/leave, honest
/// degradation on tiny meshes.
@Suite("Carbon placement")
struct CarbonPlacementTests {
    private let projectUUID = UUID(uuidString: "77E1A2B3-C4D5-6789-ABCD-EF0123456789")!

    private func member(
        _ id: String, mirror: Bool = true, key: Bool = true, pinned: Bool = false
    ) -> CarbonPlacement.Member {
        .init(hostID: id, canMirrorCarbon: mirror, holdsProjectKey: key, alwaysOn: pinned)
    }

    @Test func pinnedMembersAreAlwaysIncluded() {
        let members = [
            member("runner-1", pinned: true),
            member("mac-A"), member("mac-B"), member("mac-C"), member("mac-D"),
        ]
        let set = CarbonPlacement.replicaSet(
            projectUUID: projectUUID, members: members, ownerHostID: "owner")
        #expect(set.contains("runner-1"))
        // Pinned rides *on top of* the R rendezvous winners.
        #expect(set.count == 3)
    }

    @Test func ownerIsNeverInItsOwnReplicaSet() {
        let members = [member("owner", pinned: true), member("mac-A"), member("mac-B")]
        let set = CarbonPlacement.replicaSet(
            projectUUID: projectUUID, members: members, ownerHostID: "owner")
        #expect(!set.contains("owner"))
    }

    @Test func phonesAndKeylessMembersAreExcluded() {
        let members = [
            member("phone", mirror: false),
            member("keyless", key: false),
            member("mac-A"),
        ]
        let set = CarbonPlacement.replicaSet(
            projectUUID: projectUUID, members: members, ownerHostID: "owner")
        #expect(set == ["mac-A"])
    }

    @Test func deterministicAndOrderIndependent() {
        let members = [member("mac-A"), member("mac-B"), member("mac-C"), member("runner-1", pinned: true)]
        let a = CarbonPlacement.replicaSet(
            projectUUID: projectUUID, members: members, ownerHostID: "owner")
        let b = CarbonPlacement.replicaSet(
            projectUUID: projectUUID, members: members.reversed(), ownerHostID: "owner")
        #expect(a == b)
    }

    @Test func irrelevantRosterChangesDontReshuffle() {
        let members = [member("mac-A"), member("mac-B"), member("mac-C")]
        let before = CarbonPlacement.replicaSet(
            projectUUID: projectUUID, members: members, ownerHostID: "owner")
        // An ineligible member joining must not disturb the winners.
        let after = CarbonPlacement.replicaSet(
            projectUUID: projectUUID, members: members + [member("phone", mirror: false)],
            ownerHostID: "owner")
        #expect(before == after)
    }

    @Test func joinReshufflesMinimally() {
        let members = [member("mac-A"), member("mac-B"), member("mac-C"), member("mac-D")]
        let before = CarbonPlacement.replicaSet(
            projectUUID: projectUUID, members: members, ownerHostID: "owner")
        let after = CarbonPlacement.replicaSet(
            projectUUID: projectUUID, members: members + [member("mac-E")], ownerHostID: "owner")
        // Rendezvous property: the newcomer either wins a slot (displacing exactly one
        // incumbent) or changes nothing. Never a full reshuffle.
        #expect(before.subtracting(after).count <= 1)
        #expect(after.subtracting(before).isEmpty || after.subtracting(before) == ["mac-E"])
    }

    @Test func leaveReshufflesMinimally() {
        let members = [member("mac-A"), member("mac-B"), member("mac-C"), member("mac-D")]
        let before = CarbonPlacement.replicaSet(
            projectUUID: projectUUID, members: members, ownerHostID: "owner")
        let departed = before.first!
        let after = CarbonPlacement.replicaSet(
            projectUUID: projectUUID, members: members.filter { $0.hostID != departed },
            ownerHostID: "owner")
        // The survivor keeps its slot; exactly one replacement is promoted.
        #expect(after.count == 2)
        #expect(before.subtracting([departed]).isSubset(of: after))
    }

    @Test func smallMeshesDegradeInsteadOfBlocking() {
        // Mesh of one: empty set — the caller warns ("not protected"), never blocks (D3).
        #expect(
            CarbonPlacement.replicaSet(projectUUID: projectUUID, members: [], ownerHostID: "owner")
                .isEmpty)
        // One eligible member: it is the whole set ("reduced redundancy").
        #expect(
            CarbonPlacement.replicaSet(
                projectUUID: projectUUID, members: [member("mac-A")], ownerHostID: "owner")
                == ["mac-A"])
    }

    @Test func differentProjectsPlaceIndependently() {
        // With enough members, at least one of a handful of projects lands on a different
        // set — the rendezvous hash keys on the project UUID.
        let members = (0..<8).map { member("mac-\($0)") }
        let sets = (0..<8).map { i in
            CarbonPlacement.replicaSet(
                projectUUID: UUID(uuidString: String(
                    format: "00000000-0000-4000-8000-%012d", i))!,
                members: members, ownerHostID: "owner")
        }
        #expect(Set(sets).count > 1)
    }

    @Test func scoreIsPureAndStable() {
        let a = CarbonPlacement.score(projectUUID: projectUUID, hostID: "mac-A")
        let b = CarbonPlacement.score(projectUUID: projectUUID, hostID: "mac-A")
        #expect(a == b)
        #expect(a.count == 32)
        #expect(a != CarbonPlacement.score(projectUUID: projectUUID, hostID: "mac-B"))
    }
}
