from __future__ import annotations

import json
from pathlib import Path
from typing import Any

from auditwalk.compare.differ import default_diff_path, diff
from auditwalk.core.errors import EXIT_COMPARE_FAILED, EXIT_OK, EXIT_REPORT_FAILED
from auditwalk.core.jsonio import write_json
from auditwalk.report.render_html import render_html
from auditwalk.runs.run_store import RunStore
from auditwalk.score.scoring import score


def cmd_compare(args: Any) -> int:
    store = RunStore(args.data_dir)
    data_dir = Path(store.data_dir)
    try:
        store.apply_default_compare_retention()
        store.prune_orphan_compares()
    except Exception:
        pass

    try:
        run_a_raw, run_b_raw = _resolve_compare_args(args, getattr(args, "baseline_run_id", None))
        run_a = _resolve_baseline_alias(run_a_raw, getattr(args, "baseline_run_id", None))
        left = store.load_run_bundle(run_a)
        right = store.load_run_bundle(run_b_raw)
        result = diff(
            left,
            right,
            process_normalization_profile=str(getattr(args, "process_normalization_profile", "default")),
            compare_profile=str(getattr(args, "profile", "off")),
        )

        out_path = _resolve_out_path(
            args.out,
            data_dir,
            str(result.get("left_run_id")),
            str(result.get("right_run_id")),
        )
        out_path.parent.mkdir(parents=True, exist_ok=True)
        write_json(out_path, result)
        store.sync_compare_index()
    except Exception as exc:
        print(f"compare failed: {exc}")
        return EXIT_COMPARE_FAILED

    if args.score:
        try:
            rules = Path(Path(__file__).resolve().parents[1] / "data" / "scoring_rules.yml")
            score_obj = score(right, result, rules_path=rules)
            store.save_score(run_b_raw, score_obj)
            right["score"] = score_obj
        except Exception as exc:
            print(f"scoring failed: {exc}")
            return EXIT_COMPARE_FAILED

    if args.html:
        try:
            html_out = Path(args.html_out) if args.html_out else data_dir / "reports" / f"{right.get('meta', {}).get('run_id', 'run')}.html"
            html_out.parent.mkdir(parents=True, exist_ok=True)
            html_out.write_text(render_html(right, diff_bundle=result), encoding="utf-8")
        except Exception as exc:
            print(f"report failed: {exc}")
            return EXIT_REPORT_FAILED

    if args.format == "json" or args.json:
        print(
            json.dumps(
                {
                    "diff_path": str(out_path),
                    "run_a_input": run_a_raw,
                    "run_b_input": run_b_raw,
                    "run_a_resolved": run_a,
                    "diff": result,
                },
                ensure_ascii=True,
            )
        )
    else:
        print(f"Diff: {result.get('compare_id')}")
        print(f"Saved: {out_path}")
        profile = result.get("profile", {}) if isinstance(result.get("profile"), dict) else {}
        if profile.get("name", "off") != "off":
            print(
                "note: compare profile applied "
                f"({profile.get('name')}; ignored files left={profile.get('files_left_ignored', 0)} "
                f"right={profile.get('files_right_ignored', 0)})"
            )
        norm = result.get("normalization", {}) if isinstance(result.get("normalization"), dict) else {}
        left_norm = norm.get("processes_left", {}) if isinstance(norm.get("processes_left"), dict) else {}
        right_norm = norm.get("processes_right", {}) if isinstance(norm.get("processes_right"), dict) else {}
        if left_norm.get("applied") or right_norm.get("applied"):
            print(
                "note: process normalization applied "
                f"(profile={left_norm.get('profile', right_norm.get('profile', 'default'))})"
            )
        for domain, details in (result.get("domains", {}) or {}).items():
            counts = details.get("counts", {}) if isinstance(details, dict) else {}
            print(
                f"{domain}: added={counts.get('added', 0)} removed={counts.get('removed', 0)} modified={counts.get('modified', 0)}"
            )

    return EXIT_OK


def _resolve_out_path(raw_out: Path | None, data_dir: Path, run_a: str, run_b: str) -> Path:
    if raw_out is None:
        return default_diff_path(data_dir, run_a, run_b)

    out = Path(raw_out)
    if out.exists() and out.is_dir():
        return out / f"diff_{run_a}__{run_b}.json"

    out_str = str(out)
    if out_str.endswith("_"):
        return Path(f"{out_str}{run_a}__{run_b}.json")

    if out.suffix.lower() != ".json":
        return Path(f"{out_str}_{run_a}__{run_b}.json")

    return out


def _resolve_baseline_alias(run_a: str, baseline_run_id: str | None) -> str:
    if run_a != "baseline":
        return run_a
    if baseline_run_id:
        return baseline_run_id
    raise ValueError("baseline alias used but no baseline_run_id configured")


def _resolve_compare_args(args: Any, baseline_run_id: str | None) -> tuple[str, str]:
    run_a = getattr(args, "run_a", None)
    run_b = getattr(args, "run_b", None)
    use_baseline = bool(getattr(args, "use_baseline", False))

    if use_baseline:
        if not baseline_run_id:
            raise ValueError("--use-baseline requested but baseline_run_id is not configured")
        if run_b is None and run_a is not None:
            return ("baseline", str(run_a))
        if run_b is None:
            raise ValueError("--use-baseline requires target run id/path")
        return ("baseline", str(run_b))

    if run_a is None or run_b is None:
        raise ValueError("compare requires RUN_A RUN_B (or --use-baseline RUN_B)")

    return (str(run_a), str(run_b))
