# NOVA_CLI\nova_cli\nova_core\auth\storage.py

import os
import json
import time
from typing import Optional, Dict, Any

AUTH_DIR = os.path.expanduser("~/.nova")
AUTH_FILE = os.path.join(AUTH_DIR, "auth.json")

DEFAULT_EXPIRES_IN = 3600


def _now() -> float:
    return time.time()


def _read_json(path: str) -> Optional[Dict[str, Any]]:
    if not os.path.exists(path):
        return None
    try:
        with open(path, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return None


def save_auth(data: dict) -> None:
    os.makedirs(AUTH_DIR, exist_ok=True)

    # Normalize issued_at
    if "issued_at" not in data or data["issued_at"] in (None, ""):
        data["issued_at"] = _now()
    else:
        try:
            data["issued_at"] = float(data["issued_at"])
        except Exception:
            data["issued_at"] = _now()

    # Normalize expires_in
    if "expires_in" in data and data["expires_in"] not in (None, ""):
        try:
            data["expires_in"] = int(data["expires_in"])
        except Exception:
            data["expires_in"] = DEFAULT_EXPIRES_IN
    else:
        # if missing, keep whatever is there; access expiry will be treated conservatively
        data.setdefault("expires_in", DEFAULT_EXPIRES_IN)

    with open(AUTH_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, indent=2)


def load_auth() -> Optional[Dict[str, Any]]:
    return _read_json(AUTH_FILE)


def logout() -> None:
    if os.path.exists(AUTH_FILE):
        os.remove(AUTH_FILE)


def has_refresh_token() -> bool:
    data = load_auth()
    return bool(data and data.get("refresh_token"))


def _access_expired(data: Dict[str, Any]) -> bool:
    access = data.get("access_token")
    if not access:
        return True

    expires_in = data.get("expires_in")
    issued_at = data.get("issued_at")

    # If metadata missing, treat as expired so client will refresh safely.
    if expires_in in (None, "", 0) or issued_at in (None, ""):
        return True

    try:
        exp_ts = float(issued_at) + float(expires_in)
    except Exception:
        return True

    return _now() >= exp_ts


def is_logged_in() -> bool:
    """
    Logged-in means we have either:
      - a non-expired access token, OR
      - a refresh token (so we can get a new access token)
    """
    data = load_auth()
    if not data:
        return False

    if data.get("refresh_token"):
        return True

    # no refresh token -> must have valid access
    return not _access_expired(data)


def get_access_token() -> Optional[str]:
    data = load_auth()
    if not data:
        return None

    if _access_expired(data):
        return None

    return data.get("access_token")


def get_refresh_token() -> Optional[str]:
    data = load_auth()
    if not data:
        return None
    return data.get("refresh_token")


def update_tokens(
    access_token: str,
    refresh_token: str,
    expires_in: int = DEFAULT_EXPIRES_IN,
    issued_at: Optional[float] = None,
) -> None:
    data = load_auth() or {}
    data["access_token"] = access_token
    data["refresh_token"] = refresh_token
    data["expires_in"] = int(expires_in) if expires_in else DEFAULT_EXPIRES_IN
    data["issued_at"] = float(issued_at) if issued_at else _now()
    save_auth(data)


def update_access_token(
    access_token: str,
    expires_in: int = DEFAULT_EXPIRES_IN,
    issued_at: Optional[float] = None,
) -> None:
    data = load_auth() or {}
    data["access_token"] = access_token
    data["expires_in"] = int(expires_in) if expires_in else DEFAULT_EXPIRES_IN
    data["issued_at"] = float(issued_at) if issued_at else _now()
    save_auth(data)
