"""
store.py — Encrypted persistent config backed by a single JSON file on disk.

Rules:
  • ai_api_key is always stored encrypted; decrypted transparently on read.
  • The Fernet symmetric key lives in ENCRYPTION_KEY_FILE (mode 0o600).
  • Module-level singleton: the file is parsed only once per process.
  • exists() returns True only when setup_complete == True.
"""
from __future__ import annotations

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

from cryptography.fernet import Fernet

from .config import CONFIG_FILE, DATA_DIR, ENCRYPTION_KEY_FILE

# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------

def _ensure_dirs() -> None:
    DATA_DIR.mkdir(parents=True, exist_ok=True)


def _load_or_create_fernet() -> Fernet:
    """Return a Fernet instance, generating + persisting the key if needed."""
    _ensure_dirs()
    if ENCRYPTION_KEY_FILE.exists():
        key = ENCRYPTION_KEY_FILE.read_bytes().strip()
    else:
        key = Fernet.generate_key()
        ENCRYPTION_KEY_FILE.write_bytes(key)
        # chmod 600 — owner read/write only
        ENCRYPTION_KEY_FILE.chmod(stat.S_IRUSR | stat.S_IWUSR)
    return Fernet(key)


def _read_raw() -> dict:
    if not CONFIG_FILE.exists():
        return {}
    try:
        return json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError):
        return {}


def _write_raw(data: dict) -> None:
    _ensure_dirs()
    CONFIG_FILE.write_text(
        json.dumps(data, indent=2, ensure_ascii=False),
        encoding="utf-8",
    )


# ---------------------------------------------------------------------------
# ConfigStore
# ---------------------------------------------------------------------------

class ConfigStore:
    """
    Thin wrapper around the single JSON config file.

    Usage:
        store = ConfigStore()
        store.set("telegram_token", "123:ABC")
        token = store.get("telegram_token")
        store.set("ai_api_key", "sk-...")   # auto-encrypted
        key = store.get("ai_api_key")       # auto-decrypted
    """

    _ENCRYPTED_FIELDS = {"ai_api_key"}

    def __init__(self) -> None:
        self._fernet: Fernet = _load_or_create_fernet()
        self._cache: dict | None = None   # loaded lazily, once per process

    # ------------------------------------------------------------------
    # Private
    # ------------------------------------------------------------------

    def _load(self) -> dict:
        if self._cache is None:
            self._cache = _read_raw()
        return self._cache

    def _save(self) -> None:
        _write_raw(self._cache or {})

    def _encrypt(self, value: str) -> str:
        return self._fernet.encrypt(value.encode()).decode()

    def _decrypt(self, value: str) -> str:
        try:
            return self._fernet.decrypt(value.encode()).decode()
        except Exception:
            # Value may already be plaintext (migration / manual edit)
            return value

    def _is_encrypted(self, value: str) -> bool:
        """Fernet tokens start with 'gAAAAA'."""
        return isinstance(value, str) and value.startswith("gAAAAA")

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def get(self, key: str, default: Any = None) -> Any:
        """Return the value for *key*, decrypting if necessary."""
        data = self._load()
        value = data.get(key, default)
        if key in self._ENCRYPTED_FIELDS and isinstance(value, str) and self._is_encrypted(value):
            return self._decrypt(value)
        return value

    def set(self, key: str, value: Any) -> None:
        """Persist *key* → *value*, encrypting sensitive fields."""
        data = self._load()
        if key in self._ENCRYPTED_FIELDS and isinstance(value, str) and not self._is_encrypted(value):
            value = self._encrypt(value)
        data[key] = value
        self._save()

    def all(self) -> dict:
        """
        Return a copy of the entire config dict with sensitive fields decrypted.
        Safe for internal use; never expose raw to untrusted callers.
        """
        data = dict(self._load())
        for field in self._ENCRYPTED_FIELDS:
            if field in data and isinstance(data[field], str) and self._is_encrypted(data[field]):
                data[field] = self._decrypt(data[field])
        return data

    def exists(self) -> bool:
        """True only when setup has been completed."""
        return bool(self._load().get("setup_complete", False))

    def reset(self) -> None:
        """Wipe the config (used by tests / re-init)."""
        self._cache = {}
        self._save()


# ---------------------------------------------------------------------------
# Module-level singleton
# ---------------------------------------------------------------------------

_store: ConfigStore | None = None


def get_store() -> ConfigStore:
    """Return the process-wide singleton ConfigStore."""
    global _store
    if _store is None:
        _store = ConfigStore()
    return _store
