# -*- coding: utf-8 -*-
"""GeoStats library status Processing Algorithm."""
from __future__ import annotations

import html
import importlib
import os
import sys
import tempfile

from ._mixins import HelpUrlMixin
from qgis.core import (
    QgsProcessingAlgorithm,
    QgsProcessingOutputHtml,
    QgsProcessingParameterFileDestination,
)

from ..dependencies import (
    MODULES,
    PIP_PACKAGES,
    build_qgis_python_pip_command,
    find_osgeo_shell,
    format_command,
    resolve_qgis_python_executable,
)


from ._icons import algorithm_icon


class GeoStatsLibraryStatusAlgorithm(HelpUrlMixin, QgsProcessingAlgorithm):
    HTML_REPORT = "HTML_REPORT"

    def name(self) -> str:
        return "geostats_library_status"

    def displayName(self) -> str:
        return "GeoStats Library Status"

    def group(self) -> str:
        return "00 | Setup and Diagnostics"

    def groupId(self) -> str:
        return "planx_setup_diagnostics"

    def icon(self):
        return algorithm_icon("geostats_library_status")

    def createInstance(self):
        return GeoStatsLibraryStatusAlgorithm()

    def shortHelpString(self) -> str:
        return (
            "Creates a non-destructive HTML diagnostic report for the fifteen optional "
            "Python libraries used by PlanX GeoStats Lab: numpy, numba, scikit-learn, "
            "libpysal, esda, spreg, mgwr, xgboost, lightgbm, shap, catboost, interpret, "
            "mapie, dice-ml, and tabpfn. Each "
            "package is tested with importlib.import_module and the result (available "
            "or missing, with the exact import error when it fails) is written to a "
            "structured report alongside the active QGIS host executable, the Python "
            "executable resolved for pip, and any detected OSGeo4W shell. No package "
            "is installed, upgraded, or removed by this tool.\n\n"
            "Missing mgwr only blocks Multiscale GWR. Missing spreg only blocks "
            "Spatial Autoregression, Spatial Error Regression, and Spatial Regime "
            "Regression. Missing xgboost, lightgbm, or catboost only blocks the "
            "matching Gradient Boosting engine (the scikit-learn engine still runs). "
            "Missing shap only blocks the three SHAP explanation tools - Permutation "
            "Importance and Partial Dependence still work without it. Missing mapie "
            "only blocks Conformal Prediction Interval. Missing dice-ml only blocks "
            "Counterfactual Explanation. Missing interpret only blocks the two EBM "
            "tools. Missing tabpfn only blocks the two TabPFN tools, which also need "
            "an internet connection the first time they run (see that tool's help for "
            "why). "
            "Most other algorithms run on numpy alone, so a missing optional package "
            "is rarely a reason to stop an analysis already in progress.\n\n"
            "A package that imports successfully is not proof it computes correctly: "
            "numba compiles against a specific NumPy ABI, and a stale numba build can "
            "import without error while silently producing wrong results against a "
            "newer NumPy. After any environment change, re-run a known workflow on the "
            "bundled sample dataset before trusting new output.\n\n"
            "Run this tool first when documenting a QGIS profile or preparing a bug "
            "report. Follow up with Install / Update GeoStats Libraries only after "
            "reviewing which packages are actually missing."
        )

    def initAlgorithm(self, config=None):
        self.addParameter(
            QgsProcessingParameterFileDestination(
                self.HTML_REPORT,
                "Output HTML report",
                fileFilter="HTML files (*.html)",
                optional=True,
            )
        )
        self.addOutput(QgsProcessingOutputHtml("HTML_REPORT_OUT", "GeoStats library status report"))

    def processAlgorithm(self, parameters, context, feedback):
        html_path = self.parameterAsFileOutput(parameters, self.HTML_REPORT, context)
        if not html_path:
            html_path = os.path.join(tempfile.gettempdir(), "planx_geostats_library_status.html")

        statuses = []
        for package, module in MODULES.items():
            available, error = self._import_status(module)
            statuses.append({
                "package": package,
                "module": module,
                "available": available,
                "error": error,
                "role": self._package_role(package),
            })

        missing = [item["package"] for item in statuses if not item["available"]]
        qgis_python = resolve_qgis_python_executable()
        command = self._install_command()
        osgeo_shell = find_osgeo_shell()
        feedback.pushInfo(f"QGIS host application executable: {sys.executable}")
        feedback.pushInfo(f"Python executable selected for pip: {qgis_python or 'not found'}")
        if missing:
            feedback.pushInfo("Missing GeoStats libraries: " + ", ".join(missing))
        else:
            feedback.pushInfo("All checked GeoStats libraries are available.")

        self._write_html(html_path, statuses, missing, command, qgis_python, osgeo_shell)
        return {self.HTML_REPORT: html_path, "HTML_REPORT_OUT": html_path}

    def _install_command(self) -> str:
        try:
            program, args = build_qgis_python_pip_command(list(PIP_PACKAGES))
        except RuntimeError:
            return "Run PlanX GeoStats Lab > 00 | Setup and Diagnostics > Install / Update GeoStats Libraries and use OSGeo Shell mode."
        return format_command(program, args)

    def _package_role(self, package: str) -> str:
        roles = {
            "numpy": "Numerical arrays, matrix operations, and core statistical calculations.",
            "numba": "Compiled numerical kernels used by the PySAL and MGWR stack; stale versions can break imports against newer NumPy.",
            "scikit-learn": "Nearest-neighbor search, clustering support, and standardized multivariate workflows.",
            "libpysal": "Spatial weights, neighborhood graphs, and PySAL-compatible spatial structures.",
            "esda": "Exploratory spatial data analysis statistics such as Moran and Getis-Ord routines.",
            "spreg": "Spatial regression utilities for model-oriented workflows, including Spatial Lag and Spatial Error regression.",
            "mgwr": "Multiscale geographically weighted regression with variable-specific bandwidths.",
            "xgboost": "Gradient Boosting engine option for the Machine Learning and Explainable AI group.",
            "lightgbm": "Gradient Boosting engine option for the Machine Learning and Explainable AI group.",
            "shap": "SHAP feature-attribution engine for the global, spatial-mapping, and local explanation tools.",
            "catboost": "Fourth Gradient Boosting engine option, with native categorical-field handling (ordered boosting).",
            "interpret": "Explainable Boosting Machine (EBM) glass-box regression/classification engine.",
            "mapie": "Conformal prediction engine for distribution-free prediction intervals on any model in the group.",
            "dice-ml": "Counterfactual-explanation engine for the Counterfactual Explanation tool.",
            "tabpfn": "TabPFN zero-shot tabular foundation-model engine; requires an internet connection on first use to fetch model weights.",
        }
        return roles.get(package, "Optional GeoStats support package.")

    def _import_status(self, module: str) -> tuple[bool, str]:
        try:
            importlib.import_module(module)
            return True, ""
        except Exception as exc:
            return False, f"{type(exc).__name__}: {exc}"

    def _write_html(self, path, statuses, missing, command, qgis_python, osgeo_shell):
        rows = []
        for item in statuses:
            state = "Available" if item["available"] else "Missing"
            state_class = "ok" if item["available"] else "missing"
            role = item["role"]
            if item.get("error"):
                role += f" Import check failed: {item['error']}"
            rows.append(
                "<tr>"
                f"<td>{html.escape(item['package'])}</td>"
                f"<td>{html.escape(item['module'])}</td>"
                f"<td class=\"{state_class}\">{state}</td>"
                f"<td>{html.escape(role)}</td>"
                "</tr>"
            )

        if missing:
            summary = (
                "Missing optional libraries: "
                + html.escape(", ".join(missing))
                + ". Use PlanX GeoStats Lab > 00 | Setup and Diagnostics > Install / Update GeoStats Libraries for guided installation."
            )
        else:
            summary = "All checked optional GeoStats libraries are available in the active QGIS Python environment."

        content = f"""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>PlanX GeoStats Lab Library Status</title>
<style>
body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: #243040; background: #f6f8fb; margin: 0; padding: 24px; }}
.container {{ max-width: 980px; margin: 0 auto; background: #fff; border: 1px solid #d9e2ec; border-radius: 8px; padding: 28px; }}
h1 {{ margin: 0 0 8px; font-size: 1.65rem; }}
.subtitle {{ color: #64748b; margin: 0 0 22px; }}
.summary {{ background: #eef7f3; border-left: 5px solid #2f855a; padding: 14px 18px; margin: 20px 0; }}
.command {{ background: #111827; color: #f9fafb; padding: 14px 16px; border-radius: 6px; font-family: Consolas, monospace; overflow-x: auto; }}
table {{ width: 100%; border-collapse: collapse; margin-top: 18px; }}
th, td {{ border-bottom: 1px solid #edf2f7; padding: 10px; text-align: left; vertical-align: top; font-size: .9rem; }}
th {{ background: #ebf4ff; color: #24527a; text-transform: uppercase; font-size: .72rem; letter-spacing: .05em; }}
.ok {{ color: #2f855a; font-weight: 700; }}
.missing {{ color: #c2410c; font-weight: 700; }}
</style>
</head>
<body>
<div class="container">
<h1>GeoStats Library Status</h1>
<p class="subtitle">QGIS host application executable: <strong>{html.escape(sys.executable)}</strong><br>
Python executable selected for pip: <strong>{html.escape(qgis_python or 'Not found')}</strong></p>
<div class="summary">{summary}</div>
<h2>How to install the missing libraries</h2>
<p><strong>Recommended path:</strong> run <strong>PlanX GeoStats Lab &gt; 00 | Setup and Diagnostics &gt; Install / Update GeoStats Libraries</strong>, review the command preview, enable the approval checkbox, and run the tool. This runs pip only after your confirmation and streams the install log inside QGIS.</p>
<p><strong>Status check:</strong> run <strong>PlanX GeoStats Lab &gt; 00 | Setup and Diagnostics &gt; GeoStats Library Status</strong> first when you only need a non-installing environment report.</p>
<p><strong>Manual path:</strong> copy the command below into OSGeo Shell or a terminal that belongs to the same QGIS installation. Do not use the QGIS application executable directly with <code>-m pip</code>; pip must be run by Python.</p>
<p>Detected OSGeo Shell: <strong>{html.escape(osgeo_shell or 'Not found')}</strong></p>
<div class="command">{html.escape(command)}</div>
<h2>Package status</h2>
<table>
<thead><tr><th>Package</th><th>Import module</th><th>Status</th><th>GeoStats role</th></tr></thead>
<tbody>{''.join(rows)}</tbody>
</table>
</div>
</body>
</html>"""
        with open(path, "w", encoding="utf-8") as handle:
            handle.write(content)
