"""Base HTTP client for the Edge SDK."""

from __future__ import annotations

import re
from datetime import date, datetime, timedelta
from typing import Any

import httpx
import pandas as pd

from edge.config import EdgeConfig
from edge.exceptions import (
    AuthenticationError,
    DataNotFoundError,
    EdgeAPIError,
    ServiceUnavailableError,
    ValidationError,
)

# ── Relative date parsing ────────────────────────────────────────────────────

_RELATIVE_RE = re.compile(r"^(\d+)([YMWD])$", re.IGNORECASE)
_NAMED_SHORTCUTS = {"YTD", "MTD", "QTD"}


def _parse_relative_date(value: str) -> date | None:
    """Convert shorthand like ``1Y``, ``6M``, ``YTD`` to an absolute date.

    Returns None if *value* is not a recognised shorthand (i.e. it is
    already an ISO date string).
    """
    upper = value.strip().upper()
    today = date.today()

    if upper == "YTD":
        return date(today.year, 1, 1)
    if upper == "MTD":
        return date(today.year, today.month, 1)
    if upper == "QTD":
        q_month = ((today.month - 1) // 3) * 3 + 1
        return date(today.year, q_month, 1)

    m = _RELATIVE_RE.match(upper)
    if not m:
        return None

    n, unit = int(m.group(1)), m.group(2)
    if unit == "D":
        return today - timedelta(days=n)
    if unit == "W":
        return today - timedelta(weeks=n)
    if unit == "M":
        month = today.month - n
        year = today.year
        while month < 1:
            month += 12
            year -= 1
        day = min(today.day, 28)  # safe for all months
        return date(year, month, day)
    if unit == "Y":
        return date(today.year - n, today.month, min(today.day, 28))
    return None


# ── Symbol helpers ───────────────────────────────────────────────────────────

def _expand_symbol(symbol: str) -> str:
    """Expand a bare root symbol to front-month continuous.

    ``"ZC"`` → ``"ZC*1"``, but ``"ZC*1"``, ``"ZCZ26"``, ``"ZCZ26|500C"``
    are returned unchanged.
    """
    from edge.commodities import COMMODITIES

    s = symbol.strip()
    if s in COMMODITIES:
        return f"{s}*1"
    return s


def _expand_symbols(symbols: str | list[str]) -> list[str]:
    """Expand and normalise symbol input to a list."""
    if isinstance(symbols, str):
        return [_expand_symbol(s.strip()) for s in symbols.split(",")]
    return [_expand_symbol(s) for s in symbols]


def _infer_exchange(symbols: list[str]) -> str:
    """Best-effort exchange inference from the first recognised root."""
    from edge.commodities import infer_exchange

    for sym in symbols:
        ex = infer_exchange(sym)
        if ex != "CME":
            return ex
    return "CME"


# ── Base client ──────────────────────────────────────────────────────────────

class BaseClient:
    """Low-level HTTP client shared by all services."""

    def __init__(self, config: EdgeConfig):
        self.config = config
        self.client = httpx.Client(
            base_url=config.api_url,
            headers=config.headers,
            timeout=config.timeout,
        )

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    def close(self):
        self.client.close()

    # ── HTTP helpers ─────────────────────────────────────────────────────────

    def _handle_response(self, response: httpx.Response) -> Any:
        if response.status_code == 200:
            return response.json()

        detail = ""
        try:
            body = response.json()
            detail = body.get("detail", body.get("error", ""))
        except Exception:
            detail = response.text

        code = response.status_code
        if code == 401:
            raise AuthenticationError(detail or "Authentication failed", status_code=code)
        if code == 404:
            raise DataNotFoundError(detail or "Resource not found", status_code=code)
        if code in (400, 422):
            raise ValidationError(detail or "Validation error", status_code=code)
        if code == 503:
            raise ServiceUnavailableError(detail or "Service unavailable", status_code=code)
        raise EdgeAPIError(f"API error ({code}): {detail}", status_code=code)

    def _get(self, endpoint: str, params: dict | None = None) -> Any:
        response = self.client.get(endpoint, params=params)
        return self._handle_response(response)

    def _post(self, endpoint: str, json: dict | None = None) -> Any:
        response = self.client.post(endpoint, json=json)
        return self._handle_response(response)

    # ── Conversion helpers ───────────────────────────────────────────────────

    @staticmethod
    def _to_dataframe(data: list[dict]) -> pd.DataFrame:
        """Convert a list of dicts to a DataFrame with auto date parsing."""
        if not data:
            return pd.DataFrame()
        df = pd.DataFrame(data)
        for col in df.columns:
            if col in ("ts_event", "date", "event_ts", "trade_date", "start_ts", "end_ts"):
                df[col] = pd.to_datetime(df[col], errors="coerce")
        return df

    @staticmethod
    def _format_symbols(symbols: str | list[str]) -> str:
        """Normalize symbol input to a comma-separated string."""
        if isinstance(symbols, list):
            return ",".join(s.strip() for s in symbols)
        return symbols.strip()

    @staticmethod
    def _format_date(value: str | date | datetime | None) -> str | None:
        """Format a date value, resolving relative shorthands first."""
        if value is None:
            return None
        if isinstance(value, str):
            resolved = _parse_relative_date(value)
            if resolved is not None:
                return resolved.isoformat()
            return value
        if isinstance(value, datetime):
            return value.strftime("%Y-%m-%d")
        if isinstance(value, date):
            return value.isoformat()
        return str(value)
