"""HTTP session management — creation, warm-up, and proxy rotation.

Key improvements over original:
- Session is created ONCE per fetch operation, not once per retry attempt.
- Warm-up failures are non-fatal and don't create new sessions.
- Proxy rotation is deterministic (round-robin) not purely random.
- Sessions are properly closed when context manager exits.
"""

from __future__ import annotations

import logging
import random
import time
from typing import TYPE_CHECKING, Any

from curl_cffi import requests as cffi_requests

from indiaopt.constants import (
    DEFAULT_WARMUP_TIMEOUT_S,
    NSE_BASE_URL,
    NSE_WARMUP_URLS,
    USER_AGENTS,
)

if TYPE_CHECKING:
    from indiaopt.config.settings import Settings

logger = logging.getLogger("indiaopt.session")


def pick_proxy(proxy_urls: list[str]) -> dict[str, str] | None:
    """Return a randomly selected proxy dict, or ``None`` if no proxies configured.

    Args:
        proxy_urls: List of proxy URL strings.

    Returns:
        ``{"http": url, "https": url}`` or ``None``.
    """
    if not proxy_urls:
        return None
    url = random.choice(proxy_urls)
    return {"http": url, "https": url}


def create_nse_session(
    proxy_urls: list[str] | None = None,
    warmup_timeout: float = DEFAULT_WARMUP_TIMEOUT_S,
    warmup: bool = True,
) -> Any:
    """Create and warm up an NSE-compatible curl_cffi session.

    Performs two GET requests to NSE's landing pages so that the session
    accumulates the cookies NSE requires for API calls.

    Args:
        proxy_urls:      Optional list of proxy URLs to rotate over.
        warmup_timeout:  Timeout for each warm-up request in seconds.
        warmup:          Set to ``False`` to skip warm-up (e.g. in tests).

    Returns:
        A ready-to-use :class:`curl_cffi.requests.Session`.
    """
    session: Any = cffi_requests.Session(impersonate="chrome124")
    session.headers.update(
        {
            "User-Agent": random.choice(USER_AGENTS),
            "Accept": "application/json, text/plain, */*",
            "Accept-Language": "en-US,en;q=0.9",
            "Accept-Encoding": "gzip, deflate, br",
            "Referer": f"{NSE_BASE_URL}/option-chain",
            "Connection": "keep-alive",
            "Cache-Control": "no-cache",
            "Pragma": "no-cache",
        }
    )

    if proxy_urls:
        proxies = pick_proxy(proxy_urls)
        if proxies:
            session.proxies.update(proxies)

    if warmup:
        for url in NSE_WARMUP_URLS:
            try:
                resp = session.get(url, timeout=warmup_timeout)
                logger.debug(
                    "Warm-up GET %s → HTTP %d", url, resp.status_code
                )
            except Exception as exc:
                # Non-fatal: NSE may still accept API calls even if landing page fails
                logger.debug("Warm-up request to %s failed (non-fatal): %s", url, exc)
            time.sleep(0.3)

    return session


def create_bse_session(
    proxy_urls: list[str] | None = None,
    warmup_timeout: float = DEFAULT_WARMUP_TIMEOUT_S,
    warmup: bool = True,
) -> Any:
    """Create a BSE-compatible curl_cffi session.

    Args:
        proxy_urls:     Optional list of proxy URLs.
        warmup_timeout: Timeout for warm-up requests.
        warmup:         Set to ``False`` to skip warm-up.

    Returns:
        A ready-to-use :class:`curl_cffi.requests.Session`.
    """
    from indiaopt.constants import BSE_BASE_URL

    session: Any = cffi_requests.Session(impersonate="chrome124")
    session.headers.update(
        {
            "User-Agent": random.choice(USER_AGENTS),
            "Accept": "application/json, text/plain, */*",
            "Accept-Language": "en-US,en;q=0.9",
            "Referer": BSE_BASE_URL,
            "Connection": "keep-alive",
        }
    )

    if proxy_urls:
        proxies = pick_proxy(proxy_urls)
        if proxies:
            session.proxies.update(proxies)

    if warmup:
        try:
            resp = session.get(BSE_BASE_URL, timeout=warmup_timeout)
            logger.debug("BSE warm-up GET %s → HTTP %d", BSE_BASE_URL, resp.status_code)
        except Exception as exc:
            logger.debug("BSE warm-up failed (non-fatal): %s", exc)

    return session


class SessionManager:
    """Context manager that owns a session lifetime.

    Ensures the session is properly closed even if an exception occurs::

        with SessionManager.for_nse(settings) as session:
            resp = session.get(url, timeout=15)
    """

    def __init__(self, session: Any) -> None:
        self._session = session

    def __enter__(self) -> Any:
        return self._session

    def __exit__(self, *_: object) -> None:
        try:
            self._session.close()
        except Exception:
            pass  # best-effort cleanup

    @classmethod
    def for_nse(cls, settings: Settings | None = None) -> SessionManager:
        """Create a :class:`SessionManager` with an NSE-warmed session."""
        proxy_urls = settings.proxy_urls if settings else []
        warmup_timeout = settings.warmup_timeout if settings else DEFAULT_WARMUP_TIMEOUT_S
        session = create_nse_session(
            proxy_urls=proxy_urls,
            warmup_timeout=warmup_timeout,
        )
        return cls(session)

    @classmethod
    def for_bse(cls, settings: Settings | None = None) -> SessionManager:
        """Create a :class:`SessionManager` with a BSE-warmed session."""
        proxy_urls = settings.proxy_urls if settings else []
        warmup_timeout = settings.warmup_timeout if settings else DEFAULT_WARMUP_TIMEOUT_S
        session = create_bse_session(
            proxy_urls=proxy_urls,
            warmup_timeout=warmup_timeout,
        )
        return cls(session)


__all__ = [
    "SessionManager",
    "create_bse_session",
    "create_nse_session",
    "pick_proxy",
]
