import json

import FreeCAD as App
import Part

from freecad_mutation_evidence import capture_property_mutation

COMMIT = "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d"


def feature_status(case_id, feature, expected):
    shape = feature.Shape
    shape_null = bool(shape.isNull())
    state = [str(value) for value in feature.State]
    try:
        status_string = str(feature.getStatusString())
    except Exception:
        status_string = ""
    shape_valid = bool(shape.isValid()) if not shape_null else False
    observed = "success" if not shape_null and shape_valid else "rejected" if "Invalid" in state or status_string != "Valid" else "accepted-empty"
    return {
        "id": case_id,
        "typeId": feature.TypeId,
        "expected": expected,
        "observed": observed,
        "passed": observed == expected,
        "shapeNull": shape_null,
        "shapeValid": shape_valid,
        "shapeType": shape.ShapeType if not shape_null else "Null",
        "solids": len(shape.Solids) if not shape_null else 0,
        "faces": len(shape.Faces) if not shape_null else 0,
        "edges": len(shape.Edges) if not shape_null else 0,
        "vertices": len(shape.Vertexes) if not shape_null else 0,
        "volume": float(shape.Volume) if not shape_null else 0.0,
        "area": float(shape.Area) if not shape_null else 0.0,
        "state": state,
        "statusString": status_string,
    }


def add_shape(document, name, shape):
    feature = document.addObject("Part::Feature", name)
    feature.Shape = shape
    return feature


def rectangle_wire(x_min, y_min, x_max, y_max, z=0):
    points = [
        App.Vector(x_min, y_min, z),
        App.Vector(x_max, y_min, z),
        App.Vector(x_max, y_max, z),
        App.Vector(x_min, y_max, z),
        App.Vector(x_min, y_min, z),
    ]
    return Part.makePolygon(points)


def success_extrusion():
    document = App.newDocument("PartExtrusionSuccessOracle")
    try:
        profile = add_shape(document, "Profile", rectangle_wire(0, 0, 3, 2))
        feature = document.addObject("Part::Extrusion", "Extrusion")
        feature.Base = profile
        feature.Dir = App.Vector(0, 0, 5)
        feature.Solid = True
        mutation = capture_property_mutation(document, feature, "Dir", App.Vector(0, 0, 7))
        result = feature_status("part-extrusion-success", feature, "success")
        result["mutation"] = mutation
        return result
    finally:
        App.closeDocument(document.Name)


def success_revolution():
    document = App.newDocument("PartRevolutionSuccessOracle")
    try:
        profile = add_shape(document, "Profile", rectangle_wire(2, 0, 4, 2))
        feature = document.addObject("Part::Revolution", "Revolution")
        feature.Source = profile
        feature.Axis = App.Vector(0, 1, 0)
        feature.Base = App.Vector(0, 0, 0)
        feature.Angle = 360
        feature.Solid = True
        mutation = capture_property_mutation(document, feature, "Angle", 270.0)
        result = feature_status("part-revolution-success", feature, "success")
        result["mutation"] = mutation
        return result
    finally:
        App.closeDocument(document.Name)


def success_loft():
    document = App.newDocument("PartLoftSuccessOracle")
    try:
        first = add_shape(document, "SectionA", rectangle_wire(-2, -2, 2, 2, 0))
        second = add_shape(document, "SectionB", rectangle_wire(-1, -1, 1, 1, 5))
        feature = document.addObject("Part::Loft", "Loft")
        feature.Sections = [first, second]
        feature.Solid = True
        feature.Ruled = False
        feature.Closed = False
        mutation = capture_property_mutation(document, feature, "Ruled", True)
        result = feature_status("part-loft-success", feature, "success")
        result["mutation"] = mutation
        return result
    finally:
        App.closeDocument(document.Name)


def success_sweep():
    document = App.newDocument("PartSweepSuccessOracle")
    try:
        section = add_shape(document, "Section", rectangle_wire(-1, -1, 1, 1, 0))
        edited_section = add_shape(document, "EditedSection", rectangle_wire(-0.75, -0.75, 0.75, 0.75, 0))
        spine = add_shape(document, "Spine", Part.makePolygon([App.Vector(0, 0, 0), App.Vector(0, 0, 6)]))
        feature = document.addObject("Part::Sweep", "Sweep")
        feature.Sections = [section]
        feature.Spine = spine
        feature.Solid = True
        feature.Frenet = False
        mutation = capture_property_mutation(document, feature, "Sections", [edited_section])
        result = feature_status("part-sweep-success", feature, "success")
        result["mutation"] = mutation
        return result
    finally:
        App.closeDocument(document.Name)


def success_fillet():
    document = App.newDocument("PartFilletSuccessOracle")
    try:
        base = add_shape(document, "Base", Part.makeBox(10, 8, 6))
        feature = document.addObject("Part::Fillet", "Fillet")
        feature.Base = base
        feature.Edges = [(1, 1, 1)]
        mutation = capture_property_mutation(document, feature, "Edges", [(1, 0.5, 0.5)])
        result = feature_status("part-fillet-success", feature, "success")
        result["mutation"] = mutation
        return result
    finally:
        App.closeDocument(document.Name)


def success_chamfer():
    document = App.newDocument("PartChamferSuccessOracle")
    try:
        base = add_shape(document, "Base", Part.makeBox(10, 8, 6))
        feature = document.addObject("Part::Chamfer", "Chamfer")
        feature.Base = base
        feature.Edges = [(1, 1, 1)]
        mutation = capture_property_mutation(document, feature, "Edges", [(1, 0.5, 0.5)])
        result = feature_status("part-chamfer-success", feature, "success")
        result["mutation"] = mutation
        return result
    finally:
        App.closeDocument(document.Name)


def rejected_case(type_id):
    name = type_id.split("::", 1)[1]
    document = App.newDocument("Part{}FailureOracle".format(name))
    try:
        feature = document.addObject(type_id, name)
        document.recompute()
        result = feature_status("part-{}-missing-input".format(name.lower()), feature, "rejected")
        result["inputClass"] = "missing-required-input"
        return result
    except Exception as error:
        return {
            "id": "part-{}-missing-input".format(name.lower()),
            "typeId": type_id,
            "inputClass": "missing-required-input",
            "expected": "rejected",
            "observed": "rejected",
            "passed": True,
            "errorType": type(error).__name__,
            "error": str(error),
        }
    finally:
        App.closeDocument(document.Name)


success_cases = [
    success_extrusion(),
    success_revolution(),
    success_loft(),
    success_sweep(),
    success_fillet(),
    success_chamfer(),
]
failure_cases = [rejected_case(type_id) for type_id in [
    "Part::Extrusion",
    "Part::Revolution",
    "Part::Loft",
    "Part::Sweep",
    "Part::Fillet",
    "Part::Chamfer",
]]
all_cases = success_cases + failure_cases
report = {
    "schemaVersion": 1,
    "baselineId": "freecad-1.1.1-part-builders-oracle",
    "freecadVersion": ".".join(App.Version()[0:3]),
    "gitCommit": COMMIT,
    "status": "pass" if all(case.get("passed") for case in all_cases) else "failed",
    "summary": {
        "successCases": len(success_cases),
        "successPassed": sum(1 for case in success_cases if case.get("passed")),
        "failureCases": len(failure_cases),
        "failurePassed": sum(1 for case in failure_cases if case.get("passed")),
        "rejected": sum(1 for case in failure_cases if case.get("observed") == "rejected"),
        "acceptedEmpty": sum(1 for case in failure_cases if case.get("observed") == "accepted-empty"),
    },
    "successCases": success_cases,
    "failureCases": failure_cases,
}
print("FREECAD_PART_BUILDERS_ORACLE_RESULT=" + json.dumps(report, sort_keys=True))
