from __future__ import annotations

import os
from typing import Any, Dict, Optional, Tuple

import httpx

from socialrails.exceptions import SocialRailsError
from socialrails.resources.posts import Posts, AsyncPosts
from socialrails.resources.analytics import Analytics, AsyncAnalytics
from socialrails.resources.accounts import Accounts, AsyncAccounts
from socialrails.resources.ai import AI, AsyncAI
from socialrails.resources.workspace import WorkspaceResource, AsyncWorkspaceResource
from socialrails.resources.media import Media, AsyncMedia
from socialrails.resources.webhooks import Webhooks, AsyncWebhooks

DEFAULT_BASE_URL = "https://socialrails.com/api/v1"


class _BaseClient:
    """Shared setup for both sync and async clients."""

    def __init__(
        self,
        api_key: Optional[str] = None,
        base_url: str = DEFAULT_BASE_URL,
    ) -> None:
        resolved_key = api_key or os.environ.get("SOCIALRAILS_API_KEY")
        if not resolved_key:
            raise ValueError(
                "No API key provided. Pass api_key= to the constructor "
                "or set the SOCIALRAILS_API_KEY environment variable."
            )
        self._api_key = resolved_key
        self._base_url = base_url.rstrip("/")
        self._headers = {
            "Authorization": f"Bearer {self._api_key}",
            "Content-Type": "application/json",
        }

    # ------------------------------------------------------------------
    # Shared helpers
    # ------------------------------------------------------------------

    def _build_url(self, path: str) -> str:
        return f"{self._base_url}{path}"

    @staticmethod
    def _handle_response(response: httpx.Response) -> Any:
        """Parse a response, raising SocialRailsError on non-2xx status."""
        if response.status_code >= 200 and response.status_code < 300:
            data = response.json()
            # The API wraps successful responses in {"data": ...}
            if isinstance(data, dict) and "data" in data:
                return data["data"]
            return data

        # Error path
        try:
            body = response.json()
        except Exception:
            raise SocialRailsError(
                message=response.text or "Unknown error",
                code="unknown",
                status=response.status_code,
            )

        error = body.get("error", {})
        if isinstance(error, dict):
            raise SocialRailsError(
                message=error.get("message", "Unknown error"),
                code=error.get("code", "unknown"),
                status=response.status_code,
            )
        raise SocialRailsError(
            message=str(error),
            code="unknown",
            status=response.status_code,
        )


class SocialRails(_BaseClient):
    """Synchronous SocialRails API client.

    Usage::

        from socialrails import SocialRails

        client = SocialRails(api_key="sr_live_...")

        # Create a post
        post = client.posts.create(content="Hello!", platform="twitter")

        # Always close when done
        client.close()

    Or use as a context manager::

        with SocialRails(api_key="sr_live_...") as client:
            posts = client.posts.list()
    """

    def __init__(
        self,
        api_key: Optional[str] = None,
        base_url: str = DEFAULT_BASE_URL,
    ) -> None:
        super().__init__(api_key=api_key, base_url=base_url)
        self._http = httpx.Client(
            headers=self._headers,
            timeout=30.0,
        )

        # Resource namespaces
        self.posts = Posts(self)
        self.analytics = Analytics(self)
        self.accounts = Accounts(self)
        self.ai = AI(self)
        self.workspace = WorkspaceResource(self)
        self.media = Media(self)
        self.webhooks = Webhooks(self)

    # ------------------------------------------------------------------
    # HTTP primitives
    # ------------------------------------------------------------------

    def _get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
        response = self._http.get(self._build_url(path), params=params)
        return self._handle_response(response)

    def _post(self, path: str, json: Optional[Dict[str, Any]] = None) -> Any:
        response = self._http.post(self._build_url(path), json=json)
        return self._handle_response(response)

    def _patch(self, path: str, json: Optional[Dict[str, Any]] = None) -> Any:
        response = self._http.patch(self._build_url(path), json=json)
        return self._handle_response(response)

    def _delete(self, path: str) -> Any:
        response = self._http.delete(self._build_url(path))
        return self._handle_response(response)

    def _post_files(
        self,
        path: str,
        *,
        files: Dict[str, Tuple[str, bytes]],
        data: Optional[Dict[str, str]] = None,
    ) -> Any:
        # For multipart uploads we must not send the JSON Content-Type header
        headers = {"Authorization": f"Bearer {self._api_key}"}
        response = self._http.post(
            self._build_url(path),
            files=files,
            data=data or {},
            headers=headers,
        )
        return self._handle_response(response)

    # ------------------------------------------------------------------
    # Lifecycle
    # ------------------------------------------------------------------

    def close(self) -> None:
        """Close the underlying HTTP client."""
        self._http.close()

    def __enter__(self) -> "SocialRails":
        return self

    def __exit__(self, *args: Any) -> None:
        self.close()


class AsyncSocialRails(_BaseClient):
    """Asynchronous SocialRails API client.

    Usage::

        import asyncio
        from socialrails import AsyncSocialRails

        async def main():
            client = AsyncSocialRails(api_key="sr_live_...")
            post = await client.posts.create(content="Hello!", platform="twitter")
            await client.close()

        asyncio.run(main())

    Or use as an async context manager::

        async with AsyncSocialRails(api_key="sr_live_...") as client:
            posts = await client.posts.list()
    """

    def __init__(
        self,
        api_key: Optional[str] = None,
        base_url: str = DEFAULT_BASE_URL,
    ) -> None:
        super().__init__(api_key=api_key, base_url=base_url)
        self._http = httpx.AsyncClient(
            headers=self._headers,
            timeout=30.0,
        )

        # Resource namespaces
        self.posts = AsyncPosts(self)
        self.analytics = AsyncAnalytics(self)
        self.accounts = AsyncAccounts(self)
        self.ai = AsyncAI(self)
        self.workspace = AsyncWorkspaceResource(self)
        self.media = AsyncMedia(self)
        self.webhooks = AsyncWebhooks(self)

    # ------------------------------------------------------------------
    # HTTP primitives
    # ------------------------------------------------------------------

    async def _get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
        response = await self._http.get(self._build_url(path), params=params)
        return self._handle_response(response)

    async def _post(self, path: str, json: Optional[Dict[str, Any]] = None) -> Any:
        response = await self._http.post(self._build_url(path), json=json)
        return self._handle_response(response)

    async def _patch(self, path: str, json: Optional[Dict[str, Any]] = None) -> Any:
        response = await self._http.patch(self._build_url(path), json=json)
        return self._handle_response(response)

    async def _delete(self, path: str) -> Any:
        response = await self._http.delete(self._build_url(path))
        return self._handle_response(response)

    async def _post_files(
        self,
        path: str,
        *,
        files: Dict[str, Tuple[str, bytes]],
        data: Optional[Dict[str, str]] = None,
    ) -> Any:
        headers = {"Authorization": f"Bearer {self._api_key}"}
        response = await self._http.post(
            self._build_url(path),
            files=files,
            data=data or {},
            headers=headers,
        )
        return self._handle_response(response)

    # ------------------------------------------------------------------
    # Lifecycle
    # ------------------------------------------------------------------

    async def close(self) -> None:
        """Close the underlying HTTP client."""
        await self._http.aclose()

    async def __aenter__(self) -> "AsyncSocialRails":
        return self

    async def __aexit__(self, *args: Any) -> None:
        await self.close()
