from __future__ import annotations

import json
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Any

IDENTITY_FILENAME = ".audit_storage_node.json"


@dataclass(frozen=True)
class StorageIdentity:
    erp_base_url: str
    node_code: str
    tenant_id: str | None
    branch_id: str | None
    storage_root: str
    agent_identity_version: int = 1


def _normalise(value: Any) -> str:
    return str(value or "").strip().rstrip("/")


def _normalise_optional(value: Any) -> str | None:
    value = str(value or "").strip()
    return value or None


def identity_path(storage_root: Path) -> Path:
    return storage_root / IDENTITY_FILENAME


def build_identity(config) -> StorageIdentity:
    return StorageIdentity(
        erp_base_url=_normalise(config.erp_base_url),
        node_code=_normalise(config.node_code),
        tenant_id=_normalise_optional(getattr(config, "tenant_id", None)),
        branch_id=_normalise_optional(getattr(config, "branch_id", None)),
        storage_root=str(config.storage_root.resolve()),
    )


def ensure_storage_identity(config, *, allow_initialise: bool = True) -> Path:
    """Create/validate one identity file in the selected storage root.

    This protects the branch storage folder from accidentally being reused by a
    different ERP node/branch, and gives the branch one stable storage root.
    It does not scan the whole machine; ERP still remains the final authority
    for enforcing one active node and one configured root per branch.
    """
    root = config.storage_root.resolve()
    root.mkdir(parents=True, exist_ok=True)
    path = identity_path(root)
    expected = build_identity(config)

    if not path.exists():
        if not allow_initialise:
            raise RuntimeError(
                f"Storage identity file is missing at {path}. Initialise the branch storage root first."
            )
        path.write_text(json.dumps(asdict(expected), indent=2), encoding="utf-8")
        return path

    try:
        existing = json.loads(path.read_text(encoding="utf-8"))
    except Exception as exc:
        raise RuntimeError(f"Storage identity file is unreadable/corrupt: {path}") from exc

    checks = {
        "erp_base_url": expected.erp_base_url,
        "node_code": expected.node_code,
        "tenant_id": expected.tenant_id,
        "branch_id": expected.branch_id,
    }
    conflicts: list[str] = []
    for key, expected_value in checks.items():
        existing_value = existing.get(key)
        # Older packages may not have tenant_id/branch_id. Allow blank existing
        # values to be upgraded below, but never allow contradictory values.
        if key in {"tenant_id", "branch_id"} and not existing_value:
            continue
        if str(existing_value or "").strip().rstrip("/") != str(expected_value or "").strip().rstrip("/"):
            conflicts.append(f"{key}: existing={existing_value!r}, expected={expected_value!r}")

    if conflicts:
        raise RuntimeError(
            "This storage folder is already linked to a different ERP storage node/branch. "
            "Use the existing branch storage package or reset/change the storage root from ERP. "
            + "; ".join(conflicts)
        )

    # Backfill missing tenant/branch fields and refresh resolved root path.
    changed = False
    updated = dict(existing)
    for key, value in asdict(expected).items():
        if updated.get(key) in (None, "") and value not in (None, ""):
            updated[key] = value
            changed = True
    if updated.get("storage_root") != expected.storage_root:
        updated["storage_root"] = expected.storage_root
        changed = True
    if changed:
        path.write_text(json.dumps(updated, indent=2), encoding="utf-8")
    return path
