from __future__ import annotations

import hashlib
import json
from pathlib import Path
from typing import Any, Mapping

from pyimgano.weights.manifest import validate_weights_manifest_file
from pyimgano.weights.model_card import validate_model_card_file

_LEADERBOARD_METADATA_JSON = "leaderboard_metadata.json"


def _load_json_dict(path: Path) -> dict[str, Any]:
    payload = json.loads(path.read_text(encoding="utf-8"))
    if not isinstance(payload, dict):
        raise ValueError(f"Expected JSON object in: {path}")
    return payload


def _resolve_metadata_path(path: str | Path) -> Path:
    candidate = Path(path).resolve(strict=False)
    if candidate.is_dir():
        return (candidate / _LEADERBOARD_METADATA_JSON).resolve(strict=False)
    return candidate


def _resolve_exported_path(root: Path, raw: Any) -> Path | None:
    if not isinstance(raw, str) or not raw:
        return None
    root_resolved = root.resolve(strict=False)
    path = Path(raw)
    if path.is_absolute():
        try:
            path = path.relative_to(root_resolved)
        except ValueError:
            return None
    if path.is_absolute() or ".." in path.parts:
        return None
    candidate = (root_resolved / path).resolve(strict=False)
    try:
        candidate.relative_to(root_resolved)
    except ValueError:
        return None
    return candidate


def _display_exported_path(root: Path, raw: Any) -> str | None:
    resolved = _resolve_exported_path(root, raw)
    if resolved is None:
        return None
    try:
        return str(resolved.relative_to(root.resolve(strict=False)))
    except Exception:
        return str(resolved)


def _validation_payload(rel_path: str) -> dict[str, Any]:
    return {
        "path": rel_path,
        "present": False,
        "valid": None,
        "errors": [],
        "warnings": [],
    }


def _nonempty_str(value: Any) -> str | None:
    if not isinstance(value, str):
        return None
    text = value.strip()
    return text if text else None


def _append_missing_required(missing_required: list[str], item: str) -> None:
    if item not in missing_required:
        missing_required.append(item)


def _split_fingerprint_sha256(split_fingerprint: Any) -> str | None:
    if not isinstance(split_fingerprint, dict):
        return None
    return _nonempty_str(split_fingerprint.get("sha256"))


def _file_sha256(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


def _required_exported_digest_keys(exported_files: Any) -> list[str]:
    if not isinstance(exported_files, dict):
        return []
    return [str(key) for key in exported_files if str(key) != "leaderboard_metadata_json"]


def _publication_ready_requested(metadata: Mapping[str, Any]) -> bool:
    return metadata.get("publication_ready") is True


def _collect_exported_file_presence(
    root: Path,
    *,
    exported_files: Any,
    audit_refs: dict[str, str],
    missing_required: list[str],
) -> tuple[dict[str, bool], dict[str, Path]]:
    exported_files_present: dict[str, bool] = {}
    resolved_exported_paths: dict[str, Path] = {}
    if not isinstance(exported_files, dict):
        return exported_files_present, resolved_exported_paths

    for key, raw_path in exported_files.items():
        resolved = _resolve_exported_path(root, raw_path)
        present = bool(resolved is not None and resolved.is_file())
        exported_files_present[str(key)] = present
        if present and resolved is not None:
            resolved_exported_paths[str(key)] = resolved
        display_path = _display_exported_path(root, raw_path)
        if display_path is not None and present:
            audit_refs[str(key)] = str(display_path)
        if (
            not present
            and str(key) != "leaderboard_metadata_json"
            and str(key) not in missing_required
        ):
            missing_required.append(str(key))
    return exported_files_present, resolved_exported_paths


def _collect_exported_file_digests(
    metadata_exported_file_digests: Mapping[str, Any],
    *,
    required_exported_digest_keys: list[str],
    resolved_exported_paths: Mapping[str, Path],
    missing_required: list[str],
) -> tuple[dict[str, str], bool]:
    exported_file_digests: dict[str, str] = {}
    has_exported_file_digests = bool(required_exported_digest_keys)
    for key in required_exported_digest_keys:
        digest = _nonempty_str(metadata_exported_file_digests.get(key))
        if digest is None:
            _append_missing_required(missing_required, f"exported_file_digests.{key}")
            has_exported_file_digests = False
            continue
        resolved_exported = resolved_exported_paths.get(key)
        if resolved_exported is None:
            has_exported_file_digests = False
            continue
        actual_digest = _file_sha256(resolved_exported)
        if digest != actual_digest:
            _append_missing_required(missing_required, f"{key}_sha256_mismatch")
            has_exported_file_digests = False
            continue
        exported_file_digests[key] = digest
    return exported_file_digests, has_exported_file_digests


def _collect_run_artifact_refs(
    root: Path,
    *,
    metadata_audit_refs: Mapping[str, Any],
    audit_refs: dict[str, str],
    missing_required: list[str],
) -> tuple[dict[str, Path], bool]:
    resolved_run_artifact_paths: dict[str, Path] = {}
    has_run_artifact_refs = True
    for key in ("report_json", "config_json", "environment_json"):
        raw_ref = _nonempty_str(metadata_audit_refs.get(key))
        if raw_ref is None:
            _append_missing_required(missing_required, f"audit_refs.{key}")
            has_run_artifact_refs = False
            continue
        resolved_ref = _resolve_exported_path(root, raw_ref)
        if resolved_ref is None or not resolved_ref.is_file():
            _append_missing_required(missing_required, key)
            has_run_artifact_refs = False
            continue
        resolved_run_artifact_paths[key] = resolved_ref
        display_ref = _display_exported_path(root, raw_ref)
        if display_ref is not None:
            audit_refs[key] = str(display_ref)
    return resolved_run_artifact_paths, has_run_artifact_refs


def _collect_run_artifact_digests(
    metadata_audit_digests: Mapping[str, Any],
    *,
    resolved_run_artifact_paths: Mapping[str, Path],
    missing_required: list[str],
) -> tuple[dict[str, str], bool]:
    audit_digests: dict[str, str] = {}
    has_run_artifact_digests = True
    for key in ("report_json", "config_json", "environment_json"):
        digest = _nonempty_str(metadata_audit_digests.get(key))
        if digest is None:
            _append_missing_required(missing_required, f"audit_digests.{key}")
            has_run_artifact_digests = False
            continue
        resolved_ref = resolved_run_artifact_paths.get(key)
        if resolved_ref is None:
            has_run_artifact_digests = False
            continue
        actual_digest = _file_sha256(resolved_ref)
        if digest != actual_digest:
            _append_missing_required(missing_required, f"{key}_sha256_mismatch")
            has_run_artifact_digests = False
            continue
        audit_digests[key] = digest
    return audit_digests, has_run_artifact_digests


def _benchmark_config_details(
    benchmark_config: Any,
    *,
    audit_refs: dict[str, str],
    missing_required: list[str],
) -> tuple[str | None, str | None, dict[str, Any] | None]:
    benchmark_config_source = None
    benchmark_config_sha256 = None
    benchmark_config_trust = None
    if isinstance(benchmark_config, dict):
        raw_source = _nonempty_str(benchmark_config.get("source", None))
        if raw_source is not None:
            benchmark_config_source = raw_source
            audit_refs["benchmark_config_source"] = str(raw_source)
        raw_sha256 = _nonempty_str(benchmark_config.get("sha256", None))
        if raw_sha256 is not None:
            benchmark_config_sha256 = raw_sha256
        raw_trust = benchmark_config.get("trust_summary", None)
        if isinstance(raw_trust, dict):
            benchmark_config_trust = raw_trust
    else:
        _append_missing_required(missing_required, "benchmark_config")
    if isinstance(benchmark_config, dict) and benchmark_config_source is None:
        _append_missing_required(missing_required, "benchmark_config.source")
    if isinstance(benchmark_config, dict) and benchmark_config_sha256 is None:
        _append_missing_required(missing_required, "benchmark_config.sha256")
    return benchmark_config_source, benchmark_config_sha256, benchmark_config_trust


def _publication_trust_signals(
    *,
    benchmark_config: Any,
    benchmark_config_source: str | None,
    benchmark_config_sha256: str | None,
    benchmark_config_trust: dict[str, Any] | None,
    has_evaluation_contract: bool,
    has_benchmark_citation: bool,
    has_run_artifact_refs: bool,
    has_run_artifact_digests: bool,
    has_exported_file_digests: bool,
    asset_audit: Mapping[str, Any],
) -> dict[str, Any]:
    has_official_benchmark_config = bool(
        isinstance(benchmark_config, dict) and benchmark_config.get("official")
    )
    has_benchmark_provenance = bool(
        isinstance(benchmark_config, dict)
        and benchmark_config.get("official")
        and benchmark_config_source
        and benchmark_config_sha256
    )
    trust_signals = {
        "has_official_benchmark_config": has_official_benchmark_config,
        "has_evaluation_contract": has_evaluation_contract,
        "has_benchmark_citation": has_benchmark_citation,
        "has_cross_checked_assets": bool(asset_audit.get("cross_checked")),
        "has_benchmark_provenance": has_benchmark_provenance,
        "has_benchmark_config_ref": bool(benchmark_config_source),
        "has_run_artifact_refs": bool(has_run_artifact_refs),
        "has_run_artifact_digests": bool(has_run_artifact_digests),
        "has_exported_file_digests": bool(has_exported_file_digests),
    }
    if isinstance(benchmark_config_trust, dict):
        trust_signals["has_trust_signaled_benchmark_config"] = (
            str(benchmark_config_trust.get("status", "")) == "trust-signaled"
        )
    return trust_signals


def _publication_artifact_quality_payload(
    artifact_quality: Any,
    *,
    missing_required: list[str],
    environment_fingerprint_sha256: str | None,
    split_fingerprint_sha256: str | None,
    has_evaluation_contract: bool,
    has_benchmark_citation: bool,
    trust_signals: Mapping[str, Any],
) -> dict[str, Any]:
    artifact_quality_payload = dict(artifact_quality) if isinstance(artifact_quality, dict) else {}
    artifact_quality_payload["required_files_present"] = len(missing_required) == 0
    artifact_quality_payload["missing_required"] = list(missing_required)
    artifact_quality_payload["has_official_benchmark_config"] = bool(
        trust_signals["has_official_benchmark_config"]
    )
    artifact_quality_payload["has_environment_fingerprint"] = bool(environment_fingerprint_sha256)
    artifact_quality_payload["has_split_fingerprint"] = bool(split_fingerprint_sha256)
    artifact_quality_payload["has_evaluation_contract"] = has_evaluation_contract
    artifact_quality_payload["has_benchmark_citation"] = has_benchmark_citation
    artifact_quality_payload["has_benchmark_provenance"] = bool(
        trust_signals["has_benchmark_provenance"]
    )
    artifact_quality_payload["has_run_artifact_refs"] = bool(trust_signals["has_run_artifact_refs"])
    artifact_quality_payload["has_run_artifact_digests"] = bool(
        trust_signals["has_run_artifact_digests"]
    )
    artifact_quality_payload["has_exported_file_digests"] = bool(
        trust_signals["has_exported_file_digests"]
    )
    if "has_trust_signaled_benchmark_config" in trust_signals:
        artifact_quality_payload["has_trust_signaled_benchmark_config"] = bool(
            trust_signals["has_trust_signaled_benchmark_config"]
        )
    return artifact_quality_payload


def _evaluate_declared_weight_artifacts(
    root: Path,
    exported_files: Any,
) -> tuple[list[str], dict[str, Any]]:
    payload = {
        "present": False,
        "valid": None,
        "cross_checked": False,
        "model_card": _validation_payload("model_card_json"),
        "weights_manifest": _validation_payload("weights_manifest_json"),
    }
    invalid_declared: list[str] = []
    if not isinstance(exported_files, dict):
        return invalid_declared, payload

    manifest_path, manifest_ok = _evaluate_declared_manifest(
        root,
        exported_files=exported_files,
        payload=payload,
        invalid_declared=invalid_declared,
    )
    _evaluate_declared_model_card(
        root,
        exported_files=exported_files,
        payload=payload,
        invalid_declared=invalid_declared,
        manifest_path=(manifest_path if manifest_ok else None),
        manifest_ok=manifest_ok,
    )

    if bool(payload["present"]):
        valid = True
        for key in ("model_card", "weights_manifest"):
            artifact_payload = payload[key]
            if bool(artifact_payload["present"]) and artifact_payload["valid"] is False:
                valid = False
        payload["valid"] = bool(valid)

    return invalid_declared, payload


def _evaluate_declared_manifest(
    root: Path,
    *,
    exported_files: dict[str, Any],
    payload: dict[str, Any],
    invalid_declared: list[str],
) -> tuple[Path | None, bool]:
    manifest_path = _resolve_exported_path(root, exported_files.get("weights_manifest_json"))
    manifest_ok = False
    if manifest_path is None or not manifest_path.is_file():
        return manifest_path, manifest_ok

    payload["present"] = True
    payload["weights_manifest"]["present"] = True
    payload["weights_manifest"]["path"] = str(manifest_path)
    report = validate_weights_manifest_file(
        manifest_path=manifest_path,
        check_files=False,
        check_hashes=False,
    )
    payload["weights_manifest"]["valid"] = bool(report.ok)
    payload["weights_manifest"]["errors"] = list(report.errors)
    payload["weights_manifest"]["warnings"] = list(report.warnings)
    if not report.ok:
        invalid_declared.append("weights_manifest_json")
    manifest_ok = bool(report.ok)
    return manifest_path, manifest_ok


def _evaluate_declared_model_card(
    root: Path,
    *,
    exported_files: dict[str, Any],
    payload: dict[str, Any],
    invalid_declared: list[str],
    manifest_path: Path | None,
    manifest_ok: bool,
) -> None:
    model_card_path = _resolve_exported_path(root, exported_files.get("model_card_json"))
    if model_card_path is None or not model_card_path.is_file():
        return

    payload["present"] = True
    payload["model_card"]["present"] = True
    payload["model_card"]["path"] = str(model_card_path)
    report = validate_model_card_file(
        model_card_path,
        manifest_path=manifest_path,
        check_files=False,
        check_hashes=False,
    )
    payload["model_card"]["valid"] = bool(report.ok)
    payload["model_card"]["errors"] = list(report.errors)
    payload["model_card"]["warnings"] = list(report.warnings)
    if not report.ok:
        invalid_declared.append("model_card_json")
    payload["cross_checked"] = bool(manifest_ok)


def evaluate_publication_quality(path: str | Path) -> dict[str, Any]:
    metadata_path = _resolve_metadata_path(path)
    root = metadata_path.parent
    if not metadata_path.is_file():
        return {
            "root": str(root),
            "metadata_path": str(metadata_path),
            "status": "broken",
            "publication_ready": False,
            "missing_required": [_LEADERBOARD_METADATA_JSON],
            "exported_files_present": {},
            "exported_file_digests": {},
            "artifact_quality": None,
            "invalid_declared": [],
            "asset_audit": {
                "present": False,
                "valid": None,
                "cross_checked": False,
                "model_card": _validation_payload("model_card_json"),
                "weights_manifest": _validation_payload("weights_manifest_json"),
            },
        }

    metadata = _load_json_dict(metadata_path)
    dataset_readiness_raw = metadata.get("dataset_readiness", None)
    dataset_readiness = (
        dict(dataset_readiness_raw) if isinstance(dataset_readiness_raw, dict) else None
    )
    if isinstance(dataset_readiness, dict):
        if not isinstance(dataset_readiness.get("issue_codes"), list):
            dataset_readiness["issue_codes"] = []
        if not isinstance(dataset_readiness.get("issue_details"), list):
            dataset_readiness["issue_details"] = []
    artifact_quality = metadata.get("artifact_quality", None)
    benchmark_config = metadata.get("benchmark_config", None)
    declared_missing = []
    if isinstance(artifact_quality, dict):
        declared_missing = [
            str(item)
            for item in artifact_quality.get("missing_required", [])
            if isinstance(item, str) and item
        ]

    exported_files = metadata.get("exported_files", None)
    audit_refs: dict[str, str] = {"leaderboard_metadata_json": _LEADERBOARD_METADATA_JSON}
    missing_required = list(declared_missing)
    exported_files_present, resolved_exported_paths = _collect_exported_file_presence(
        root,
        exported_files=exported_files,
        audit_refs=audit_refs,
        missing_required=missing_required,
    )
    if not any(
        key.startswith("leaderboard_") and key != "leaderboard_metadata_json"
        for key in exported_files_present
    ):
        _append_missing_required(missing_required, "leaderboard_table")

    required_exported_digest_keys = _required_exported_digest_keys(exported_files)
    metadata_exported_file_digests_raw = metadata.get("exported_file_digests", None)
    metadata_exported_file_digests = (
        dict(metadata_exported_file_digests_raw)
        if isinstance(metadata_exported_file_digests_raw, dict)
        else {}
    )
    exported_file_digests, has_exported_file_digests = _collect_exported_file_digests(
        metadata_exported_file_digests,
        required_exported_digest_keys=required_exported_digest_keys,
        resolved_exported_paths=resolved_exported_paths,
        missing_required=missing_required,
    )

    metadata_audit_refs_raw = metadata.get("audit_refs", None)
    metadata_audit_refs = (
        dict(metadata_audit_refs_raw) if isinstance(metadata_audit_refs_raw, dict) else {}
    )
    resolved_run_artifact_paths, has_run_artifact_refs = _collect_run_artifact_refs(
        root,
        metadata_audit_refs=metadata_audit_refs,
        audit_refs=audit_refs,
        missing_required=missing_required,
    )

    metadata_audit_digests_raw = metadata.get("audit_digests", None)
    metadata_audit_digests = (
        dict(metadata_audit_digests_raw) if isinstance(metadata_audit_digests_raw, dict) else {}
    )
    audit_digests, has_run_artifact_digests = _collect_run_artifact_digests(
        metadata_audit_digests,
        resolved_run_artifact_paths=resolved_run_artifact_paths,
        missing_required=missing_required,
    )
    (
        benchmark_config_source,
        benchmark_config_sha256,
        benchmark_config_trust,
    ) = _benchmark_config_details(
        benchmark_config,
        audit_refs=audit_refs,
        missing_required=missing_required,
    )

    environment_fingerprint_sha256 = _nonempty_str(metadata.get("environment_fingerprint_sha256"))
    if environment_fingerprint_sha256 is None:
        _append_missing_required(missing_required, "environment_fingerprint_sha256")

    split_fingerprint_sha256 = _split_fingerprint_sha256(metadata.get("split_fingerprint"))
    if split_fingerprint_sha256 is None:
        _append_missing_required(missing_required, "split_fingerprint.sha256")

    has_evaluation_contract = isinstance(metadata.get("evaluation_contract"), dict)
    if not has_evaluation_contract:
        _append_missing_required(missing_required, "evaluation_contract")

    has_benchmark_citation = isinstance(metadata.get("citation"), dict)
    if not has_benchmark_citation:
        _append_missing_required(missing_required, "citation")

    invalid_declared, asset_audit = _evaluate_declared_weight_artifacts(root, exported_files)
    trust_signals = _publication_trust_signals(
        benchmark_config=benchmark_config,
        benchmark_config_source=benchmark_config_source,
        benchmark_config_sha256=benchmark_config_sha256,
        benchmark_config_trust=benchmark_config_trust,
        has_evaluation_contract=has_evaluation_contract,
        has_benchmark_citation=has_benchmark_citation,
        has_run_artifact_refs=has_run_artifact_refs,
        has_run_artifact_digests=has_run_artifact_digests,
        has_exported_file_digests=has_exported_file_digests,
        asset_audit=asset_audit,
    )
    artifact_quality_payload = _publication_artifact_quality_payload(
        artifact_quality,
        missing_required=missing_required,
        environment_fingerprint_sha256=environment_fingerprint_sha256,
        split_fingerprint_sha256=split_fingerprint_sha256,
        has_evaluation_contract=has_evaluation_contract,
        has_benchmark_citation=has_benchmark_citation,
        trust_signals=trust_signals,
    )

    publication_ready = bool(
        _publication_ready_requested(metadata)
        and artifact_quality_payload["required_files_present"]
        and artifact_quality_payload["has_official_benchmark_config"]
        and artifact_quality_payload["has_evaluation_contract"]
        and artifact_quality_payload["has_benchmark_citation"]
        and artifact_quality_payload["has_benchmark_provenance"]
        and artifact_quality_payload["has_run_artifact_refs"]
        and artifact_quality_payload["has_run_artifact_digests"]
        and artifact_quality_payload["has_exported_file_digests"]
        and not invalid_declared
    )
    if publication_ready:
        status = "ready"
    else:
        status = "partial"

    return {
        "root": str(root),
        "metadata_path": str(metadata_path),
        "status": status,
        "publication_ready": bool(publication_ready),
        "missing_required": missing_required,
        "exported_files_present": exported_files_present,
        "exported_file_digests": exported_file_digests,
        "artifact_quality": artifact_quality_payload,
        "dataset_readiness": dataset_readiness,
        "invalid_declared": invalid_declared,
        "asset_audit": asset_audit,
        "trust_signals": trust_signals,
        "audit_refs": audit_refs,
        "audit_digests": audit_digests,
    }


__all__ = ["evaluate_publication_quality"]
