import XCTest
@testable import PoseCore

/// Source-only checks. Passing these does not approve perspective, utility, or coaching.
final class LeanFeasibilityTests: XCTestCase {
    private struct Pass: Decodable {
        let protocolName: String
        let passID: String
        let annotator: String
        let earlierValuesHidden: Bool
        let computedOrderingHidden: Bool
        let modelProposalsHidden: Bool
        let manifestHash: String
        let annotations: [Annotation]
        enum CodingKeys: String, CodingKey {
            case protocolName = "protocol"
            case passID, annotator, earlierValuesHidden, computedOrderingHidden, modelProposalsHidden, manifestHash, annotations
        }
    }
    private struct Annotation: Decodable {
        let frameID: String
        let file: String
        let sourceHash: String
        let panel: PixelRect
        let endpoints: [Point2]
        let uncertaintyPixels: Double
        let cue: String
    }
    private func sourcePass() throws -> (URL, Pass) {
        let env = ProcessInfo.processInfo.environment
        guard let path = env["POSE_PRIVATE_MANIFEST"] else {
            throw XCTSkip("POSE_PRIVATE_MANIFEST absent; private lean source check not run.")
        }
        guard let pass = env["POSE_LEAN_PASS_1"] else {
            throw XCTSkip("POSE_LEAN_PASS_1 absent; source annotation evidence not supplied.")
        }
        return (URL(fileURLWithPath: path), try JSONDecoder().decode(Pass.self, from: Data(contentsOf: URL(fileURLWithPath: pass))))
    }
    private func validate(_ pass: Pass, manifestURL: URL) throws {
        XCTAssertEqual(pass.protocolName, "lean-source-v1")
        XCTAssertFalse(pass.passID.isEmpty); XCTAssertFalse(pass.annotator.isEmpty)
        XCTAssertTrue(pass.earlierValuesHidden); XCTAssertTrue(pass.computedOrderingHidden); XCTAssertTrue(pass.modelProposalsHidden)
        XCTAssertEqual(pass.manifestHash, ReviewStore.digest(try Data(contentsOf: manifestURL)))
        let manifest = try SessionManifest.load(url: manifestURL)
        let expected = Set(["P05-subject", "P05-reference", "P06-subject", "P06-reference"])
        XCTAssertEqual(Set(pass.annotations.map(\.frameID)), expected)
        XCTAssertEqual(pass.annotations.count, expected.count)
        for annotation in pass.annotations {
            let pair = try XCTUnwrap(manifest.pairs.first { pair in pair.riders.contains { "\(pair.id)-\($0.role)" == annotation.frameID } })
            let rider = try XCTUnwrap(pair.riders.first { "\(pair.id)-\($0.role)" == annotation.frameID })
            let image = try ImageInput.load(url: manifest.assetURL(for: pair, rider: rider, manifestURL: manifestURL))
            XCTAssertEqual(annotation.sourceHash, image.sha256)
            XCTAssertEqual(annotation.file, rider.file ?? pair.file)
            XCTAssertEqual(annotation.panel, rider.panel, "Annotations bind to the full panel, not the human crop")
            try annotation.panel.validate(imageWidth: image.image.width, imageHeight: image.image.height)
            XCTAssertTrue(annotation.uncertaintyPixels.isFinite && annotation.uncertaintyPixels > 0)
            XCTAssertEqual(annotation.cue, "visible-front-tire-long-axis")
            guard annotation.endpoints.count == 2 else { XCTFail("Two endpoints required"); continue }
            let frame = ReviewFrame(id: annotation.frameID, pairID: pair.id, role: rider.role, rider: rider.rider,
                file: annotation.file, sourceHash: image.sha256, panel: rider.panel, raw3D: nil, raw2D: nil)
            XCTAssertTrue(annotation.endpoints.allSatisfy { frame.contains($0) })
            XCTAssertGreaterThan(distance(annotation.endpoints[0], annotation.endpoints[1]), 2 * annotation.uncertaintyPixels)
        }
    }
    func testPrivateSourceBindings() throws {
        let (url, pass) = try sourcePass()
        try validate(pass, manifestURL: url)
    }
    func testPrivateIndependentEndpointAgreement() throws {
        let (url, first) = try sourcePass()
        guard let path = ProcessInfo.processInfo.environment["POSE_LEAN_PASS_2"] else {
            throw XCTSkip("Second independent source pass absent; U1 repeatability remains pending.")
        }
        let second = try JSONDecoder().decode(Pass.self, from: Data(contentsOf: URL(fileURLWithPath: path)))
        try validate(first, manifestURL: url); try validate(second, manifestURL: url)
        XCTAssertNotEqual(first.passID, second.passID)
        for a in first.annotations {
            let b = try XCTUnwrap(second.annotations.first { $0.frameID == a.frameID })
            guard a.endpoints.count == 2, b.endpoints.count == 2 else { continue }
            let direct = max(distance(a.endpoints[0], b.endpoints[0]), distance(a.endpoints[1], b.endpoints[1]))
            let reversed = max(distance(a.endpoints[0], b.endpoints[1]), distance(a.endpoints[1], b.endpoints[0]))
            XCTAssertLessThanOrEqual(min(direct, reversed), a.uncertaintyPixels + b.uncertaintyPixels,
                "Endpoint uncertainty disks do not overlap; review disagreement before accepting this cue")
        }
        // Independence is an externally reviewed procedure, not something pass IDs can prove.
    }
    private func distance(_ a: Point2, _ b: Point2) -> Double { hypot(a.x - b.x, a.y - b.y) }
}
