# Standard library imports
import json
import os

# Third party imports
import flask
import flask_cors  # type: ignore[import-untyped]
import opengeode
import geode_explicit
import geode_explicit_geosciences
import opengeode_geosciences
from opengeodeweb_back import geode_functions, utils_functions
from opengeodeweb_back.geode_objects.geode_structural_model import GeodeStructuralModel
from opengeodeweb_back.geode_objects.geode_brep import GeodeBRep
from opengeodeweb_back.geode_objects.geode_surface_mesh3d import GeodeSurfaceMesh3D
from opengeodeweb_back.geode_objects.geode_polygonal_surface3d import (
    GeodePolygonalSurface3D,
)
from opengeodeweb_back.geode_objects.geode_triangulated_surface3d import (
    GeodeTriangulatedSurface3D,
)
from opengeodeweb_microservice.schemas import get_schemas_dict
import geode_simplex as simplex

# Local application imports
from . import schemas

schemas_dict = get_schemas_dict(os.path.join(os.path.dirname(__file__), "schemas"))

routes = flask.Blueprint("remesh_routes", __name__)
flask_cors.CORS(routes)


@routes.route(
    schemas_dict["remesh"]["route"], methods=schemas_dict["remesh"]["methods"]
)
def brep_remesh() -> flask.Response:
    utils_functions.validate_request(flask.request, schemas_dict["remesh"])
    params = schemas.Remesh.from_dict(flask.request.get_json())

    model_object = geode_functions.load_geode_object(params.id)
    if not isinstance(model_object, GeodeBRep):
        flask.abort(
            400,
            f"Model with id {params.id} is not a valid model type BRep",
        )
    model = model_object.brep

    constraints = simplex.BRepIsotropicMetricConstraints(model)
    remesh_constraints = flask.request.get_json()
    tmp_file_path = geode_functions.upload_file_path("constraints.json")
    try:
        with open(tmp_file_path, "w") as tmp_file:
            json.dump(remesh_constraints, tmp_file)
        constraints.import_constraints(tmp_file_path)
    finally:
        if os.path.exists(tmp_file_path):
            os.remove(tmp_file_path)

    metric = constraints.build_metric()
    simplex.brep_simplex_remesh(model, metric)

    model_object.builder().set_name(params.name)

    result = utils_functions.generate_native_viewable_and_light_viewable_from_object(
        model_object
    )
    return flask.make_response(result, 200)


@routes.route(
    schemas_dict["explicit"]["route"], methods=schemas_dict["explicit"]["methods"]
)
def brep_to_structural_model() -> flask.Response:
    utils_functions.validate_request(flask.request, schemas_dict["explicit"])
    params = schemas.Explicit.from_dict(flask.request.get_json())
    model_object = geode_functions.load_geode_object(params.id)
    if isinstance(model_object, GeodeStructuralModel):
        geode_structural_model = model_object
    elif isinstance(model_object, GeodeBRep):
        geode_structural_model = GeodeStructuralModel(
            opengeode_geosciences.StructuralModel(model_object.brep)
        )
    else:
        flask.abort(
            400,
            f"Model with id {params.id} is not a valid model type (BRep or StructuralModel)",
        )
    structural_model = geode_structural_model.structural_model
    parameters = geode_explicit.BRepExplicitModelerParameters()
    modeler = geode_explicit_geosciences.StructuralModelExplicitModeler(
        structural_model, parameters
    )
    structural_model_builder = geode_structural_model.builder()

    def _get_meshes(
        element_group: schemas.Fault | schemas.Horizon,
    ) -> list[opengeode.TriangulatedSurface3D]:
        meshes: list[opengeode.TriangulatedSurface3D] = []
        for surface_id in element_group.id:
            surface_object = geode_functions.load_geode_object(surface_id)
            if not isinstance(surface_object, GeodeSurfaceMesh3D):
                flask.abort(
                    400, f"Object with id {surface_id} is not a valid SurfaceMesh3D"
                )
            if isinstance(surface_object, GeodePolygonalSurface3D):
                surface_mesh = surface_object.surface_mesh
                opengeode.triangulate_surface_mesh3D(surface_mesh)
                tri_mesh = opengeode.convert_surface_mesh_into_triangulated_surface3D(
                    surface_mesh
                )
                if tri_mesh:
                    meshes.append(tri_mesh)
            elif isinstance(surface_object, GeodeTriangulatedSurface3D):
                tri_mesh_existing = surface_object.surface_mesh
                if isinstance(tri_mesh_existing, opengeode.TriangulatedSurface3D):
                    meshes.append(tri_mesh_existing)
        return meshes

    for fault_group in params.faults or []:
        meshes = _get_meshes(fault_group)
        created_uuid = modeler.add_fault(meshes)
        structural_model_builder.set_fault_name(created_uuid, fault_group.name)
    for horizon_group in params.horizons or []:
        meshes = _get_meshes(horizon_group)
        created_uuid = modeler.add_horizon(meshes)
        structural_model_builder.set_horizon_name(created_uuid, horizon_group.name)
    modeler.process()
    structural_model_builder.set_name(params.name)
    result = utils_functions.generate_native_viewable_and_light_viewable_from_object(
        geode_structural_model
    )
    return flask.make_response(result, 200)


@routes.route(
    schemas_dict["voi"]["route"],
    methods=schemas_dict["voi"]["methods"],
)
def brep_voi_to_structural_model() -> flask.Response:
    """Convert a BRep into a StructuralModel."""
    utils_functions.validate_request(flask.request, schemas_dict["voi"])
    params = schemas.Voi.from_dict(flask.request.get_json())

    model_object = geode_functions.load_geode_object(params.id)
    if not isinstance(model_object, GeodeBRep):
        flask.abort(
            400,
            f"Model with id {params.id} is not a valid BRep",
        )

    geode_structural_model = GeodeStructuralModel(
        opengeode_geosciences.StructuralModel(model_object.brep)
    )
    geode_structural_model.builder().set_name(params.name)

    result = utils_functions.generate_native_viewable_and_light_viewable_from_object(
        geode_structural_model
    )
    return flask.make_response(result, 200)
