"""Export problematic geography live-report samples into a dedicated folder."""

from __future__ import annotations

from collections import defaultdict
from dataclasses import asdict, dataclass
import json
from pathlib import Path
import time
from typing import Any, Mapping, Sequence

from paper_fetch.config import build_runtime_env, resolve_repo_root
from paper_fetch.http import HttpTransport
from paper_fetch.models import RenderOptions
from paper_fetch.runtime import RuntimeContext
from paper_fetch.service import FetchStrategy, PaperFetchFailure, fetch_paper
from paper_fetch.utils import normalize_text, sanitize_filename

from .live import GeographySample, build_report_result


@dataclass(frozen=True)
class GeographyIssueExportEntry:
    provider: str
    doi: str
    title: str
    issue_flags: list[str]
    output_dir: str
    export_status: str
    elapsed_seconds: float
    exported_files: list[str]
    current_status: str | None = None
    current_source: str | None = None
    current_content_kind: str | None = None
    current_issue_flags: list[str] | None = None
    error_code: str | None = None
    error_message: str | None = None

    def to_dict(self) -> dict[str, Any]:
        return asdict(self)


def default_issue_artifact_output_dir() -> Path:
    return resolve_repo_root() / "live-downloads" / "geography-issue-artifacts"


def load_report_payload(report_json_path: Path) -> dict[str, Any]:
    return json.loads(report_json_path.read_text(encoding="utf-8"))


def collect_issue_rows(
    report_payload: Mapping[str, Any],
    *,
    issue_flags: Sequence[str] | None = None,
    limit: int | None = None,
) -> list[dict[str, Any]]:
    wanted_flags = {normalize_text(item) for item in (issue_flags or []) if normalize_text(item)}
    rows: list[dict[str, Any]] = []
    for raw_row in report_payload.get("results", []):
        row = dict(raw_row)
        row_flags = [normalize_text(item) for item in row.get("issue_flags", []) if normalize_text(item)]
        if not row_flags:
            continue
        if wanted_flags and not wanted_flags.intersection(row_flags):
            continue
        row["issue_flags"] = row_flags
        rows.append(row)
        if limit is not None and len(rows) >= limit:
            break
    return rows


def schedule_issue_rows(
    rows: Sequence[Mapping[str, Any]],
    *,
    providers: Sequence[str] | None = None,
) -> list[dict[str, Any]]:
    provider_order = list(providers or [])
    grouped: dict[str, list[dict[str, Any]]] = defaultdict(list)

    for raw_row in rows:
        row = dict(raw_row)
        provider = normalize_text(row.get("provider"))
        if not provider:
            continue
        grouped[provider].append(row)
        if provider not in provider_order:
            provider_order.append(provider)

    scheduled: list[dict[str, Any]] = []
    max_rows = max((len(grouped.get(provider, [])) for provider in provider_order), default=0)
    for row_index in range(max_rows):
        for provider in provider_order:
            provider_rows = grouped.get(provider, [])
            if row_index < len(provider_rows):
                scheduled.append(provider_rows[row_index])
    return scheduled


def export_geography_issue_artifacts(
    *,
    report_json_path: Path,
    output_dir: Path | None = None,
    issue_flags: Sequence[str] | None = None,
    limit: int | None = None,
    env: Mapping[str, str] | None = None,
    transport: HttpTransport | None = None,
) -> dict[str, Any]:
    report_payload = load_report_payload(report_json_path)
    selected_rows = collect_issue_rows(report_payload, issue_flags=issue_flags, limit=limit)
    providers = [normalize_text(item) for item in report_payload.get("providers", []) if normalize_text(item)]
    scheduled_rows = schedule_issue_rows(selected_rows, providers=providers)
    export_root = Path(output_dir or default_issue_artifact_output_dir())
    export_root.mkdir(parents=True, exist_ok=True)

    active_env = build_runtime_env(env)
    active_transport = transport if transport is not None else HttpTransport()

    entries: list[GeographyIssueExportEntry] = []
    started_at = time.monotonic()

    for row in scheduled_rows:
        entry = export_issue_row(row=row, output_root=export_root, env=active_env, transport=active_transport)
        entries.append(entry)

    summary = {
        "generated_from_report": str(report_json_path),
        "output_dir": str(export_root),
        "selected_issue_flags": sorted({flag for row in scheduled_rows for flag in row.get("issue_flags", [])}),
        "requested_issue_flags": [normalize_text(item) for item in (issue_flags or []) if normalize_text(item)],
        "total_selected": len(scheduled_rows),
        "exported": sum(1 for entry in entries if entry.export_status == "exported"),
        "failed": sum(1 for entry in entries if entry.export_status != "exported"),
        "elapsed_seconds": round(time.monotonic() - started_at, 3),
        "entries": [entry.to_dict() for entry in entries],
    }
    (export_root / "index.json").write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    return summary


def export_issue_row(
    *,
    row: Mapping[str, Any],
    output_root: Path,
    env: Mapping[str, str],
    transport: HttpTransport,
) -> GeographyIssueExportEntry:
    doi = normalize_text(row.get("doi")) or "unknown-doi"
    provider = normalize_text(row.get("provider")) or "unknown-provider"
    title = normalize_text(row.get("title")) or doi
    flags = [normalize_text(item) for item in row.get("issue_flags", []) if normalize_text(item)]
    entry_dir = output_root / sanitize_filename(doi)
    entry_dir.mkdir(parents=True, exist_ok=True)
    (entry_dir / "original-issue-row.json").write_text(json.dumps(dict(row), ensure_ascii=False, indent=2) + "\n", encoding="utf-8")

    started_at = time.monotonic()
    context = RuntimeContext(env=env, transport=transport, download_dir=entry_dir)
    try:
        envelope = fetch_paper(
            doi,
            modes={"article", "markdown"},
            strategy=FetchStrategy(
                allow_metadata_only_fallback=True,
            ),
            render=RenderOptions(include_refs="all", asset_profile="none", max_tokens="full_text"),
            context=context,
        )
        elapsed_seconds = round(time.monotonic() - started_at, 3)
        sample = GeographySample(
            provider=provider,
            doi=doi,
            title=title,
            landing_url="",
            topic_tags=tuple(),
            year=0,
            seed_level=0,
        )
        current_result = build_report_result(sample, envelope, elapsed_seconds=elapsed_seconds)
        (entry_dir / "fetch-envelope.json").write_text(envelope.to_json() + "\n", encoding="utf-8")
        if envelope.article is not None:
            (entry_dir / "article.json").write_text(envelope.article.to_json() + "\n", encoding="utf-8")
        if envelope.markdown is not None:
            (entry_dir / "extracted.md").write_text(envelope.markdown, encoding="utf-8")
        (entry_dir / "current-issue-row.json").write_text(json.dumps(current_result.to_dict(), ensure_ascii=False, indent=2) + "\n", encoding="utf-8")

        return GeographyIssueExportEntry(
            provider=provider,
            doi=doi,
            title=title,
            issue_flags=flags,
            output_dir=str(entry_dir),
            export_status="exported",
            elapsed_seconds=elapsed_seconds,
            exported_files=list_exported_files(entry_dir),
            current_status=current_result.status,
            current_source=current_result.source,
            current_content_kind=current_result.content_kind,
            current_issue_flags=current_result.issue_flags,
        )
    except PaperFetchFailure as exc:
        elapsed_seconds = round(time.monotonic() - started_at, 3)
        error_payload = {
            "provider": provider,
            "doi": doi,
            "title": title,
            "issue_flags": flags,
            "error_code": exc.status,
            "error_message": exc.reason,
        }
        (entry_dir / "export-error.json").write_text(json.dumps(error_payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
        return GeographyIssueExportEntry(
            provider=provider,
            doi=doi,
            title=title,
            issue_flags=flags,
            output_dir=str(entry_dir),
            export_status="error",
            elapsed_seconds=elapsed_seconds,
            exported_files=list_exported_files(entry_dir),
            error_code=exc.status,
            error_message=exc.reason,
        )
    except Exception as exc:  # pragma: no cover - defensive export path
        elapsed_seconds = round(time.monotonic() - started_at, 3)
        error_payload = {
            "provider": provider,
            "doi": doi,
            "title": title,
            "issue_flags": flags,
            "error_code": exc.__class__.__name__,
            "error_message": str(exc),
        }
        (entry_dir / "export-error.json").write_text(json.dumps(error_payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
        return GeographyIssueExportEntry(
            provider=provider,
            doi=doi,
            title=title,
            issue_flags=flags,
            output_dir=str(entry_dir),
            export_status="error",
            elapsed_seconds=elapsed_seconds,
            exported_files=list_exported_files(entry_dir),
            error_code=exc.__class__.__name__,
            error_message=str(exc),
        )
    finally:
        context.close()


def list_exported_files(entry_dir: Path) -> list[str]:
    return sorted(
        str(path.relative_to(entry_dir))
        for path in entry_dir.rglob("*")
        if path.is_file()
    )


def effective_issue_flags(entry: Mapping[str, Any]) -> list[str]:
    if "current_issue_flags" in entry:
        return [
            normalize_text(item)
            for item in entry.get("current_issue_flags", [])
            if normalize_text(item)
        ]
    return [normalize_text(item) for item in entry.get("issue_flags", []) if normalize_text(item)]


def materialize_issue_type_view(
    *,
    artifact_root: Path,
    clean: bool = True,
) -> dict[str, Any]:
    index_payload = load_report_payload(artifact_root / "index.json")
    export_dir_names = {
        Path(entry["output_dir"]).name
        for entry in index_payload.get("entries", [])
        if normalize_text(entry.get("output_dir"))
    }
    issue_dirs = sorted(
        {
            flag
            for entry in index_payload.get("entries", [])
            for flag in effective_issue_flags(entry)
            if normalize_text(flag)
        }
    )

    if clean:
        stale_issue_dirs = [
            path
            for path in artifact_root.iterdir()
            if path.is_dir() and path.name not in export_dir_names and path.name not in issue_dirs
        ]
        for stale_dir in stale_issue_dirs:
            for child in sorted(stale_dir.iterdir()):
                if child.is_symlink() or child.is_file():
                    child.unlink()
                elif child.is_dir():
                    for nested in sorted(child.rglob("*"), reverse=True):
                        if nested.is_symlink() or nested.is_file():
                            nested.unlink()
                        elif nested.is_dir():
                            nested.rmdir()
                    child.rmdir()
            stale_dir.rmdir()
        for issue_flag in issue_dirs:
            issue_dir = artifact_root / issue_flag
            if issue_dir.exists():
                for child in sorted(issue_dir.iterdir()):
                    if child.is_symlink() or child.is_file():
                        child.unlink()
                    elif child.is_dir():
                        for nested in sorted(child.rglob("*"), reverse=True):
                            if nested.is_symlink() or nested.is_file():
                                nested.unlink()
                            elif nested.is_dir():
                                nested.rmdir()
                        child.rmdir()
                issue_dir.rmdir()

    summary_entries: list[dict[str, Any]] = []
    for issue_flag in issue_dirs:
        issue_dir = artifact_root / issue_flag
        issue_dir.mkdir(parents=True, exist_ok=True)
        linked: list[str] = []
        for entry in index_payload.get("entries", []):
            entry_flags = effective_issue_flags(entry)
            if issue_flag not in entry_flags:
                continue
            entry_dir = Path(entry["output_dir"])
            link_path = issue_dir / entry_dir.name
            if link_path.exists() or link_path.is_symlink():
                link_path.unlink()
            link_path.symlink_to(entry_dir.resolve(), target_is_directory=True)
            linked.append(entry["doi"])

        summary = {
            "issue_flag": issue_flag,
            "count": len(linked),
            "dois": linked,
        }
        (issue_dir / "index.json").write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
        summary_entries.append(summary)

    view_summary = {
        "artifact_root": str(artifact_root),
        "issue_dirs": summary_entries,
    }
    (artifact_root / "issue-view-index.json").write_text(json.dumps(view_summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    return view_summary
