from __future__ import annotations

from abc import ABC, abstractmethod
import json
from pathlib import Path
import stat

from .models import AgentState


class SecureStorage(ABC):
    """Storage abstraction for local secure state without token leaks."""

    @abstractmethod
    def save_state(self, state: AgentState) -> None:
        raise NotImplementedError

    @abstractmethod
    def load_state(self) -> AgentState | None:
        raise NotImplementedError

    @abstractmethod
    def clear_pair_token(self) -> None:
        raise NotImplementedError

    @abstractmethod
    def clear_paired_state(self) -> None:
        """Remove persisted device credentials (local unpair)."""
        raise NotImplementedError


class InMemorySecureStorage(SecureStorage):
    def __init__(self) -> None:
        self._state: AgentState | None = None
        self._ephemeral_pair_token: str | None = None

    def set_ephemeral_pair_token(self, pair_token: str) -> None:
        self._ephemeral_pair_token = pair_token

    def save_state(self, state: AgentState) -> None:
        self._state = state

    def load_state(self) -> AgentState | None:
        return self._state

    def clear_pair_token(self) -> None:
        self._ephemeral_pair_token = None

    def clear_paired_state(self) -> None:
        self._state = None
        self._ephemeral_pair_token = None


class DesktopKeychainStoragePlaceholder(InMemorySecureStorage):
    """Placeholder for OS keychain integration (Windows/macOS/Linux)."""


class ServerSecureFileStoragePlaceholder(InMemorySecureStorage):
    """Placeholder for owner-only config file storage (0600 planned)."""


class JsonFileSecureStorage(SecureStorage):
    """Simple owner-only JSON storage for paired passive agent state."""

    def __init__(self, file_path: str | Path) -> None:
        self._path = Path(file_path)
        self._ephemeral_pair_token: str | None = None

    @property
    def file_path(self) -> Path:
        return self._path

    def set_ephemeral_pair_token(self, pair_token: str) -> None:
        self._ephemeral_pair_token = pair_token

    def save_state(self, state: AgentState) -> None:
        self._path.parent.mkdir(parents=True, exist_ok=True)
        payload = {
            "device_id": state.device_id,
            "device_auth_token": state.device_auth_token,
            "heartbeat_secret": state.heartbeat_secret,
        }
        self._path.write_text(
            json.dumps(payload, ensure_ascii=True, sort_keys=True, separators=(",", ":")),
            encoding="utf-8",
        )
        self._enforce_0600_permissions()

    def load_state(self) -> AgentState | None:
        if not self._path.exists():
            return None
        try:
            raw = self._path.read_text(encoding="utf-8")
            data = json.loads(raw)
            device_id = str(data.get("device_id") or data.get("local_device_id") or "").strip()
            device_auth_token = str(
                data.get("device_auth_token")
                or data.get("trust_token")
                or data.get("access_token")
                or ""
            ).strip()
            heartbeat_secret = str(data.get("heartbeat_secret") or "").strip()

            if not device_id or not device_auth_token or not heartbeat_secret:
                return None
            return AgentState(
                device_id=device_id,
                device_auth_token=device_auth_token,
                heartbeat_secret=heartbeat_secret,
            )
        except Exception:
            # Corrupted or invalid state file: treat as unpaired
            return None

    def clear_pair_token(self) -> None:
        self._ephemeral_pair_token = None

    def clear_paired_state(self) -> None:
        self._ephemeral_pair_token = None
        if self._path.exists():
            self._path.unlink()

    def _enforce_0600_permissions(self) -> None:
        self._path.chmod(0o600)
        mode = stat.S_IMODE(self._path.stat().st_mode)
        if mode != 0o600:
            raise PermissionError(f"Expected 0o600 permissions, got {oct(mode)}")
