"""
Saxo OpenAPI OAuth2 Authorization Code Grant — SIM or Live, set via SAXO_ENVIRONMENT.

Access tokens last ~20 min, refresh tokens ~40 min (SIM figures — Live may differ), so a
background scheduler (services/saxo_scheduler.py) must proactively refresh well before
either expires — this module only exposes the primitives (build the login URL, exchange
code, refresh, read a currently-valid token).
"""
import os
import secrets
import logging
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, Optional

import httpx

logger = logging.getLogger(__name__)

SAXO_APP_KEY = os.environ.get("SAXO_APP_KEY", "")
SAXO_APP_SECRET = os.environ.get("SAXO_APP_SECRET", "")
SAXO_REDIRECT_URI = os.environ.get("SAXO_REDIRECT_URI", "https://openfin.open-squared.tech/oauth/saxo/callback")

# SAXO_ENVIRONMENT=sim (default) or live — picks the matching auth/API host pair.
# Explicit SAXO_AUTH_BASE_URL/SAXO_API_BASE_URL env vars still win if set, for edge cases.
SAXO_ENVIRONMENT = os.environ.get("SAXO_ENVIRONMENT", "sim").strip().lower()
_DEFAULT_HOSTS = {
    "sim":  ("https://sim.logonvalidation.net", "https://gateway.saxobank.com/sim/openapi"),
    "live": ("https://live.logonvalidation.net", "https://gateway.saxobank.com/openapi"),
}
_default_auth, _default_api = _DEFAULT_HOSTS.get(SAXO_ENVIRONMENT, _DEFAULT_HOSTS["sim"])
SAXO_AUTH_BASE_URL = os.environ.get("SAXO_AUTH_BASE_URL", _default_auth)
SAXO_API_BASE_URL = os.environ.get("SAXO_API_BASE_URL", _default_api)

# In-memory CSRF state for the OAuth login->callback round trip (single-user desktop app).
_pending_state: Optional[str] = None


def is_configured() -> bool:
    return bool(SAXO_APP_KEY and SAXO_APP_SECRET)


def new_login_state() -> str:
    global _pending_state
    _pending_state = secrets.token_urlsafe(24)
    return _pending_state


def consume_and_verify_state(state: str) -> bool:
    global _pending_state
    ok = bool(_pending_state) and secrets.compare_digest(_pending_state, state or "")
    _pending_state = None
    return ok


def build_authorize_url(state: str) -> str:
    from urllib.parse import urlencode
    params = {
        "response_type": "code",
        "client_id": SAXO_APP_KEY,
        "redirect_uri": SAXO_REDIRECT_URI,
        "state": state,
    }
    return f"{SAXO_AUTH_BASE_URL}/authorize?{urlencode(params)}"


def _store_token_response(token_resp: Dict[str, Any]):
    from services.database import save_saxo_tokens
    now = datetime.now(timezone.utc)
    expires_at = (now + timedelta(seconds=int(token_resp["expires_in"]))).isoformat()
    refresh_expires_at = (now + timedelta(seconds=int(token_resp["refresh_token_expires_in"]))).isoformat()
    save_saxo_tokens(
        access_token=token_resp["access_token"],
        refresh_token=token_resp["refresh_token"],
        expires_at=expires_at,
        refresh_expires_at=refresh_expires_at,
        environment=SAXO_ENVIRONMENT,
    )


def exchange_code_for_tokens(code: str) -> Dict[str, Any]:
    resp = httpx.post(
        f"{SAXO_AUTH_BASE_URL}/token",
        data={
            "grant_type": "authorization_code",
            "code": code,
            "redirect_uri": SAXO_REDIRECT_URI,
        },
        auth=(SAXO_APP_KEY, SAXO_APP_SECRET),
        headers={"Content-Type": "application/x-www-form-urlencoded"},
        timeout=15,
    )
    resp.raise_for_status()
    token_resp = resp.json()
    _store_token_response(token_resp)
    logger.info("[Saxo] Connected — tokens stored")
    return token_resp


def refresh_tokens() -> Optional[Dict[str, Any]]:
    from services.database import get_saxo_tokens
    stored = get_saxo_tokens()
    if not stored or not stored.get("refresh_token"):
        return None

    resp = httpx.post(
        f"{SAXO_AUTH_BASE_URL}/token",
        data={
            "grant_type": "refresh_token",
            "refresh_token": stored["refresh_token"],
            "redirect_uri": SAXO_REDIRECT_URI,
        },
        auth=(SAXO_APP_KEY, SAXO_APP_SECRET),
        headers={"Content-Type": "application/x-www-form-urlencoded"},
        timeout=15,
    )
    resp.raise_for_status()
    token_resp = resp.json()
    _store_token_response(token_resp)
    logger.info("[Saxo] Token refreshed")
    return token_resp


def get_valid_access_token() -> Optional[str]:
    from services.database import get_saxo_tokens
    stored = get_saxo_tokens()
    if not stored or not stored.get("access_token"):
        return None

    expires_at = datetime.fromisoformat(stored["expires_at"])
    if datetime.now(timezone.utc) < expires_at - timedelta(seconds=60):
        return stored["access_token"]

    try:
        refreshed = refresh_tokens()
    except Exception as e:
        # Refresh token rejected — clear the dead session instead of leaving a token that
        # looks "connected" in /status but gets 401'd on the next real API call.
        logger.error(f"[Saxo] Refresh failed on-demand, clearing dead session: {e}")
        disconnect()
        return None
    return refreshed["access_token"] if refreshed else None


def get_status() -> Dict[str, Any]:
    from services.database import get_saxo_tokens
    stored = get_saxo_tokens()
    if not stored or not stored.get("access_token"):
        return {"connected": False, "configured": is_configured(), "environment": SAXO_ENVIRONMENT}
    return {
        "connected": True,
        "configured": is_configured(),
        "environment": stored.get("environment", "sim"),
        "expires_at": stored.get("expires_at"),
        "refresh_expires_at": stored.get("refresh_expires_at"),
    }


def disconnect():
    from services.database import clear_saxo_tokens
    clear_saxo_tokens()
