"""Resume and legacy-state migration decisions for convergence seeding."""
from __future__ import annotations

from dataclasses import dataclass
import hashlib
import os
from pathlib import Path
import tempfile
from typing import Any, Literal, Mapping

from .convergence_engine import (
    ConvergenceContractError,
    FINAL_SCHEMA_VERSION,
    grouped_input_digest,
    validate_final_state,
    validate_working_state,
)
from .convergence_store import load_json_object


SeedAction = Literal[
    "create-work",
    "resume-work",
    "reuse-final",
    "restart-round0",
]


@dataclass(frozen=True)
class SeedDecision:
    action: SeedAction
    archive_path: Path | None
    legacy_digest: str | None
    reason: str


def decide_seed_action(
    *,
    grouped_input: Mapping[str, Any],
    work_state_path: Path,
    final_state_path: Path,
    restart_from_round0: bool,
) -> SeedDecision:
    """Classify existing new-engine/legacy state without mutating files."""
    task_key = grouped_input.get("taskKey")
    digest = grouped_input_digest(grouped_input)
    final_status, final_value = _inspect_final(final_state_path)
    if final_status == "terminal":
        assert final_value is not None
        version = final_value.get("schemaVersion")
        reason = (
            f"valid historical final {version} reused without rewrite"
            if version != FINAL_SCHEMA_VERSION
            else "valid terminal final state"
        )
        return SeedDecision("reuse-final", None, None, reason)

    work_status, work_value = _inspect_work(work_state_path)
    invalid_final = final_status == "invalid"
    if work_status == "valid":
        assert work_value is not None
        if work_value.get("taskKey") != task_key:
            raise ConvergenceContractError(
                "existing working state taskKey does not match grouped input"
            )
        if work_value.get("groupsDigest") != digest:
            raise ConvergenceContractError(
                "existing working state groupsDigest does not match grouped input"
            )
        if invalid_final:
            archive_path, legacy_digest = _archive_identity(
                final_state_path, final_state_path.parent / "migrations"
            )
            return SeedDecision(
                "resume-work",
                archive_path,
                legacy_digest,
                "matching working state with invalid legacy final",
            )
        return SeedDecision("resume-work", None, None, "matching working state")

    if work_status == "invalid" and not restart_from_round0:
        raise ConvergenceContractError(
            "existing convergence working state is invalid; pass "
            "--restart-from-round0 to archive it and restart"
        )
    if work_status == "invalid":
        source = final_state_path if invalid_final else work_state_path
        archive_path, legacy_digest = _archive_identity(
            source, source.parent / "migrations"
        )
        return SeedDecision(
            "restart-round0",
            archive_path,
            legacy_digest,
            "explicit restart of invalid working state",
        )
    if invalid_final:
        archive_path, legacy_digest = _archive_identity(
            final_state_path, final_state_path.parent / "migrations"
        )
        return SeedDecision(
            "restart-round0",
            archive_path,
            legacy_digest,
            "invalid or partial legacy final state",
        )
    return SeedDecision("create-work", None, None, "no existing convergence state")


def archive_state_bytes(
    *,
    source_path: Path,
    migration_dir: Path,
) -> tuple[Path, str]:
    """Archive source bytes under a digest-derived immutable filename."""
    data = source_path.read_bytes()
    digest = hashlib.sha256(data).hexdigest()
    archive_path = migration_dir / f"{source_path.name}.{digest[:12]}.json"
    if archive_path.exists():
        if archive_path.read_bytes() != data:
            raise ConvergenceContractError(
                f"migration archive path contains different bytes: {archive_path}"
            )
        return archive_path, digest
    migration_dir.mkdir(parents=True, exist_ok=True)
    temp_path: Path | None = None
    try:
        with tempfile.NamedTemporaryFile(
            mode="wb",
            dir=migration_dir,
            prefix=f".{archive_path.name}.",
            suffix=".tmp",
            delete=False,
        ) as handle:
            temp_path = Path(handle.name)
            handle.write(data)
            handle.flush()
            os.fsync(handle.fileno())
        os.replace(temp_path, archive_path)
        temp_path = None
    finally:
        if temp_path is not None:
            temp_path.unlink(missing_ok=True)
    return archive_path, digest


def migration_record(
    source_path: Path,
    archive_path: Path,
    digest: str,
) -> dict[str, str]:
    return {
        "mode": "restart-round0",
        "sourcePath": str(source_path),
        "archivePath": str(archive_path),
        "legacyDigest": digest,
    }


def _inspect_final(path: Path) -> tuple[str, dict[str, Any] | None]:
    if not path.exists():
        return "absent", None
    try:
        value = load_json_object(path)
    except (OSError, ValueError):
        return "invalid", None
    if validate_final_state(value):
        return "invalid", value
    if value.get("finalState") not in {
        "converged",
        "max-rounds-reached",
        "aborted-non-result",
    }:
        return "invalid", value
    return "terminal", value


def _inspect_work(path: Path) -> tuple[str, dict[str, Any] | None]:
    if not path.exists():
        return "absent", None
    try:
        value = load_json_object(path)
    except (OSError, ValueError):
        return "invalid", None
    return ("valid", value) if not validate_working_state(value) else ("invalid", value)


def _archive_identity(source_path: Path, migration_dir: Path) -> tuple[Path, str]:
    data = source_path.read_bytes()
    digest = hashlib.sha256(data).hexdigest()
    return migration_dir / f"{source_path.name}.{digest[:12]}.json", digest
