import html
import json
import os
import subprocess
import traceback
from pathlib import Path

from CityForge.reconstruct import JsonConstructor
from CityForge.toolbelt.log_handler import PlgLogger
from CityForge.tr import Translatable


def log_failed_subprocess(
    result: subprocess.CompletedProcess | None,
    message: str,
    args: list[str],
    exception: Exception | None = None,
) -> None:
    PlgLogger.log(
        message,
        log_level=2,
        push=True,
    )
    if result:
        PlgLogger.log(
            f"Call arguments: {' '.join(result.args)}",
            log_level=2,
            push=True,
        )
        PlgLogger.log(
            f"Exit code: {result.returncode}",
            log_level=2,
            push=True,
        )
        PlgLogger.log(
            "stdout: " + html.escape(result.stdout),
            log_level=2,
            push=True,
        )
        PlgLogger.log(
            "stderr: " + html.escape(result.stderr),
            log_level=2,
            push=True,
        )
    else:
        PlgLogger.log(
            f"Call arguments: {' '.join(args)}",
            log_level=2,
            push=True,
        )

    if exception:
        PlgLogger.log(
            "Exception: " + html.escape(str(exception)), log_level=2, push=True
        )
        PlgLogger.log(
            html.escape("".join(traceback.format_exception(exception))),
            log_level=2,
            push=True,
        )


class InvalidConfigurationError(Exception):
    """Error that come from the user configuration.
    The user should be able to fix it from the documentation.
    """

    pass


class GeoflowError(Exception):
    """Error that is unknown, might be a bug in CityForge, or not."""

    pass


class ConfigurationWarning(Warning):
    """A warning, that is not critical."""

    pass


class CheckInstall(Translatable):

    def check_docker(self, docker_path: str) -> None:
        container_name = "ignfab/lod22-reconstruct-batch"

        PlgLogger.log("Checking available Docker images...", log_level=0, push=True)
        result = None
        args = [docker_path, "images"]
        try:
            try:
                result = subprocess.run(args, capture_output=True, text=True)
            except (FileNotFoundError, PermissionError) as e:
                raise InvalidConfigurationError(
                    self.tr("Docker could not be executed at {}.").format(docker_path)
                    + " "
                    + self.tr("Check that the path is correct in your settings.")
                ) from e
            if container_name not in result.stdout:
                raise InvalidConfigurationError(
                    self.tr("The Docker image {} does not exist.").format(
                        container_name
                    )
                    + " "
                    + self.tr("Check that the Docker images have been built.")
                )
        except Exception as e:
            log_failed_subprocess(
                result,
                "Docker image {} could not be found".format(container_name),
                args,
                e,
            )
            raise e

        PlgLogger.log("Checking geoflow help...", log_level=0, push=True)
        result = None
        try:
            args = [docker_path, "run", "--rm", container_name, "--help"]
            result = subprocess.run(
                args,
                capture_output=True,
                text=True,
            )
            if "/usr/local/bin/geof <flowchart_file>" not in result.stdout:
                raise InvalidConfigurationError(
                    self.tr("The Docker image {} did not run correctly.").format(
                        container_name
                    )
                    + " "
                    + self.tr(
                        "Check that the Docker images have been built using the latest version."
                    )
                )
        except Exception as e:
            log_failed_subprocess(
                result,
                "The Docker image {} did not run correctly.".format(container_name),
                args,
            )
            raise e

    def check_geof(self, geof_path: str) -> None:
        PlgLogger.log("Checking geoflow help...", log_level=0, push=True)
        result = None
        args = [geof_path, "--help"]
        try:
            try:
                result = subprocess.run(
                    args,
                    capture_output=True,
                    text=True,
                )
                print(f"#####{args, result}#####")
                if " <flowchart_file>" not in result.stdout:
                    raise InvalidConfigurationError(
                        self.tr("Geoflow did not run correctly.")
                        + " "
                        + self.tr(
                            "Check that the executable have been installed at the latest version."
                        )
                    )
            except (FileNotFoundError, PermissionError) as e:
                raise InvalidConfigurationError(
                    self.tr("Geof could not be executed at {}.").format(geof_path)
                    + " "
                    + self.tr("Check that the path is correct in your settings.")
                ) from e
        except Exception as e:
            log_failed_subprocess(result, "Geoflow did not run correctly.", args, e)
            raise e

    def check_reconstruction(
        self, geoflow_install: str, docker_path: str, geof_path: str
    ) -> None:
        reconstruct = JsonConstructor()
        resources = Path(__file__).parent.parent / "resources" / "test_data"
        local_resources = Path.home() / ".cityforge"
        local_resources.mkdir(exist_ok=True)
        json_file = local_resources / "model.json"

        if json_file.exists():
            os.remove(json_file)
        print(f"######################{resources}/footprint.gpkg")
        PlgLogger.log("Running sample reconstruction...", log_level=0, push=True)
        result = reconstruct.reconstruct(
            resources / "footprint.gpkg",
            resources / "pointcloud.laz",
            local_resources,
            None,
            "2154",
            geoflow_install,
            docker_path,
            geof_path,
            True,
            True,
        )

        PlgLogger.log(
            "Reconstruction ended, checking it succeeded...", log_level=0, push=True
        )

        if result.returncode != 0:
            log_failed_subprocess(
                result, "Geoflow was not able to process test data.", []
            )
            raise GeoflowError(
                self.tr(
                    "Geoflow was not able to process test data for an unknown reason."
                )
            )

        PlgLogger.log("Checking generated file...", log_level=0, push=True)

        json_file = local_resources / "model.json"
        if not json_file.exists():
            log_failed_subprocess(
                result,
                "Geoflow did not output any data, or the file is not readable",
                [],
            )
            raise GeoflowError(
                self.tr("Geoflow did not output any data, or the file is not readable")
            )

        try:
            with open(json_file, "r") as f:
                cityjson = json.load(f)
        except Exception as e:
            log_failed_subprocess(
                result,
                "Generated file with is not readable or not valid JSON",
                [],
                e,
            )
            raise GeoflowError(
                self.tr("Generated file with is not readable or not valid JSON")
            ) from e

        if (
            "CityObjects" not in cityjson
            or "1" not in cityjson["CityObjects"]
            or "1-0" not in cityjson["CityObjects"]["1"]["children"]
            or "1-0" not in cityjson["CityObjects"]
            or "2.2"
            not in [x["lod"] for x in cityjson["CityObjects"]["1-0"]["geometry"]]
        ):
            log_failed_subprocess(
                result,
                "Generated file with test data is incorrect: missing geometries",
                [],
            )
            raise GeoflowError(
                self.tr(
                    "Generated file with test data is incorrect: missing geometries"
                )
            )

        try:
            with open(json_file, "a") as f:
                f.write("")
        except Exception as e:
            raise ConfigurationWarning(
                self.tr("The generated file is not writable.")
                + " "
                + self.tr(
                    "You will be able to correctly generate the CityJSON files, but not filter LOD 2.2."
                )
                + " "
                + self.tr(
                    "This is a limitation and should be fixed in upcoming releases."
                )
            ) from e
