"""Discord HTTP client. Domain services depend on this port, not raw httpx."""
from __future__ import annotations

from typing import Any, Optional, Protocol

import httpx

from src.config import Settings, get_settings
from src.core.exceptions import ValidationError

API_BASE = "https://discord.com/api/v10"


class DiscordCommunityClient(Protocol):
    def authorization_url(self, *, redirect_uri: str, state: str) -> str: ...

    async def exchange_code(self, *, code: str, redirect_uri: str) -> dict[str, Any]: ...

    async def get_current_user(self, access_token: str) -> dict[str, Any]: ...

    async def add_guild_member(
        self, *, discord_user_id: str, access_token: str, role_ids: list[str]
    ) -> None: ...

    async def add_guild_member_role(self, *, discord_user_id: str, role_id: str) -> None: ...

    async def list_guild_roles(self) -> list[dict[str, Any]]: ...

    async def create_guild_role(self, name: str) -> dict[str, Any]: ...


class DiscordHttpClient:
    def __init__(self, settings: Optional[Settings] = None):
        self._settings = settings or get_settings()

    def authorization_url(self, *, redirect_uri: str, state: str) -> str:
        from urllib.parse import urlencode

        params = {
            "client_id": (self._settings.discord_client_id or "").strip(),
            "redirect_uri": redirect_uri,
            "response_type": "code",
            "scope": "identify guilds.join",
            "state": state,
            "prompt": "consent",
        }
        return f"https://discord.com/oauth2/authorize?{urlencode(params)}"

    async def exchange_code(self, *, code: str, redirect_uri: str) -> dict[str, Any]:
        async with httpx.AsyncClient(timeout=30.0) as client:
            response = await client.post(
                f"{API_BASE}/oauth2/token",
                data={
                    "client_id": self._settings.discord_client_id,
                    "client_secret": self._settings.discord_client_secret,
                    "grant_type": "authorization_code",
                    "code": code,
                    "redirect_uri": redirect_uri,
                },
                headers={"Content-Type": "application/x-www-form-urlencoded"},
            )
        if response.status_code >= 400:
            raise ValidationError("Discord did not accept the authorization code.")
        payload = response.json()
        if not payload.get("access_token"):
            raise ValidationError("Discord did not return an access token.")
        return payload

    async def get_current_user(self, access_token: str) -> dict[str, Any]:
        async with httpx.AsyncClient(timeout=30.0) as client:
            response = await client.get(
                f"{API_BASE}/users/@me",
                headers={"Authorization": f"Bearer {access_token}"},
            )
        if response.status_code >= 400:
            raise ValidationError("Could not read the Discord user profile.")
        return response.json()

    def _bot_headers(self) -> dict[str, str]:
        token = (self._settings.discord_bot_token or "").strip()
        return {"Authorization": f"Bot {token}", "Content-Type": "application/json"}

    def _guild_id(self) -> str:
        guild_id = (self._settings.discord_guild_id or "").strip()
        if not guild_id:
            raise ValidationError("Discord guild is not configured.")
        return guild_id

    async def add_guild_member(
        self, *, discord_user_id: str, access_token: str, role_ids: list[str]
    ) -> None:
        body: dict[str, Any] = {"access_token": access_token}
        if role_ids:
            body["roles"] = role_ids
        async with httpx.AsyncClient(timeout=30.0) as client:
            response = await client.put(
                f"{API_BASE}/guilds/{self._guild_id()}/members/{discord_user_id}",
                headers=self._bot_headers(),
                json=body,
            )
        if response.status_code not in (201, 204):
            raise ValidationError("Could not add the member to the P2HS Discord server.")

    async def add_guild_member_role(self, *, discord_user_id: str, role_id: str) -> None:
        async with httpx.AsyncClient(timeout=30.0) as client:
            response = await client.put(
                f"{API_BASE}/guilds/{self._guild_id()}/members/{discord_user_id}/roles/{role_id}",
                headers=self._bot_headers(),
            )
        if response.status_code not in (204, 201):
            raise ValidationError("Could not assign the Discord community role.")

    async def list_guild_roles(self) -> list[dict[str, Any]]:
        async with httpx.AsyncClient(timeout=30.0) as client:
            response = await client.get(
                f"{API_BASE}/guilds/{self._guild_id()}/roles",
                headers=self._bot_headers(),
            )
        if response.status_code >= 400:
            raise ValidationError("Could not list Discord roles.")
        return list(response.json() or [])

    async def create_guild_role(self, name: str) -> dict[str, Any]:
        async with httpx.AsyncClient(timeout=30.0) as client:
            response = await client.post(
                f"{API_BASE}/guilds/{self._guild_id()}/roles",
                headers=self._bot_headers(),
                json={"name": name, "mentionable": False, "hoist": False},
            )
        if response.status_code >= 400:
            raise ValidationError(f"Could not create Discord role '{name}'.")
        return response.json()
