"""Persist and restore sessions to/from local JSON files.

Files are stored at:
    $XDG_CONFIG_HOME/isard/   (Linux/macOS default: ~/.config/isard/)
    %APPDATA%\\isard\\          (Windows)

  session.json      — Gencat SAML AuthSession (user info, SAML expiry)
  isardvdi.json     — IsardVDI JWT session (token, expiry)

File permissions are set to 0o600 (owner read/write only) on POSIX systems
because the files contain tokens and cookies.
"""

from __future__ import annotations

import json
import os
import platform
import stat
from datetime import datetime
from pathlib import Path
from typing import Optional

from .types import AuthSession, IsardVdiSession, UserInfo


def _config_dir() -> Path:
    """Return the platform-appropriate config directory for isard."""
    if platform.system() == "Windows":
        base = Path(os.environ.get("APPDATA", Path.home()))
    else:
        base = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"))
    return base / "isard"


def _session_path() -> Path:
    return _config_dir() / "session.json"


def _isardvdi_session_path() -> Path:
    return _config_dir() / "isardvdi.json"


def _parse_dt(value: str) -> datetime:
    return datetime.fromisoformat(value)


def _fmt_dt(value: datetime) -> str:
    return value.isoformat()


def _write_secure(path: Path, data: dict) -> None:
    """Write *data* as JSON to *path* with 0o600 permissions on POSIX."""
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(data, indent=2), encoding="utf-8")
    if platform.system() != "Windows":
        path.chmod(stat.S_IRUSR | stat.S_IWUSR)


# ---------------------------------------------------------------------------
# Gencat SAML session (AuthSession)
# ---------------------------------------------------------------------------


def _auth_session_to_dict(session: AuthSession) -> dict:
    ui = session.user_info
    return {
        "user_info": {
            "username": ui.username,
            "display_name": ui.display_name,
            "email": ui.email,
            "given_name": ui.given_name,
            "family_name": ui.family_name,
            "organization": ui.organization,
            "role": ui.role,
            "saml_attributes": ui.saml_attributes,
        },
        "expires_at": _fmt_dt(session.expires_at),
        "session_id": session.session_id,
        "access_token": session.access_token,
        "refresh_token": session.refresh_token,
        "cookies": session.cookies,
        "attributes": session.attributes,
        "created_at": _fmt_dt(session.created_at),
        "last_refreshed_at": (
            _fmt_dt(session.last_refreshed_at)
            if session.last_refreshed_at is not None
            else None
        ),
    }


def _auth_session_from_dict(data: dict) -> AuthSession:
    ui_data = data["user_info"]
    user_info = UserInfo(
        username=ui_data["username"],
        display_name=ui_data["display_name"],
        email=ui_data["email"],
        given_name=ui_data.get("given_name"),
        family_name=ui_data.get("family_name"),
        organization=ui_data.get("organization"),
        role=ui_data.get("role"),
        saml_attributes=ui_data.get("saml_attributes", {}),
    )
    last_refreshed_raw = data.get("last_refreshed_at")
    return AuthSession(
        user_info=user_info,
        expires_at=_parse_dt(data["expires_at"]),
        session_id=data["session_id"],
        access_token=data.get("access_token"),
        refresh_token=data.get("refresh_token"),
        cookies=data.get("cookies", {}),
        attributes=data.get("attributes", {}),
        created_at=_parse_dt(data["created_at"]),
        last_refreshed_at=(
            _parse_dt(last_refreshed_raw) if last_refreshed_raw is not None else None
        ),
    )


def save_session(session: AuthSession) -> Path:
    """Serialise the Gencat SAML *session* to disk and return the file path."""
    path = _session_path()
    _write_secure(path, _auth_session_to_dict(session))
    return path


def load_session() -> Optional[AuthSession]:
    """Load and return the cached Gencat session, or *None* if absent or expired."""
    path = _session_path()
    if not path.exists():
        return None
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        session = _auth_session_from_dict(data)
    except Exception:
        return None
    return session if session.is_valid() else None


# ---------------------------------------------------------------------------
# IsardVDI JWT session (IsardVdiSession)
# ---------------------------------------------------------------------------


def _isardvdi_session_to_dict(session: IsardVdiSession) -> dict:
    return {
        "token": session.token,
        "username": session.username,
        "expires_at": _fmt_dt(session.expires_at),
    }


def _isardvdi_session_from_dict(data: dict) -> IsardVdiSession:
    return IsardVdiSession(
        token=data["token"],
        username=data["username"],
        expires_at=_parse_dt(data["expires_at"]),
    )


def save_isardvdi_session(session: IsardVdiSession) -> Path:
    """Serialise the IsardVDI JWT *session* to disk and return the file path."""
    path = _isardvdi_session_path()
    _write_secure(path, _isardvdi_session_to_dict(session))
    return path


def load_isardvdi_session() -> Optional[IsardVdiSession]:
    """Load and return the cached IsardVDI session, or *None* if absent or expired."""
    path = _isardvdi_session_path()
    if not path.exists():
        return None
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        session = _isardvdi_session_from_dict(data)
    except Exception:
        return None
    return session if session.is_valid() else None


# ---------------------------------------------------------------------------
# Clear
# ---------------------------------------------------------------------------


def clear_session() -> bool:
    """Delete both cached session files. Returns True if any file was removed."""
    removed = False
    for path in (_session_path(), _isardvdi_session_path()):
        if path.exists():
            path.unlink()
            removed = True
    return removed
