"""Where the activated license lives on disk.

Default path: ``~/.datatools/license.json``. Overridable via
``$DATATOOLS_LICENSE_PATH`` for tests (the conftest fixture uses this
to point each test session at a tmp file).

The directory is created lazily on first write — we don't want to
create the user's config dir just for reading.
"""

from __future__ import annotations

import json
import os
from pathlib import Path
from typing import Any, Optional


def default_license_path() -> Path:
    """The resolved license file path for the current process.

    Order of resolution:

    1. ``$DATATOOLS_LICENSE_PATH`` (absolute path; used by tests).
    2. ``~/.datatools/license.json`` (everyone else).
    """
    override = os.environ.get("DATATOOLS_LICENSE_PATH")
    if override:
        return Path(override).expanduser().resolve()
    return Path.home() / ".datatools" / "license.json"


def read_raw(path: Optional[Path] = None) -> Optional[dict[str, Any]]:
    """Return the on-disk license dict, or ``None`` if no file exists.

    Anything else (truncated file, invalid JSON) raises ``ValueError``
    so the caller surfaces it as :class:`InvalidLicenseError`. We
    don't try to recover from a corrupt license file — a user that
    sees "invalid license" can paste their blob again.
    """
    p = path or default_license_path()
    if not p.exists():
        return None
    try:
        return json.loads(p.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, UnicodeDecodeError) as e:
        raise ValueError(f"License file at {p} is corrupted: {e}") from e


def write_raw(data: dict[str, Any], path: Optional[Path] = None) -> Path:
    """Atomically write *data* to the license path.

    Atomic = write-to-temp-then-rename, so a crashed write doesn't
    leave a half-written license file that would fail verification on
    the next launch.
    """
    p = path or default_license_path()
    p.parent.mkdir(parents=True, exist_ok=True)
    tmp = p.with_suffix(p.suffix + ".tmp")
    tmp.write_text(
        json.dumps(data, indent=2, sort_keys=True), encoding="utf-8",
    )
    tmp.replace(p)
    return p


def remove(path: Optional[Path] = None) -> bool:
    """Delete the license file. Returns ``True`` if a file was
    removed, ``False`` if nothing was there. Used by the
    ``datatools-license deactivate`` command and by test cleanup."""
    p = path or default_license_path()
    try:
        p.unlink()
        return True
    except FileNotFoundError:
        return False
