from __future__ import annotations

import httpx

from termchat.auth import load_tokens, save_tokens
from termchat.config import API_URL


class ApiClient:
    def __init__(self) -> None:
        tokens = load_tokens()
        self._access_token: str | None = tokens[0] if tokens else None
        self._refresh_token: str | None = tokens[1] if tokens else None
        self._client = httpx.AsyncClient(base_url=API_URL, timeout=10.0, follow_redirects=True)

    def _headers(self) -> dict[str, str]:
        if self._access_token:
            return {"Authorization": f"Bearer {self._access_token}"}
        return {}

    async def _request(
        self,
        method: str,
        path: str,
        *,
        json: dict | None = None,
        params: dict | None = None,
        auth_required: bool = True,
        _retried: bool = False,
    ) -> dict | list | None:
        headers = self._headers() if auth_required else {}
        resp = await self._client.request(
            method, path, json=json, params=params, headers=headers
        )
        if resp.status_code == 401 and auth_required and not _retried:
            refreshed = await self.refresh_token()
            if refreshed:
                return await self._request(
                    method, path, json=json, params=params,
                    auth_required=auth_required, _retried=True,
                )
        if resp.status_code >= 400:
            # Try to extract detail from FastAPI error response
            detail = None
            try:
                body = resp.json()
                detail = body.get("detail")
            except Exception:
                pass
            if detail:
                raise httpx.HTTPStatusError(
                    f"{resp.status_code}: {detail}",
                    request=resp.request,
                    response=resp,
                )
            resp.raise_for_status()
        if resp.status_code == 204:
            return None
        return resp.json()

    # ── auth ──────────────────────────────────────────────────────

    async def register(self, username: str, display_name: str, password: str) -> dict:
        resp = await self._client.post(
            "/auth/register",
            json={"username": username, "display_name": display_name, "password": password},
        )
        resp.raise_for_status()
        data = resp.json()
        self._access_token = data["access_token"]
        self._refresh_token = data["refresh_token"]
        save_tokens(self._access_token, self._refresh_token)
        return data

    async def login(self, username: str, password: str) -> dict:
        resp = await self._client.post(
            "/auth/login",
            json={"username": username, "password": password},
        )
        resp.raise_for_status()
        data = resp.json()
        self._access_token = data["access_token"]
        self._refresh_token = data["refresh_token"]
        save_tokens(self._access_token, self._refresh_token)
        return data

    async def refresh_token(self) -> bool:
        if not self._refresh_token:
            return False
        try:
            resp = await self._client.post(
                "/auth/refresh",
                json={"refresh_token": self._refresh_token},
            )
            if resp.status_code != 200:
                return False
            data = resp.json()
            self._access_token = data["access_token"]
            self._refresh_token = data["refresh_token"]
            save_tokens(self._access_token, self._refresh_token)
            return True
        except Exception:
            return False

    # ── user ──────────────────────────────────────────────────────

    async def get_me(self) -> dict:
        return await self._request("GET", "/auth/me")

    async def search_users(self, query: str) -> list:
        return await self._request("GET", "/users/search", params={"q": query})

    async def set_status(self, status: str) -> dict:
        return await self._request("PUT", "/users/status", json={"status": status})

    # ── conversations ─────────────────────────────────────────────

    async def get_conversations(self) -> list:
        return await self._request("GET", "/conversations/")

    async def create_conversation(
        self, conv_type: str, member_ids: list[str], name: str | None = None
    ) -> dict:
        payload: dict = {"type": conv_type, "member_ids": member_ids}
        if name:
            payload["name"] = name
        return await self._request("POST", "/conversations/", json=payload)

    async def get_profile(self) -> dict:
        return await self._request("GET", "/users/profile")

    async def update_profile(self, **fields) -> dict:
        return await self._request("PUT", "/users/profile", json=fields)

    async def get_user_profile(self, username: str) -> dict:
        return await self._request("GET", f"/users/{username}/profile")

    async def delete_conversation(self, conversation_id: str) -> None:
        await self._request("DELETE", f"/conversations/{conversation_id}")

    async def mute_conversation(self, conversation_id: str, mute: bool) -> dict:
        return await self._request(
            "PUT",
            f"/conversations/{conversation_id}/mute",
            json={"is_muted": mute},
        )

    # ── messages ──────────────────────────────────────────────────

    async def get_messages(
        self, conversation_id: str, before: str | None = None, limit: int = 50
    ) -> list:
        params: dict = {"limit": limit}
        if before:
            params["before"] = before
        return await self._request("GET", f"/messages/{conversation_id}", params=params)

    async def send_message(
        self,
        conversation_id: str,
        content: str,
        message_type: str = "text",
        reply_to_id: str | None = None,
    ) -> dict:
        payload = {"content": content, "message_type": message_type}
        if reply_to_id:
            payload["reply_to_id"] = reply_to_id
        return await self._request(
            "POST",
            f"/messages/{conversation_id}",
            json=payload,
        )

    async def edit_message(
        self, conversation_id: str, message_id: str, content: str
    ) -> dict:
        return await self._request(
            "PUT",
            f"/messages/{conversation_id}/{message_id}",
            json={"content": content},
        )

    async def delete_message(self, conversation_id: str, message_id: str) -> None:
        await self._request("DELETE", f"/messages/{conversation_id}/{message_id}")

    # ── search ─────────────────────────────────────────────────

    async def search_messages(self, conversation_id: str, query: str) -> list:
        return await self._request(
            "GET", f"/messages/search/{conversation_id}", params={"q": query}
        )

    # ── blocking ──────────────────────────────────────────────

    async def block_user(self, username: str) -> dict:
        return await self._request("POST", f"/users/{username}/block")

    async def unblock_user(self, username: str) -> None:
        await self._request("DELETE", f"/users/{username}/block")

    async def get_blocked_users(self) -> list:
        return await self._request("GET", "/users/blocked")

    # ── reactions ──────────────────────────────────────────────

    async def toggle_reaction(
        self, conversation_id: str, message_id: str, emoji: str
    ) -> dict:
        return await self._request(
            "POST",
            f"/messages/{conversation_id}/{message_id}/react",
            json={"emoji": emoji},
        )

    # ── password ───────────────────────────────────────────────

    async def change_password(self, current_password: str, new_password: str) -> dict:
        return await self._request(
            "PUT", "/auth/password",
            json={"current_password": current_password, "new_password": new_password},
        )

    # ── group management ───────────────────────────────────────

    async def rename_conversation(self, conversation_id: str, name: str) -> dict:
        return await self._request(
            "PUT", f"/conversations/{conversation_id}",
            json={"name": name},
        )

    async def add_members(self, conversation_id: str, member_ids: list[str]) -> dict:
        return await self._request(
            "POST", f"/conversations/{conversation_id}/members",
            json={"member_ids": member_ids},
        )

    async def remove_member(self, conversation_id: str, user_id: str) -> None:
        await self._request(
            "DELETE", f"/conversations/{conversation_id}/members/{user_id}",
        )

    # ── file upload ────────────────────────────────────────────────

    async def upload_file(self, conversation_id: str, file_path: str) -> dict:
        import os
        filename = os.path.basename(file_path)
        with open(file_path, "rb") as f:
            files = {"file": (filename, f)}
            headers = self._headers()
            resp = await self._client.post(
                f"/messages/{conversation_id}/upload",
                files=files,
                headers=headers,
            )
            if resp.status_code >= 400:
                detail = None
                try:
                    body = resp.json()
                    detail = body.get("detail")
                except Exception:
                    pass
                if detail:
                    raise httpx.HTTPStatusError(
                        f"{resp.status_code}: {detail}",
                        request=resp.request,
                        response=resp,
                    )
                resp.raise_for_status()
            return resp.json()

    # ── lifecycle ─────────────────────────────────────────────────

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