"""
Secure storage abstraction for credentials and keys.

Separates storage concerns from crypto logic so backends
can be swapped (filesystem, database, secure enclave).
"""

from __future__ import annotations

import json
import logging
import os
import stat
from pathlib import Path
from typing import Any

from .exceptions import WalletError

logger = logging.getLogger(__name__)

_DEFAULT_STORAGE_DIR = Path.home() / ".privacy-creds"


class FileStorage:
    """
    Filesystem-backed storage with restricted permissions.

    Files are created with owner-only read/write (0600 for secrets, 0644 for public).
    The storage directory itself is 0700.
    """

    def __init__(self, base_dir: Path | str | None = None):
        self.base_dir = Path(base_dir) if base_dir else _DEFAULT_STORAGE_DIR
        self._ensure_dir()

    def _ensure_dir(self) -> None:
        self.base_dir.mkdir(parents=True, exist_ok=True)
        # Owner-only access on the storage directory
        try:
            self.base_dir.chmod(stat.S_IRWXU)
        except OSError:
            logger.warning("Could not set directory permissions on %s", self.base_dir)

    def _path(self, filename: str) -> Path:
        # Prevent path traversal
        safe_name = Path(filename).name
        if safe_name != filename:
            raise WalletError(f"Invalid filename: {filename}")
        return self.base_dir / safe_name

    def write_bytes(self, filename: str, data: bytes, secret: bool = True) -> Path:
        """Write raw bytes. Secret files get 0600, public files get 0644."""
        path = self._path(filename)
        path.write_bytes(data)
        mode = stat.S_IRUSR | stat.S_IWUSR  # 0600
        if not secret:
            mode |= stat.S_IRGRP | stat.S_IROTH  # 0644
        try:
            path.chmod(mode)
        except OSError:
            logger.warning("Could not set file permissions on %s", path)
        return path

    def read_bytes(self, filename: str) -> bytes:
        path = self._path(filename)
        if not path.exists():
            raise WalletError(f"File not found: {path}")
        return path.read_bytes()

    def write_json(self, filename: str, data: Any, secret: bool = True) -> Path:
        raw = json.dumps(data, indent=2, sort_keys=True).encode()
        return self.write_bytes(filename, raw, secret=secret)

    def read_json(self, filename: str) -> Any:
        raw = self.read_bytes(filename)
        try:
            return json.loads(raw)
        except json.JSONDecodeError as exc:
            raise WalletError(f"Corrupt JSON in {filename}") from exc

    def exists(self, filename: str) -> bool:
        return self._path(filename).exists()

    def delete(self, filename: str) -> None:
        path = self._path(filename)
        if path.exists():
            # Overwrite before delete to prevent recovery
            path.write_bytes(os.urandom(path.stat().st_size))
            path.unlink()
