"""HTTP client: login, bearer auth, helpers for tickets, portal, companies, channels."""

from __future__ import annotations

import logging
from typing import Any, Mapping, MutableMapping

import httpx

LOG = logging.getLogger(__name__)


class SimClient:
    """Async API client for one simulated user session (or channel actor)."""

    def __init__(self, base_url: str, timeout_s: float = 45.0) -> None:
        self._base = base_url.rstrip("/")
        self._token: str | None = None
        self._client = httpx.AsyncClient(base_url=self._base, timeout=timeout_s)

    @property
    def base_url(self) -> str:
        return self._base

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

    def _auth_headers(self) -> dict[str, str]:
        if not self._token:
            raise RuntimeError("Not logged in")
        return {"Authorization": f"Bearer {self._token}"}

    async def login(self, email: str, password: str) -> Mapping[str, Any]:
        r = await self._client.post(
            "/api/v1/auth/login",
            json={"email": email.strip(), "password": password},
        )
        if r.status_code != 200:
            LOG.warning("login failed %s %s", r.status_code, r.text[:500])
            r.raise_for_status()
        data = r.json()
        token = data.get("token")
        if not token or not isinstance(token, str):
            raise RuntimeError("login response missing token")
        self._token = token
        return data

    async def get_me(self) -> Mapping[str, Any]:
        r = await self._client.get("/api/v1/auth/me", headers=self._auth_headers())
        if r.status_code != 200:
            LOG.warning("auth/me failed %s %s", r.status_code, r.text[:500])
            r.raise_for_status()
        return r.json()

    async def get_tickets(
        self,
        *,
        status: str | None = None,
        limit: int = 50,
        offset: int = 0,
    ) -> list[MutableMapping[str, Any]]:
        params: dict[str, str | int] = {"limit": limit, "offset": offset}
        if status:
            params["status"] = status
        r = await self._client.get("/api/v1/tickets", headers=self._auth_headers(), params=params)
        if r.status_code != 200:
            LOG.warning("GET tickets failed %s %s", r.status_code, r.text[:500])
            r.raise_for_status()
        body = r.json()
        if not isinstance(body, list):
            raise RuntimeError(f"unexpected tickets list shape: {type(body)}")
        return body

    async def get_ticket(self, ticket_id: str) -> Mapping[str, Any]:
        r = await self._client.get(
            f"/api/v1/tickets/{ticket_id}",
            headers=self._auth_headers(),
        )
        if r.status_code != 200:
            LOG.warning("GET ticket %s failed %s %s", ticket_id, r.status_code, r.text[:500])
            r.raise_for_status()
        return r.json()

    async def post_portal_message(self, content: str, ticket_id: str | None = None) -> Mapping[str, Any]:
        r = await self.post_portal_message_raw(content, ticket_id=ticket_id)
        if r.status_code != 201:
            LOG.warning("portal/messages failed %s %s", r.status_code, r.text[:500])
            r.raise_for_status()
        return r.json()

    async def post_portal_message_raw(self, content: str, ticket_id: str | None = None) -> httpx.Response:
        """POST portal message without raising (for expected 4xx scenarios)."""
        payload: dict[str, str] = {"content": content}
        if ticket_id:
            payload["ticket_id"] = ticket_id
        return await self._client.post(
            "/api/v1/portal/messages",
            headers=self._auth_headers(),
            json=payload,
        )

    async def patch_ticket(self, ticket_id: str, body: Mapping[str, Any]) -> Mapping[str, Any]:
        r = await self._client.patch(
            f"/api/v1/tickets/{ticket_id}",
            headers=self._auth_headers(),
            json=dict(body),
        )
        if r.status_code != 200:
            LOG.warning("PATCH ticket %s failed %s %s", ticket_id, r.status_code, r.text[:500])
            r.raise_for_status()
        return r.json()

    async def post_ticket_message(self, ticket_id: str, content: str) -> Mapping[str, Any]:
        r = await self._client.post(
            f"/api/v1/tickets/{ticket_id}/messages",
            headers=self._auth_headers(),
            json={"content": content},
        )
        if r.status_code != 201:
            LOG.warning("POST ticket message %s failed %s %s", ticket_id, r.status_code, r.text[:500])
            r.raise_for_status()
        return r.json()

    async def list_companies(self) -> list[MutableMapping[str, Any]]:
        r = await self._client.get("/api/v1/companies", headers=self._auth_headers())
        if r.status_code != 200:
            LOG.warning("GET companies failed %s %s", r.status_code, r.text[:500])
            r.raise_for_status()
        body = r.json()
        if not isinstance(body, list):
            raise RuntimeError(f"unexpected companies list shape: {type(body)}")
        return body

    async def create_company(self, name: str) -> Mapping[str, Any]:
        r = await self._client.post(
            "/api/v1/companies",
            headers=self._auth_headers(),
            json={"name": name},
        )
        if r.status_code != 201:
            LOG.warning("POST company failed %s %s", r.status_code, r.text[:500])
            r.raise_for_status()
        return r.json()

    async def create_portal_user(
        self,
        company_id: str,
        email: str,
        password: str,
        display_name: str | None = None,
    ) -> Mapping[str, Any]:
        payload: dict[str, str] = {"email": email, "password": password}
        if display_name:
            payload["display_name"] = display_name
        r = await self._client.post(
            f"/api/v1/companies/{company_id}/users",
            headers=self._auth_headers(),
            json=payload,
        )
        if r.status_code != 201:
            LOG.warning("POST portal user failed %s %s", r.status_code, r.text[:500])
            r.raise_for_status()
        return r.json()

    async def create_staff_user(
        self,
        email: str,
        password: str,
        role: str = "user",
    ) -> Mapping[str, Any]:
        r = await self._client.post(
            "/api/v1/users",
            headers=self._auth_headers(),
            json={"email": email, "password": password, "role": role},
        )
        if r.status_code != 201:
            LOG.warning("POST staff user failed %s %s", r.status_code, r.text[:500])
            r.raise_for_status()
        return r.json()

    async def list_channels(self) -> list[MutableMapping[str, Any]]:
        r = await self._client.get("/api/v1/channels", headers=self._auth_headers())
        if r.status_code != 200:
            LOG.warning("GET channels failed %s %s", r.status_code, r.text[:500])
            r.raise_for_status()
        body = r.json()
        if not isinstance(body, list):
            raise RuntimeError(f"unexpected channels list shape: {type(body)}")
        return body

    async def get_channel(self, channel_id: str) -> Mapping[str, Any]:
        r = await self._client.get(
            f"/api/v1/channels/{channel_id}",
            headers=self._auth_headers(),
        )
        if r.status_code != 200:
            LOG.warning("GET channel %s failed %s %s", channel_id, r.status_code, r.text[:500])
            r.raise_for_status()
        return r.json()

    async def post_inbound_email(
        self,
        token: str,
        *,
        from_addr: str,
        text: str,
        subject: str | None = None,
        from_name: str | None = None,
        message_id: str | None = None,
    ) -> Mapping[str, Any]:
        payload: dict[str, str] = {"from": from_addr, "text": text}
        if subject:
            payload["subject"] = subject
        if from_name:
            payload["from_name"] = from_name
        if message_id:
            payload["message_id"] = message_id
        r = await self._client.post(f"/api/v1/inbound/email/{token}", json=payload)
        if r.status_code not in (200, 201):
            LOG.warning("inbound email failed %s %s", r.status_code, r.text[:500])
            r.raise_for_status()
        return r.json() if r.content else {"ok": True}

    async def post_landing_chat(
        self,
        message: str,
        session_id: str | None = None,
        captcha_token: str | None = None,
    ) -> tuple[int, Mapping[str, Any] | str]:
        """POST landing chat; returns (status_code, body). Does not raise on 4xx."""
        payload: dict[str, Any] = {"message": message, "sessionId": session_id}
        if captcha_token:
            payload["captchaToken"] = captcha_token
        r = await self._client.post("/api/v1/landing/chat", json=payload)
        try:
            body: Mapping[str, Any] | str = r.json()
        except Exception:
            body = r.text[:500]
        return r.status_code, body
