"""
Standalone DraxonMails Discord verification helper using `wreq`.

Install:
    pip install wreq

Quick use:
    from draxonmails_discord import DraxonMailsDiscordClient

    client = DraxonMailsDiscordClient(api_key="YOUR_API_KEY")
    inbox = client.random_address()
    link = client.wait_for_verification_link(inbox["address"])
    print(link)
"""

from __future__ import annotations

import json
import re
import time
from typing import Any

from wreq import Emulation
from wreq.blocking import Client


class DraxonMailsDiscordError(Exception):
    """Base Discord helper error."""


class DraxonMailsDiscordApiError(DraxonMailsDiscordError):
    """Raised when the API returns a non-2xx response."""

    def __init__(self, status_code: int, payload: Any, message: str):
        self.status_code = status_code
        self.payload = payload
        super().__init__(message)


class DraxonMailsNoDiscordLinkError(DraxonMailsDiscordError):
    """Raised when a Discord verification email exists but no link is found."""


class DraxonMailsDiscordClient:
    """
    Super simple Discord verification helper for DraxonMails.

    It keeps only the flow most people need:
    - get random address
    - fetch inbox
    - return the Discord verification link only
    """

    def __init__(
        self,
        base_url: str = "https://mail.draxono.in",
        api_key: str = "",
        *,
        api_prefix: str = "/api/v1",
        emulate: Any = Emulation.Chrome137,
        user_agent: str = "DraxonMails-Discord-Wreq/1.0",
    ) -> None:
        self.base_url = base_url.rstrip("/")
        self.api_prefix = api_prefix if api_prefix.startswith("/") else f"/{api_prefix}"
        self.api_key = (api_key or "").strip()
        self._client = Client(
            emulation=emulate,
            user_agent=user_agent,
            cookie_store=True,
        )
        self._discord_link_re = re.compile(
            r'https?://[^\s"\'<>]+(?:discord(?:app)?\.com|click\.discord\.com)[^\s"\'<>]*',
            re.IGNORECASE,
        )

    @property
    def base_api_url(self) -> str:
        return f"{self.base_url}{self.api_prefix}"

    def set_api_key(self, api_key: str) -> None:
        self.api_key = (api_key or "").strip()

    def random_address(self) -> dict[str, Any]:
        return self._request_json("GET", "/random-address")

    def get_inbox(
        self,
        address: str,
        *,
        password: str = "",
        domain_secret: str = "",
    ) -> list[dict[str, Any]]:
        headers = self._headers(domain_secret=domain_secret)
        if password:
            return self._request_json(
                "POST",
                f"/inbox/{address}",
                json_body={"password": password},
                headers=headers,
            )
        return self._request_json("GET", f"/inbox/{address}", headers=headers)

    def extract_verification_link(self, messages: list[dict[str, Any]]) -> str:
        for message in messages or []:
            for text in (
                str(message.get("html", "")),
                str(message.get("body", "")),
                str(message.get("markdown", "")),
            ):
                for match in self._discord_link_re.findall(text):
                    lowered = match.lower()
                    if "discord" not in lowered:
                        continue
                    if any(token in lowered for token in ("verify", "email", "ls/click", "click.discord")):
                        return match.rstrip(').,;"\'')
        raise DraxonMailsNoDiscordLinkError(
            "Discord verification email found, but no verification link could be extracted"
        )

    def get_verification_link(
        self,
        address: str,
        *,
        password: str = "",
        domain_secret: str = "",
    ) -> str:
        messages = self.get_inbox(address, password=password, domain_secret=domain_secret)
        return self.extract_verification_link(messages)

    def wait_for_verification_link(
        self,
        address: str,
        *,
        password: str = "",
        domain_secret: str = "",
        timeout_seconds: int = 120,
        poll_every_seconds: float = 3.0,
    ) -> str:
        deadline = time.time() + max(1, int(timeout_seconds))
        last_error: Exception | None = None
        while time.time() < deadline:
            try:
                return self.get_verification_link(
                    address,
                    password=password,
                    domain_secret=domain_secret,
                )
            except DraxonMailsNoDiscordLinkError as exc:
                last_error = exc
            time.sleep(max(0.5, float(poll_every_seconds)))
        if last_error:
            raise last_error
        raise TimeoutError("Timed out waiting for a Discord verification link")

    def _headers(self, *, domain_secret: str = "") -> dict[str, str]:
        headers = {
            "Accept": "application/json",
            "Content-Type": "application/json",
        }
        if self.api_key:
            headers["X-Api-Key"] = self.api_key
        if domain_secret:
            headers["X-Draxon-Domain-Secret"] = domain_secret
        return headers

    def _request_json(
        self,
        method: str,
        path: str,
        *,
        json_body: dict[str, Any] | None = None,
        headers: dict[str, str] | None = None,
    ) -> Any:
        url = f"{self.base_api_url}{path if path.startswith('/') else '/' + path}"
        kwargs: dict[str, Any] = {"headers": headers or self._headers()}
        if json_body is not None:
            kwargs["json"] = json_body
        if method == "GET":
            response = self._client.get(url, **kwargs)
        elif method == "POST":
            response = self._client.post(url, **kwargs)
        else:
            raise ValueError(f"Unsupported method: {method}")

        payload = self._read_json(response)
        status_code = int(getattr(response, "status_code", 0) or 0)
        if 200 <= status_code < 300:
            return payload
        raise DraxonMailsDiscordApiError(status_code, payload, self._extract_error_message(payload, status_code))

    @staticmethod
    def _read_json(response) -> Any:
        json_method = getattr(response, "json", None)
        if callable(json_method):
            try:
                return json_method()
            except Exception:
                pass
        text_method = getattr(response, "text", None)
        if callable(text_method):
            text_value = text_method()
            if isinstance(text_value, bytes):
                text_value = text_value.decode("utf-8", errors="ignore")
            if isinstance(text_value, str) and text_value.strip():
                return json.loads(text_value)
        return {}

    @staticmethod
    def _extract_error_message(payload: Any, status_code: int) -> str:
        if isinstance(payload, dict):
            parts = [payload.get("detail"), payload.get("error"), payload.get("note")]
            parts = [str(part).strip() for part in parts if part]
            if parts:
                return " — ".join(parts)
        return f"HTTP {status_code}"
