"""Reddit JSON API client with automatic rate limiting.

Uses curl_cffi with Chrome TLS impersonation to bypass Reddit's
TLS fingerprint detection. Python's default ssl module (used by httpx/requests)
gets 403'd because its TLS handshake differs from real browsers.
"""

from __future__ import annotations

import asyncio
import json
import re
from datetime import datetime
from typing import Any, Literal

from curl_cffi.requests import AsyncSession

from .config import RATE_LIMIT_FLOOR, RATE_LIMIT_MAX_SLEEP

# Type aliases for readability
SortType = Literal["hot", "new", "top", "rising"]
TimeFilter = Literal["hour", "day", "week", "month", "year", "all"]
SearchSort = Literal["relevance", "new", "hot", "top", "comments"]
CommentSort = Literal["best", "top", "new", "controversial", "old"]

_POST_ID_RE = re.compile(r"/comments/([a-z0-9]+)")
# Extract subreddit from various URL formats (r/name, /r/name, full URL)
_SUBREDDIT_URL_RE = re.compile(r"(?:https?://[^/]+)?/?r/([a-zA-Z0-9_\-]+)")
# Only allow safe subreddit characters (alphanumeric, underscore, hyphen)
_SAFE_NAME_RE = re.compile(r"^[a-zA-Z0-9_\-]+$")


def _parse_subreddit(value: str) -> str:
    """Parse subreddit from various input formats.

    Accepts:
      - bare name: "technology"
      - r/ prefix: "r/technology"
      - full URL: "https://www.reddit.com/r/technology"
      - post URL: "https://www.reddit.com/r/technology/comments/abc123/title"
    """
    value = value.strip().rstrip("/")
    # Try URL/r-prefix pattern first
    m = _SUBREDDIT_URL_RE.search(value)
    if m:
        return m.group(1)
    # Bare name
    if _SAFE_NAME_RE.match(value):
        return value
    raise ValueError(f"Invalid subreddit: {value!r}")


def _extract_post_id_and_subreddit(value: str) -> tuple[str, str | None]:
    """Extract post ID and optionally subreddit from a URL.

    Returns (post_id, subreddit_or_None).
    """
    # Full URL: extract both subreddit and post ID
    m = re.search(r"/r/([a-zA-Z0-9_\-]+)/comments/([a-z0-9]+)", value)
    if m:
        return m.group(2), m.group(1)
    # URL with just /comments/
    m2 = _POST_ID_RE.search(value)
    if m2:
        return m2.group(1), None
    # Bare ID
    raw = value.strip().removeprefix("t3_")
    if not re.match(r"^[a-z0-9]+$", raw):
        raise ValueError(f"Invalid post_id: {value!r}")
    return raw, None


def _utc_to_local(ts: float | None) -> str | None:
    """Convert Unix timestamp to local time string, e.g. '2026-03-26 09:20:07'."""
    if ts is None:
        return None
    return datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S")


def _extract_post_id(post_id_or_url: str) -> str:
    """Accept a bare post ID or full Reddit URL, return just the ID."""
    pid, _ = _extract_post_id_and_subreddit(post_id_or_url)
    return pid


class RedditClient:
    """Async Reddit JSON API client with cookie-based auth and rate limiting.

    Uses curl_cffi with Chrome TLS impersonation (via scrapling's approach)
    to avoid Reddit's TLS fingerprint detection.
    """

    def __init__(self, cookies: dict[str, str]) -> None:
        self._cookies = cookies
        self._session = AsyncSession(impersonate="chrome")
        # Rate limit state (updated from response headers)
        self.rate_remaining: float = 100.0
        self.rate_reset: int = 600

    async def close(self) -> None:
        await self._session.close()

    # -- public API -------------------------------------------------------------

    async def get_subreddit_posts(
        self,
        subreddit: str,
        sort: SortType = "hot",
        limit: int = 25,
        time_filter: TimeFilter = "day",
        after: str | None = None,
    ) -> dict[str, Any]:
        params: dict[str, str | int] = {"limit": max(1, min(limit, 100)), "raw_json": 1}
        if sort == "top":
            params["t"] = time_filter
        if after:
            params["after"] = after
        sub = _parse_subreddit(subreddit)
        data = await self._request(f"/r/{sub}/{sort}.json", params)
        return self._format_listing(data)

    async def get_post_content(
        self,
        post_id: str,
        comment_limit: int = 50,
        comment_depth: int = 3,
        comment_sort: CommentSort = "best",
    ) -> dict[str, Any]:
        pid = _extract_post_id(post_id)
        params: dict[str, str | int] = {
            "limit": max(1, min(comment_limit, 200)),
            "depth": max(1, min(comment_depth, 10)),
            "sort": comment_sort,
            "raw_json": 1,
        }
        data = await self._request(f"/comments/{pid}.json", params)

        # Reddit returns [post_listing, comments_listing]
        if not isinstance(data, list) or len(data) < 2:
            return {"post": None, "comments": [], "has_more": False}

        post_data = data[0]["data"]["children"]
        post = self._format_post(post_data[0]) if post_data else None

        comments_listing = data[1]["data"]
        comments = [
            self._format_comment(c) for c in comments_listing.get("children", [])
        ]
        # "more" object indicates truncated comments
        has_more = any(
            c.get("kind") == "more" for c in comments_listing.get("children", [])
        )

        return {"post": post, "comments": comments, "has_more": has_more}

    async def search_reddit(
        self,
        query: str,
        sort: SearchSort = "relevance",
        time_filter: TimeFilter = "all",
        limit: int = 25,
        subreddit: str | None = None,
        after: str | None = None,
    ) -> dict[str, Any]:
        params: dict[str, str | int] = {
            "q": query,
            "sort": sort,
            "t": time_filter,
            "limit": max(1, min(limit, 100)),
            "raw_json": 1,
        }
        if after:
            params["after"] = after

        if subreddit:
            params["restrict_sr"] = "on"
            sub = _parse_subreddit(subreddit)
            path = f"/r/{sub}/search.json"
        else:
            path = "/search.json"

        data = await self._request(path, params)
        listing = self._format_listing(data)
        # Rename 'posts' → 'results' for search
        return {"results": listing.get("posts", []), "after": listing.get("after")}

    async def get_subreddit_info(self, subreddit: str) -> dict[str, Any]:
        sub = _parse_subreddit(subreddit)
        data = await self._request(f"/r/{sub}/about.json", {"raw_json": 1})
        d = data.get("data", {})
        return {
            "name": d.get("display_name"),
            "title": d.get("title"),
            "description": d.get("public_description"),
            "subscribers": d.get("subscribers"),
            "active_users": d.get("accounts_active"),
            "created_utc": d.get("created_utc"),
            "created_time": _utc_to_local(d.get("created_utc")),
            "over18": d.get("over18"),
            "subreddit_type": d.get("subreddit_type"),
        }

    async def batch_get_posts_info(
        self,
        inputs: list[str],
        comment_limit: int = 10,
        comment_depth: int = 2,
    ) -> dict[str, Any]:
        """Batch fetch post details + comments. Each input can be a post ID or URL."""
        results: list[dict[str, Any]] = []
        errors: list[dict[str, Any]] = []
        for i, raw in enumerate(inputs):
            try:
                content = await self.get_post_content(
                    raw, comment_limit=comment_limit, comment_depth=comment_depth,
                )
                content["_input"] = raw
                results.append(content)
            except Exception as e:
                errors.append({"index": i + 1, "input": raw, "error": str(e)})
        return {
            "results": results,
            "errors": errors,
            "requested": len(inputs),
            "returned": len(results),
        }

    async def batch_get_subreddits_info(
        self,
        inputs: list[str],
    ) -> dict[str, Any]:
        """Batch fetch subreddit info. Each input can be a name, r/ prefix, or URL."""
        results: list[dict[str, Any]] = []
        errors: list[dict[str, Any]] = []
        for i, raw in enumerate(inputs):
            try:
                info = await self.get_subreddit_info(raw)
                info["_input"] = raw
                results.append(info)
            except Exception as e:
                errors.append({"index": i + 1, "input": raw, "error": str(e)})
        return {
            "results": results,
            "errors": errors,
            "requested": len(inputs),
            "returned": len(results),
        }

    async def check_me(self) -> dict[str, Any]:
        data = await self._request("/api/me.json", {})
        d = data.get("data", {})
        return {
            "username": d.get("name"),
            "link_karma": d.get("link_karma"),
            "comment_karma": d.get("comment_karma"),
        }

    def get_rate_status(self) -> dict[str, Any]:
        return {
            "remaining": self.rate_remaining,
            "reset_seconds": self.rate_reset,
            "used": 100 - self.rate_remaining,
        }

    # -- internal ---------------------------------------------------------------

    async def _request(
        self, path: str, params: dict[str, str | int]
    ) -> Any:
        """Make a rate-limited request to Reddit JSON API."""
        # Pre-request throttle if running low
        if self.rate_remaining < RATE_LIMIT_FLOOR and self.rate_remaining > 0:
            sleep_s = self.rate_reset / max(self.rate_remaining, 1)
            await asyncio.sleep(min(sleep_s, RATE_LIMIT_MAX_SLEEP))

        url = f"https://www.reddit.com{path}"
        resp = await self._session.get(
            url, params=params, cookies=self._cookies, timeout=30,
        )

        # Update rate limit state from headers (safe parse)
        try:
            self.rate_remaining = float(
                resp.headers.get("x-ratelimit-remaining", self.rate_remaining)
            )
        except (ValueError, TypeError):
            pass
        try:
            self.rate_reset = min(int(
                resp.headers.get("x-ratelimit-reset", self.rate_reset)
            ), 600)  # Cap at 10 minutes to prevent extreme sleeps
        except (ValueError, TypeError):
            pass

        # Handle 429 — wait and retry once
        if resp.status_code == 429:
            await asyncio.sleep(self.rate_reset + 1)
            resp = await self._session.get(
                url, params=params, cookies=self._cookies, timeout=30,
            )
            if resp.status_code == 429:
                raise RuntimeError("Rate limited by Reddit even after retry")

        # Handle 403 — cookies expired, try auto-refresh
        if resp.status_code == 403:
            from .cookie_manager import auto_refresh_cookies
            new_cookies = await auto_refresh_cookies()
            if new_cookies:
                self._cookies = new_cookies
                resp = await self._session.get(
                    url, params=params, cookies=self._cookies, timeout=30,
                )
                if resp.status_code == 403:
                    raise RuntimeError(
                        "Reddit returned 403 even after cookie refresh. "
                        "Run `reddit-mcp setup-cookies` to re-login manually."
                    )
            else:
                raise RuntimeError(
                    "Reddit returned 403 and auto-refresh failed. "
                    "Run `reddit-mcp setup-cookies` to re-login."
                )

        if resp.status_code != 200:
            raise RuntimeError(f"Reddit returned {resp.status_code}: {resp.text[:200]}")

        return json.loads(resp.text)

    # -- formatters -------------------------------------------------------------

    def _format_listing(self, data: Any) -> dict[str, Any]:
        """Format a Reddit listing response into {posts, after}."""
        if not isinstance(data, dict) or "data" not in data:
            return {"posts": [], "after": None}
        listing = data["data"]
        posts = [
            self._format_post(child)
            for child in listing.get("children", [])
        ]
        return {"posts": posts, "after": listing.get("after")}

    @staticmethod
    def _format_post(child: dict) -> dict[str, Any]:
        """Extract useful fields from a post's t3 wrapper."""
        d = child.get("data", {})
        return {
            "id": d.get("id"),
            "title": d.get("title"),
            "author": d.get("author"),
            "subreddit": d.get("subreddit"),
            "score": d.get("score"),
            "upvote_ratio": d.get("upvote_ratio"),
            "num_comments": d.get("num_comments"),
            "created_utc": d.get("created_utc"),
            "created_time": _utc_to_local(d.get("created_utc")),
            "link_url": d.get("url"),
            "reddit_url": f"https://www.reddit.com{d.get('permalink', '')}",
            "body": d.get("selftext", ""),
            "is_text_post": d.get("is_self"),
            "tag": d.get("link_flair_text"),
            "over_18": d.get("over_18"),
        }

    @staticmethod
    def _format_comment(child: dict) -> dict[str, Any]:
        """Extract useful fields from a comment wrapper."""
        if child.get("kind") == "more":
            return {"kind": "more", "count": child.get("data", {}).get("count", 0)}
        d = child.get("data", {})
        result: dict[str, Any] = {
            "id": d.get("id"),
            "author": d.get("author"),
            "body": d.get("body", ""),
            "score": d.get("score"),
            "created_utc": d.get("created_utc"),
            "created_time": _utc_to_local(d.get("created_utc")),
            "depth": d.get("depth", 0),
        }
        # Recursively format replies
        replies = d.get("replies")
        if isinstance(replies, dict):
            children = replies.get("data", {}).get("children", [])
            result["replies"] = [
                RedditClient._format_comment(c) for c in children
            ]
        else:
            result["replies"] = []
        return result
