"""
Shared print and export helpers for result viewers (CDP, LPA, NetWeaver).

These match the equivalents the old ArcGIS DevExpress UI offered:
``PrintCurrentTable_ItemClick`` (per-tab print preview),
``PrintCurrentChart_ItemClick`` (chart PNG), and
``ExportToExcel_ItemClick`` (XLSX export, with the multi-tab workbook).
Centralized here so each viewer dialog stays focused on its own layout.
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Any, Iterable, List, Optional

import pandas as pd

from .qt_compat import (
    QFileDialog,
    QMessageBox,
    QPainter,
    QTableView,
    QTextDocument,
    QWidget,
)


@dataclass
class TabExportSpec:
    """One sheet in a multi-tab XLSX export."""

    name: str  # used as the sheet name; sanitized to <= 31 chars and unique
    dataframe: pd.DataFrame


def _sanitize_sheet_name(raw: str, used: set) -> str:
    """Return a valid Excel sheet name (<=31 chars, no [\\/?*:]) that's unique."""
    invalid = set('[]:*?/\\')
    cleaned = "".join(("_" if ch in invalid else ch) for ch in str(raw or "Sheet"))
    cleaned = cleaned.strip()[:31] or "Sheet"
    candidate = cleaned
    counter = 2
    while candidate.lower() in used:
        suffix = f" ({counter})"
        candidate = (cleaned[: 31 - len(suffix)] + suffix)
        counter += 1
    used.add(candidate.lower())
    return candidate


def print_table_view(
    view: QTableView,
    *,
    title: str = "Results",
    parent: Optional[QWidget] = None,
) -> None:
    """Show a print-preview window for *view*'s current contents."""
    if view is None:
        QMessageBox.information(parent, "Print", "There is no table to print.")
        return

    try:
        from qgis.PyQt.QtPrintSupport import QPrinter, QPrintPreviewDialog
    except ImportError:
        from PyQt6.QtPrintSupport import QPrinter, QPrintPreviewDialog  # type: ignore

    model = view.model()
    if model is None:
        QMessageBox.information(parent, "Print", "There is no table to print.")
        return

    column_count = model.columnCount()
    row_count = model.rowCount()
    if row_count == 0 or column_count == 0:
        QMessageBox.information(parent, "Print", "The current table is empty — nothing to print.")
        return

    # Build a simple HTML table from the model's display data.
    headers = []
    for column in range(column_count):
        header_value = model.headerData(column, 1)  # 1 == Qt.Orientation.Horizontal
        headers.append("" if header_value is None else str(header_value))
    body_rows: List[str] = []
    for row in range(row_count):
        cells = []
        for column in range(column_count):
            index = model.index(row, column)
            value = model.data(index)
            if value is None:
                value = ""
            cells.append(f"<td>{_html_escape(str(value))}</td>")
        body_rows.append(f"<tr>{''.join(cells)}</tr>")

    header_html = "".join(f"<th>{_html_escape(h)}</th>" for h in headers)
    html = (
        "<html><head><meta charset='utf-8'><style>"
        "body{font-family:sans-serif;font-size:9pt;}"
        "table{border-collapse:collapse;width:100%;}"
        "th,td{border:1px solid #888;padding:3px 6px;text-align:left;}"
        "th{background:#e2e8f0;}"
        "h2{margin:0 0 6px 0;}"
        "</style></head><body>"
        f"<h2>{_html_escape(title)}</h2>"
        f"<table><thead><tr>{header_html}</tr></thead>"
        f"<tbody>{''.join(body_rows)}</tbody></table>"
        "</body></html>"
    )

    document = QTextDocument()
    document.setHtml(html)

    printer = QPrinter(QPrinter.PrinterMode.HighResolution)
    preview = QPrintPreviewDialog(printer, parent)
    preview.setWindowTitle(f"Print Preview — {title}")
    preview.paintRequested.connect(document.print_)
    preview.resize(900, 700)
    preview.exec()


def export_widget_as_png(
    widget: QWidget,
    *,
    default_name: str = "chart.png",
    parent: Optional[QWidget] = None,
) -> Optional[str]:
    """Save *widget*'s current pixels to a PNG chosen by the user."""
    if widget is None:
        QMessageBox.information(parent, "Export Chart", "There is no chart to export.")
        return None

    file_path, _ = QFileDialog.getSaveFileName(
        parent,
        "Export Chart as PNG",
        default_name,
        "PNG image (*.png)",
    )
    if not file_path:
        return None
    if not file_path.lower().endswith(".png"):
        file_path += ".png"

    pixmap = widget.grab()
    if pixmap.isNull():
        QMessageBox.critical(parent, "Export Chart", "Could not capture the chart pixels.")
        return None

    if not pixmap.save(file_path, "PNG"):
        QMessageBox.critical(parent, "Export Chart", f"Could not save image to:\n{file_path}")
        return None

    QMessageBox.information(parent, "Export Chart", f"Chart exported to:\n{file_path}")
    return file_path


def export_dataframes_to_xlsx(
    specs: Iterable[TabExportSpec],
    *,
    default_name: str = "results.xlsx",
    parent: Optional[QWidget] = None,
) -> Optional[str]:
    """Write *specs* as sheets into a single .xlsx workbook.

    Falls back to pandas' default Excel engine resolution; both
    ``openpyxl`` and ``xlsxwriter`` work here.
    """
    spec_list: List[TabExportSpec] = [
        spec for spec in specs if isinstance(spec.dataframe, pd.DataFrame) and not spec.dataframe.empty
    ]
    if not spec_list:
        QMessageBox.information(
            parent,
            "Export to Excel",
            "None of the current tabs have data to export.",
        )
        return None

    file_path, _ = QFileDialog.getSaveFileName(
        parent,
        "Export Tabs to Excel",
        default_name,
        "Excel workbook (*.xlsx)",
    )
    if not file_path:
        return None
    if not file_path.lower().endswith(".xlsx"):
        file_path += ".xlsx"

    try:
        with pd.ExcelWriter(file_path) as writer:
            used_names: set = set()
            for spec in spec_list:
                sheet_name = _sanitize_sheet_name(spec.name, used_names)
                spec.dataframe.to_excel(writer, sheet_name=sheet_name, index=False)
    except (ImportError, ModuleNotFoundError) as exc:
        QMessageBox.critical(
            parent,
            "Export to Excel",
            "Excel export requires the 'openpyxl' or 'xlsxwriter' package, which is not "
            f"available in this QGIS Python environment.\n\nDetails: {exc}",
        )
        return None
    except Exception as exc:  # noqa: BLE001
        QMessageBox.critical(parent, "Export to Excel", f"Could not export to Excel:\n{exc}")
        return None

    QMessageBox.information(parent, "Export to Excel", f"Exported {len(spec_list)} tab(s) to:\n{file_path}")
    return file_path


def export_dataframe_to_csv(
    dataframe: pd.DataFrame,
    *,
    default_name: str = "results.csv",
    parent: Optional[QWidget] = None,
) -> Optional[str]:
    """Save *dataframe* as a single CSV file."""
    if dataframe is None or dataframe.empty:
        QMessageBox.information(parent, "Export CSV", "The current table is empty — nothing to export.")
        return None

    file_path, _ = QFileDialog.getSaveFileName(
        parent,
        "Export Table to CSV",
        default_name,
        "CSV file (*.csv)",
    )
    if not file_path:
        return None
    if not file_path.lower().endswith(".csv"):
        file_path += ".csv"

    try:
        dataframe.to_csv(file_path, index=False)
    except Exception as exc:  # noqa: BLE001
        QMessageBox.critical(parent, "Export CSV", f"Could not export CSV:\n{exc}")
        return None

    QMessageBox.information(parent, "Export CSV", f"Exported to:\n{file_path}")
    return file_path


def _html_escape(text: str) -> str:
    return (
        text.replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
    )
