import XCTest
@testable import PoseCore

final class ReviewSessionTests: XCTestCase {
    func testDragCoordinatesRoundTripWithPanelOffsetAndZoom() {
        let panel = PixelRect(x: 320, y: 40, width: 600, height: 400)
        let source = Point2(x: 470, y: 140)
        XCTAssertEqual(ReviewCoordinates.display(source, panel: panel, scale: 0.5), Point2(x: 75, y: 50))
        XCTAssertEqual(ReviewCoordinates.source(Point2(x: 75, y: 50), panel: panel, scale: 0.5), source)
    }

    func testCorrectionPreservesRawAndOcclusionRemovesUsablePoint() throws {
        let raw = PoseResult(kind: "3D", status: "needsReview", requestRevision: 1, durationSeconds: 0,
                             joints: [JointResult(name: "left_elbow", point: Point2(x: 130, y: 40),
                                                  position3D: [0.1, 0.2, 0.3])], candidateCount: 1)
        let encoder = JSONEncoder(); encoder.outputFormatting = [.sortedKeys]
        let originalRaw = try encoder.encode(raw)
        var frame = ReviewFrame(id: "P01-subject", pairID: "P01", role: "subject", rider: "Test", file: "photo.png",
                                sourceHash: "hash", panel: PixelRect(x: 100, y: 20, width: 200, height: 100),
                                raw3D: raw, raw2D: nil)
        try frame.setJoint("left_elbow", point: Point2(x: 140, y: 50), state: .corrected)
        XCTAssertEqual(frame.reviewedPoint("left_elbow"), Point2(x: 140, y: 50))
        try frame.setJoint("left_elbow", point: nil, state: .occluded)
        XCTAssertNil(frame.reviewedPoint("left_elbow"))
        XCTAssertFalse(frame.hasValidated3D)
        XCTAssertThrowsError(try frame.setJoint("left_elbow", point: Point2(x: 99, y: 30), state: .corrected))
        XCTAssertThrowsError(try frame.setJoint("left_elbow", point: Point2(x: .nan, y: 30), state: .corrected))
        XCTAssertEqual(frame.history.count, 2)
        XCTAssertEqual(try encoder.encode(frame.raw3D!), originalRaw)
        frame.restore("left_elbow", to: nil)
        XCTAssertEqual(frame.displayPoint("left_elbow", kind: "3D"), Point2(x: 130, y: 40))
        XCTAssertNil(frame.reviewedPoint("left_elbow"))
        XCTAssertEqual(frame.history.last?.action, "undo")
    }

    func testSaveReloadAndStaleWriterCannotOverwrite() throws {
        let folder = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
        try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true)
        defer { try? FileManager.default.removeItem(at: folder) }
        let url = folder.appendingPathComponent("review.json")
        var doc = ReviewDocument(manifestHash: "manifest", reportHash: "report", frames: [])
        try ReviewStore.save(doc, to: url, expectedRevision: nil)
        let first = try ReviewStore.load(from: url)
        XCTAssertEqual(first.revision, 0)
        doc.revision = 1
        XCTAssertTrue(doc.downstreamEvidenceStale)
        try ReviewStore.save(doc, to: url, expectedRevision: 0)
        XCTAssertThrowsError(try ReviewStore.save(first, to: url, expectedRevision: 0))
        XCTAssertEqual(try ReviewStore.load(from: url).revision, 1)
        var future = try JSONSerialization.jsonObject(with: Data(contentsOf: url)) as! [String: Any]
        future["schemaVersion"] = 999
        let futureData = try JSONSerialization.data(withJSONObject: future)
        try futureData.write(to: url)
        XCTAssertThrowsError(try ReviewStore.save(doc, to: url, expectedRevision: 1))
        XCTAssertEqual(try Data(contentsOf: url), futureData)
    }
}
