from __future__ import annotations

import traceback
from pathlib import Path
from threading import Event
from typing import Any

from PySide6.QtCore import QObject, Signal

from ..pipeline import analyze_pdf
from ..reporting import write_csv
from .utils import output_base_for_pdf, friendly_exception_text


class AnalyzeWorker(QObject):
    progress = Signal(int, str)
    finished = Signal(object, object, object, object)
    failed = Signal(str)

    def __init__(self, pdf_paths: list[Path], input_root: Path | None, output_root: Path, cancel_event: Event) -> None:
        super().__init__()
        self._pdf_paths = pdf_paths
        self._input_root = input_root
        self._output_root = output_root
        self._cancel_event = cancel_event

    def _display_name(self, pdf: Path) -> str:
        if self._input_root:
            try:
                return str(pdf.relative_to(self._input_root))
            except Exception:
                return pdf.name
        return pdf.name

    def run(self) -> None:
        try:
            n = len(self._pdf_paths)
            results_by_file: dict[Path, list[dict[str, Any]]] = {}
            suspected_by_file: dict[Path, list[int]] = {}
            failures_by_file: dict[Path, str] = {}
            analysis_notes_by_file: dict[Path, str] = {}

            for i, pdf in enumerate(self._pdf_paths, 1):
                name = self._display_name(pdf)

                if self._cancel_event.is_set():
                    break

                base = output_base_for_pdf(pdf, self._input_root)
                per_pdf_out = self._output_root / base
                per_pdf_csv = self._output_root / base.parent / f"{base.name}.csv"
                per_pdf_csv.parent.mkdir(parents=True, exist_ok=True)

                def progress_cb(done_pages: int, total_pages: int) -> None:
                    frac = (i - 1 + (done_pages / max(total_pages, 1))) / max(n, 1)
                    self.progress.emit(int(frac * 100), f"PDF {i}/{n}: {name} — page {done_pages}/{total_pages}")

                try:
                    rows, suspected_pages, failed_pages, canceled = analyze_pdf(
                        pdf,
                        per_pdf_out,
                        verbose=False,
                        progress=progress_cb,
                        should_cancel=self._cancel_event.is_set,
                    )
                except Exception as e:
                    failures_by_file[pdf] = friendly_exception_text(e)
                    self.progress.emit(int(i / max(n, 1) * 100), f"PDF {i}/{n}: {name} — failed")
                    continue

                try:
                    write_csv(rows, per_pdf_csv)
                except Exception as e:
                    failures_by_file[pdf] = "Unable to write CSV: " + friendly_exception_text(e)
                    self.progress.emit(int(i / max(n, 1) * 100), f"PDF {i}/{n}: {name} — CSV failed")
                    continue

                notes: list[str] = []
                if failed_pages:
                    pages_txt = ", ".join(str(p) for p in failed_pages)
                    notes.append(f"Some pages could not be analyzed: {pages_txt}.")
                if canceled:
                    notes.append("This PDF was only partially processed because the run was canceled.")
                if notes:
                    analysis_notes_by_file[pdf] = " ".join(notes)

                results_by_file[pdf] = rows
                suspected_by_file[pdf] = suspected_pages

                status_suffix = "partial" if notes else "done"
                self.progress.emit(int(i / max(n, 1) * 100), f"PDF {i}/{n}: {name} — {status_suffix}")

                if self._cancel_event.is_set():
                    break

            self.finished.emit(results_by_file, suspected_by_file, failures_by_file, analysis_notes_by_file)

        except Exception:
            self.failed.emit(traceback.format_exc())
