from __future__ import annotations

import asyncio
import logging
from typing import Any

from ..base import (
    CaptchaSolver,
    CaptchaTaskResult,
    CaptchaTaskSpec,
    captcha_task_handler,
)
from ..router import CaptchaRouter

logger = logging.getLogger("twocaptcha_provider")


@CaptchaRouter.provider("2captcha", "twocaptcha", api_key_env="TWOCAPTCHA_KEY")
class TwoCaptchaProvider(CaptchaSolver):
    """2Captcha implementation of CaptchaSolver."""

    BASE_URL = "http://2captcha.com"
    RETRYABLE_ERROR_CODES = ["ERROR_NO_SLOT_AVAILABLE"]
    DEFAULT_INITIAL_WAIT = 5.0
    DEFAULT_POLLING_DELAY = 5.0
    DEFAULT_MAX_ATTEMPTS = 60

    async def _post(self, endpoint: str, data: dict[str, Any]) -> dict[str, Any]:
        url = f"{self.BASE_URL}/{endpoint}"
        session = await self._get_session()

        if "json" not in data:
            data["json"] = 1
        data["key"] = self.api_key

        for attempt in range(self.max_retries + 1):
            try:
                async with session.post(url, data=data) as response:
                    try:
                        resp_data = await response.json()
                    except Exception:
                        text = await response.text()
                        logger.error(f"2Captcha Non-JSON response: {text}")
                        return {"status": 0, "request": text}

                    if resp_data.get("status") == 1:
                        return resp_data

                    error_code = resp_data.get("request")

                    if error_code in self.RETRYABLE_ERROR_CODES and attempt < self.max_retries:
                        await asyncio.sleep(self.retry_delay * (2 ** attempt))
                        continue

                    logger.error(f"2Captcha API Error: {error_code}")
                    return resp_data

            except Exception as e:
                logger.error(f"2Captcha Request Failed: {e}")
                if attempt < self.max_retries:
                    await asyncio.sleep(self.retry_delay)
                else:
                    raise
        return {"status": 0, "request": "MAX_RETRIES_EXCEEDED"}

    @captcha_task_handler(
        "recaptcha",
        "recaptcha_v2",
        "recaptcha_v2_enterprise",
        "recaptcha_v3",
        "recaptcha_v3_enterprise",
    )
    async def _create_recaptcha_task(self, task: CaptchaTaskSpec, **kwargs) -> str | None:
        kind = task.kind.strip().lower()
        params = dict(task.params or {})

        enterprise_aliases = {"recaptcha_v2_enterprise", "recaptcha_v3_enterprise"}
        if kind in enterprise_aliases:
            params["is_enterprise"] = True
            kind = "recaptcha_v3" if kind == "recaptcha_v3_enterprise" else "recaptcha_v2"

        website_url = params.get("website_url")
        site_key = params.get("site_key")
        if not website_url or not site_key:
            logger.error("Task params must include website_url and site_key")
            return None

        is_enterprise = bool(params.get("is_enterprise", False))

        task_data: dict[str, Any] = {
            "method": "userrecaptcha",
            "googlekey": site_key,
            "pageurl": website_url,
            "json": 1,
        }

        if kind == "recaptcha_v3" or params.get("version") == "v3" or params.get("action") or params.get("min_score"):
            task_data["version"] = "v3"
            task_data["action"] = params.get("action", "verify")
            task_data["min_score"] = params.get("min_score", 0.5)

        if is_enterprise:
            task_data["enterprise"] = 1

        create_resp = await self._post("in.php", task_data)

        if create_resp.get("status") != 1:
            logger.error("Failed to create 2Captcha task: %s", create_resp.get("request"))
            return None

        captcha_id = create_resp.get("request")
        logger.info(f"Created 2Captcha task {captcha_id}")
        return str(captcha_id)

    async def get_task_result(self, task_id: str, **kwargs) -> CaptchaTaskResult:
        poll_resp = await self._get_result(task_id)

        if poll_resp.get("status") == 1:
            token = poll_resp.get("request")
            return CaptchaTaskResult(status="ready", solution=token, raw=poll_resp)

        error_code = poll_resp.get("request")
        if error_code == "CAPCHA_NOT_READY":
            return CaptchaTaskResult(status="processing", raw=poll_resp)

        return CaptchaTaskResult(
            status="failed",
            error=str(error_code or "unknown_error"),
            raw=poll_resp,
        )

    async def _get_result(self, captcha_id: str) -> dict[str, Any]:
        url = f"{self.BASE_URL}/res.php"
        session = await self._get_session()
        params = {
            "key": self.api_key,
            "action": "get",
            "id": captcha_id,
            "json": 1,
        }
        async with session.get(url, params=params) as response:
            try:
                return await response.json()
            except Exception:
                return {"status": 0, "request": "JSON_PARSE_ERROR"}

    async def get_balance(self) -> float:
        url = f"{self.BASE_URL}/res.php"
        session = await self._get_session()
        params = {
            "key": self.api_key,
            "action": "getbalance",
            "json": 1,
        }
        async with session.get(url, params=params) as response:
            data = await response.json()
            if data.get("status") == 1:
                return float(data.get("request", 0.0))
            return 0.0
