"""위저드 상태 파일 — `load_state_file`·`save_state_file` 과 v2 companion 파일 탐색."""
from __future__ import annotations

import hashlib
from pathlib import Path
from typing import Any

from okstra_ctl.convergence_store import write_json_atomic
from okstra_ctl.json_boundary import JsonBoundaryError, load_owned_object

from .state import WizardError, WizardState


# ---- File I/O helpers (used by CLI) -------------------------------------

_LEGACY_SOURCE_FIELD = "legacySource"


def _legacy_v2_companion_path(path: Path) -> Path:
    return path.with_name(f"{path.name}.v2.json")


def _content_addressed_v2_companion_path(
    path: Path,
    source_digest: str,
    suffix: int | None = None,
) -> Path:
    numbered = f".{suffix}" if suffix is not None else ""
    return path.with_name(f"{path.name}.v2.{source_digest}{numbered}.json")


def _content_addressed_v2_companion_paths(
    path: Path,
    source_digest: str,
) -> tuple[Path, ...]:
    canonical = _content_addressed_v2_companion_path(path, source_digest)
    numbered_prefix = f"{path.name}.v2.{source_digest}."
    numbered = tuple(sorted(
        candidate
        for candidate in path.parent.iterdir()
        if candidate.is_file()
        and candidate.name.startswith(numbered_prefix)
        and candidate.name.endswith(".json")
    ))
    return (canonical, *numbered)


def _available_v2_companion_path(path: Path, source_digest: str) -> Path:
    candidate = _content_addressed_v2_companion_path(path, source_digest)
    suffix = 1
    while candidate.exists():
        candidate = _content_addressed_v2_companion_path(
            path,
            source_digest,
            suffix,
        )
        suffix += 1
    return candidate


def _legacy_source_metadata(path: Path, source: bytes) -> dict[str, str]:
    return {
        "path": str(path.resolve()),
        "sha256": hashlib.sha256(source).hexdigest(),
    }


def _matching_v2_companion(
    path: Path,
    source_metadata: dict[str, str],
) -> dict[str, Any] | None:
    try:
        payload = load_owned_object(path, artifact="wizard state")
    except (OSError, UnicodeError, JsonBoundaryError):
        return None
    if not isinstance(payload, dict):
        return None
    if payload.get("executionIdentityVersion") != 2:
        return None
    if payload.get(_LEGACY_SOURCE_FIELD) != source_metadata:
        return None
    return payload


def _load_matching_v2_state(
    paths: tuple[Path, ...],
    source_metadata: dict[str, str],
) -> tuple[WizardState | None, Path | None]:
    for companion_path in paths:
        payload = _matching_v2_companion(companion_path, source_metadata)
        if payload is None:
            continue
        try:
            return WizardState.from_json(payload), companion_path
        except WizardError:
            continue
    return None, None


def load_state_file(path: Path) -> WizardState:
    source_path = Path(path)
    source = source_path.read_bytes()
    data = load_owned_object(source_path, artifact="wizard state")
    source_version = data.get("executionIdentityVersion", 1)
    source_metadata = _legacy_source_metadata(source_path, source)
    source_digest = source_metadata["sha256"]
    resumed_state = None
    companion_path = None
    if source_version in (None, 1):
        content_paths = _content_addressed_v2_companion_paths(
            source_path,
            source_digest,
        )
        resumed_state, companion_path = _load_matching_v2_state(
            content_paths,
            source_metadata,
        )
        if resumed_state is None:
            resumed_state, _ = _load_matching_v2_state(
                (_legacy_v2_companion_path(source_path),),
                source_metadata,
            )
    state = resumed_state or WizardState.from_json(data)
    state._source_execution_identity_version = source_version
    if source_version in (None, 1):
        state._legacy_source_metadata = source_metadata
        state._v2_companion_path = companion_path or _available_v2_companion_path(
            source_path,
            source_digest,
        )
    return state


def _save_resumed_state_file(path: Path, state: WizardState) -> None:
    """Persist native v2 state while keeping legacy resume sources read-only."""
    if getattr(state, "_source_execution_identity_version", 2) in (None, 1):
        payload = state.to_json()
        payload[_LEGACY_SOURCE_FIELD] = state._legacy_source_metadata
        write_json_atomic(state._v2_companion_path, payload)
        return
    save_state_file(path, state)


def save_state_file(path: Path, state: WizardState) -> None:
    write_json_atomic(Path(path), state.to_json())
