from __future__ import annotations

import logging
import re
import time
from dataclasses import dataclass
from typing import Any, Dict, List, Optional

import requests

from log_utils import log_debug_event

__all__ = [
    "CastleSolverError",
    "CastleSolveResult",
    "request_castle_solves",
    "fetch_balance",
]


_CASTLESOLVER_ENDPOINT = "https://castlesolver.cc/api/getSolves"
_CASTLESOLVER_BALANCE_ENDPOINT = "https://castlesolver.cc/api/balance"
_CASTLEBREAKER_ENDPOINT = "https://castlebreaker.cc/getManyCastleTokenProxyless"
_CODE_SOLVER_CASTLE_DEFAULT_BASE = "http://37.27.240.144:5000"
_CODE_SOLVER_CASTLE_TEST_BASE = "http://144.76.92.77:5000"
_CODE_SOLVER_CASTLE_ENDPOINT = f"{_CODE_SOLVER_CASTLE_DEFAULT_BASE}/api/token/createTask"
_CODE_SOLVER_CASTLE_GET_ENDPOINT = (
    f"{_CODE_SOLVER_CASTLE_DEFAULT_BASE}/api/Token/getTask/{{session_id}}?apiKey={{api_key}}"
)

_INSUFFICIENT_BALANCE_MARKERS = (
    "insufficient",
    "not enough",
    "no balance",
    "no credits",
    "no credit",
    "out of credits",
    "out of balance",
    "out of funds",
    "balance low",
    "bakiye",
    "kredi",
    "funds",
)

_INSUFFICIENT_BALANCE_STRONG_MARKERS = (
    "no balance",
    "no credits",
    "no credit",
    "out of credits",
    "out of balance",
    "out of funds",
    "balance low",
)

_INSUFFICIENT_BALANCE_WARNINGS = (
    "insufficient",
    "not enough",
    "yetersiz",
    "tÃ¼kendi",
    "depleted",
    "no",
    "out",
    "low",
    "zero",
)


def _normalize_service_name(service: str) -> str:
    normalized = (service or "castlesolver.cc").strip().lower()
    if normalized.endswith("/"):
        normalized = normalized.rstrip("/")
    if normalized.startswith("https://"):
        normalized = normalized[len("https://") :]
    elif normalized.startswith("http://"):
        normalized = normalized[len("http://") :]
    return normalized


class CastleSolverError(Exception):
    """castle token servislerinden yanÄ±t alÄ±nÄ±rken oluÅŸan hatalarÄ± temsil eder."""


@dataclass(slots=True)
class CastleSolveResult:
    tokens: List[str]
    cuid: str
    timestamp: str
    cost: Optional[float]
    balance: Optional[float]
    solves: Optional[int]
    raw: Dict[str, Any]
    accept_lang: Optional[str] = None
    sec_ch_ua: Optional[str] = None
    sec_ch_ua_platform: Optional[str] = None
    user_agent: Optional[str] = None
    task_password_verification_castle: Optional[str] = None


def _extract_chrome_version(user_agent: str) -> Optional[str]:
    match = re.search(r"Chrome/(\d+)", user_agent or "")
    if not match:
        return None
    return match.group(1)


def _resolve_code_solver_base(service_config: Dict[str, Any]) -> str:
    use_test_flag = service_config.get("test_server")
    if isinstance(use_test_flag, str):
        lowered = use_test_flag.strip().lower()
        use_test_flag = lowered in {"1", "true", "yes", "on"}
    elif isinstance(use_test_flag, (int, float)):
        use_test_flag = bool(use_test_flag)
    elif not isinstance(use_test_flag, bool):
        use_test_flag = False

    base = (
        _CODE_SOLVER_CASTLE_TEST_BASE
        if use_test_flag
        else _CODE_SOLVER_CASTLE_DEFAULT_BASE
    )
    return base.rstrip("/")


def request_castle_solves(
    api_key: str,
    *,
    count: int = 1,
    user_agent: str = "",
    email: str = "",
    display_name: str = "",
    cuid: str = "",
    timeout: int = 30,
    service: str = "castlesolver.cc",
    proxy_country: Optional[str] = None,
    proxy_spec: Optional[str] = None,
    service_config: Optional[Dict[str, Any]] = None,
) -> CastleSolveResult:
    if not api_key:
        raise CastleSolverError("API anahtarÄ± eksik")

    safe_count = max(1, min(int(count or 1), 49))
    service_name = _normalize_service_name(service)

    if service_name == "castlebreaker.cc":
        return _request_castlebreaker(
            api_key,
            timeout=timeout,
            count=safe_count,
        )

    if service_name in {"code_solver_castle", "codesolvercastle"}:
        return _request_code_solver_castle(
            api_key,
            user_agent=user_agent,
            email=email,
            display_name=display_name,
            timeout=timeout,
            proxy_spec=proxy_spec,
            service_config=service_config or {"api_key": api_key},
        )

    return _request_castlesolver(
        api_key,
        count=safe_count,
        user_agent=user_agent,
        cuid=cuid,
        timeout=timeout,
        proxy_country=proxy_country,
    )


def _build_custom_proxy_payload(proxy_spec: Optional[str]) -> Optional[Dict[str, str]]:
    if not proxy_spec:
        return None
    value = str(proxy_spec).strip()
    if not value:
        return None

    if "://" in value:
        value = value.split("://", 1)[1]

    parts = value.split(":")
    if len(parts) < 2:
        return None

    address = f"{parts[0].strip()}:{parts[1].strip()}"
    username = parts[2].strip() if len(parts) >= 3 else ""
    password = ":".join(parts[3:]).strip() if len(parts) >= 4 else ""

    payload = {"Address": address}
    if username:
        payload["Username"] = username
    if password:
        payload["Password"] = password
    return payload


def _request_castlesolver(
    api_key: str,
    *,
    count: int,
    user_agent: str,
    cuid: str,
    timeout: int,
    proxy_country: Optional[str],
) -> CastleSolveResult:
    payload: Dict[str, Any] = {
        "site": "twitter",
        "api_key": api_key,
        "count": count,
        "accept_language": "en-US,en",
        "referral_code":"xgen15",
    }

    if proxy_country:
        payload["proxy_country"] = proxy_country
    else:
        payload["proxy_country"] = "tr"

    cuid_value = (cuid or "").strip()
    if cuid_value:
        payload["__cuid"] = cuid_value

    chrome_version = _extract_chrome_version(user_agent)
    if chrome_version:
        payload["browser_version"] = chrome_version

    try:
        response = requests.post(_CASTLESOLVER_ENDPOINT, json=payload, timeout=timeout)
        response.raise_for_status()
    except requests.RequestException as exc:
        log_debug_event(
            "CastleSolver HTTP HatasÄ±",
            {"error": str(exc), "payload": payload},
        )
        raise CastleSolverError(f"HTTP isteÄŸi baÅŸarÄ±sÄ±z: {exc}") from exc

    try:
        data = response.json()
    except ValueError as exc:
        log_debug_event("CastleSolver JSON HatasÄ±", response.text)
        raise CastleSolverError("Servisten geÃ§ersiz JSON cevabÄ± alÄ±ndÄ±") from exc

    log_debug_event("CastleSolver yanÄ±tÄ±", data)

    insufficient_balance_hint = _detect_insufficient_balance(data)
    if insufficient_balance_hint:
        log_debug_event(
            "CastleSolver yetersiz bakiye", {
                "mesaj": insufficient_balance_hint,
                "girdi": data,
            }
        )
        raise CastleSolverError(
            "CastleSolver bakiyesi tÃ¼kendi ya da yetersiz gÃ¶rÃ¼nÃ¼yor "
            f"(servis mesajÄ±: {insufficient_balance_hint})."
        )

    tokens = [str(token) for token in data.get("tokens", []) if isinstance(token, str) and token.strip()]
    cuid = str(data.get("__cuid", "") or "")
    timestamp = str(data.get("timestamp", "") or "")
    cost = None
    balance = None
    solves = None

    try:
        cost_value = data.get("cost")
        if cost_value is not None:
            cost = float(cost_value)
    except (TypeError, ValueError):
        cost = None

    try:
        balance_value = data.get("balance")
        if balance_value is not None:
            balance = float(balance_value)
    except (TypeError, ValueError):
        balance = None

    try:
        solves_value = data.get("solves")
        if solves_value is not None:
            solves = int(solves_value)
    except (TypeError, ValueError):
        solves = None

    result = CastleSolveResult(
        tokens=tokens,
        cuid=cuid,
        timestamp=timestamp,
        cost=cost,
        balance=balance,
        solves=solves,
        raw=data,
    )

    if cuid:
        log_debug_event("CastleSolver CUID", {"cuid": cuid})
    if tokens:
        log_debug_event(
            "CastleSolver Tokenler",
            {"primary_token": tokens[0], "extra_tokens": tokens[1:]},
        )

    return result




def _detect_insufficient_balance(data: Dict[str, Any]) -> Optional[str]:
    """CastleSolver yanÄ±tÄ± iÃ§inde bakiyenin tÃ¼kendiÄŸine dair izleri ara."""

    if not isinstance(data, dict):
        return None

    text_candidates: List[str] = []
    for key in ("error", "message", "detail", "Reason", "reason", "status", "Status"):
        value = data.get(key)
        if isinstance(value, str):
            cleaned = value.strip()
            if cleaned:
                text_candidates.append(cleaned)
                lowered = cleaned.lower()
                for marker in _INSUFFICIENT_BALANCE_MARKERS:
                    if marker in lowered:
                        if marker in _INSUFFICIENT_BALANCE_STRONG_MARKERS:
                            return cleaned
                        if any(warning in lowered for warning in _INSUFFICIENT_BALANCE_WARNINGS):
                            return cleaned

    numeric_keys = (
        "balance",
        "credits",
        "credit",
        "remaining_balance",
        "remaining",
        "balance_left",
        "left",
    )
    for key in numeric_keys:
        value = data.get(key)
        try:
            numeric = float(value)
        except (TypeError, ValueError):
            continue
        if numeric <= 0:
            return text_candidates[0] if text_candidates else str(value)

    success_value = data.get("success")
    if isinstance(success_value, bool) and not success_value and text_candidates:
        lowered = text_candidates[0].lower()
        if any(marker in lowered for marker in _INSUFFICIENT_BALANCE_MARKERS):
            return text_candidates[0]

    return None


def fetch_balance(api_key: str, *, timeout: int = 30) -> Dict[str, Any]:
    """CastleSolver hesabÄ±nÄ±n bakiye bilgilerini sorgula."""

    if not api_key:
        raise CastleSolverError("API anahtarÄ± eksik")

    headers = {"x-api-key": api_key}
    params = {"api_key": api_key}

    try:
        response = requests.get(
            _CASTLESOLVER_BALANCE_ENDPOINT,
            headers=headers,
            params=params,
            timeout=timeout,
        )
        response.raise_for_status()
    except requests.RequestException as exc:
        log_debug_event(
            "CastleSolver bakiye HTTP hatasÄ±",
            {"error": str(exc)},
        )
        raise CastleSolverError(f"HTTP isteÄŸi baÅŸarÄ±sÄ±z: {exc}") from exc

    try:
        data: Dict[str, Any] = response.json()
    except ValueError as exc:
        log_debug_event("CastleSolver bakiye JSON hatasÄ±", response.text)
        raise CastleSolverError("Servisten geÃ§ersiz JSON cevabÄ± alÄ±ndÄ±") from exc

    log_debug_event(
        "CastleSolver bakiye yanÄ±tÄ±",
        {"status_code": response.status_code, "body": data},
    )

    return data


def _request_castlebreaker(
    api_key: str,
    *,
    timeout: int,
    count: int = 2,
) -> CastleSolveResult:
    headers = {"x-api-key": api_key}
    params = {"count": count}

    try:
        response = requests.get(
            _CASTLEBREAKER_ENDPOINT,
            headers=headers,
            params=params,
            timeout=timeout,
        )
        response.raise_for_status()
    except requests.RequestException as exc:
        log_debug_event(
            "CastleBreaker HTTP HatasÄ±",
            {"error": str(exc)},
        )
        raise CastleSolverError(f"HTTP isteÄŸi baÅŸarÄ±sÄ±z: {exc}") from exc

    try:
        data = response.json()
    except ValueError as exc:
        log_debug_event("CastleBreaker JSON HatasÄ±", response.text)
        raise CastleSolverError("Servisten geÃ§ersiz JSON cevabÄ± alÄ±ndÄ±") from exc

    log_debug_event("CastleBreaker yanÄ±tÄ±", data)

    status = str(data.get("status") or "").lower()
    if status != "success":
        message = data.get("message") or data.get("error") or "bilinmeyen hata"
        raise CastleSolverError(f"CastleBreaker isteÄŸi baÅŸarÄ±sÄ±z: {message}")

    payload = data.get("data")
    if not isinstance(payload, dict):
        log_debug_event("CastleBreaker data alanÄ± dict deÄŸil", {"full_response": data, "data_field": payload, "data_type": type(payload).__name__})
        raise CastleSolverError(f"CastleBreaker data alanÄ± geÃ§ersiz (tip: {type(payload).__name__}). Tam yanÄ±t: {data}")

    tokens = []
    
    tokens_value = payload.get("tokens")
    if isinstance(tokens_value, list):
        tokens = [str(token).strip() for token in tokens_value if token]
    
    if not tokens:
        castle_token = payload.get("castle_token")
        if castle_token:
            token_str = str(castle_token).strip()
            if token_str:
                tokens = [token_str]
    
    if not tokens:
        log_debug_event("CastleBreaker boÅŸ token", {
            "payload_keys": list(payload.keys()),
            "tokens_value": tokens_value,
            "castle_token": payload.get("castle_token"),
            "full_response": data
        })
        raise CastleSolverError(
            f"CastleBreaker token bulunamadÄ±. Payload anahtarlarÄ±: {list(payload.keys())}. "
            f"Tam yanÄ±t: {data}"
        )
    
    log_debug_event("CastleBreaker token(s) Ã§Ä±karÄ±ldÄ±", {
        "token_count": len(tokens),
        "tokens_preview": [t[:50] + "..." if len(t) > 50 else t for t in tokens]
    })

    cuid = str(payload.get("cid") or "")
    accept_lang = str(payload.get("accept_lang") or "")
    sec_ch_ua = str(payload.get("sec_ch_ua") or "")
    sec_ch_ua_platform = str(payload.get("sec_ch_ua_platform") or "")
    user_agent = str(payload.get("user_agent") or "")

    credits_value = data.get("credits")
    balance = None
    try:
        if credits_value is not None:
            balance = float(credits_value)
    except (TypeError, ValueError):
        balance = None

    result = CastleSolveResult(
        tokens=tokens,
        cuid=cuid,
        timestamp="",
        cost=None,
        balance=balance,
        solves=None,
        raw=data,
        accept_lang=accept_lang if accept_lang else None,
        sec_ch_ua=sec_ch_ua if sec_ch_ua else None,
        sec_ch_ua_platform=sec_ch_ua_platform if sec_ch_ua_platform else None,
        user_agent=user_agent if user_agent else None,
    )

    if cuid:
        log_debug_event("CastleBreaker CUID", {"cuid": cuid})
    if tokens:
        log_debug_event(
            "CastleBreaker Tokens",
            {"token_count": len(tokens), "tokens": tokens},
        )

    return result


def _request_code_solver_castle(
    api_key: str,
    *,
    user_agent: str,
    email: str,
    display_name: str,
    timeout: int,
    proxy_spec: Optional[str],
    service_config: Dict[str, Any],
) -> CastleSolveResult:
    base_endpoint = _resolve_code_solver_base(service_config)
    endpoint = str(
        service_config.get("endpoint") or f"{base_endpoint}/api/token/createTask"
    ).strip()
    api_key_value = str(service_config.get("api_key") or api_key or "").strip()
    if not api_key_value:
        raise CastleSolverError("code_solver_castle: API anahtarÄ± eksik")

    site_url = str(service_config.get("site_url") or "").strip() or "https://x.com/i/flow/signup"
    site_key = str(service_config.get("site_key") or "").strip() or "CASTLE"
    safe_email = str(email or "").strip()
    safe_display_name = str(display_name or "").strip()
    if not safe_email:
        raise CastleSolverError("code_solver_castle: blob email eksik")
    if not safe_display_name:
        raise CastleSolverError("code_solver_castle: blob display_name eksik")
    blob_value = f"{safe_email}|{safe_display_name}"

    proxy_payload = _build_custom_proxy_payload(service_config.get("proxy") or proxy_spec)

    payload: Dict[str, Any] = {
        "ApiKey": api_key_value,
        "Blob": blob_value,
        "UserAgent": user_agent,
        "SiteKey": site_key,
        "SiteUrl": site_url,
    }
    if proxy_payload:
        payload["Proxy"] = proxy_payload

    try:
        response = requests.post(
            endpoint,
            json=payload,
            timeout=max(5, timeout),
        )
        response.raise_for_status()
    except requests.RequestException as exc:
        response_obj = getattr(exc, "response", None)
        response_status = getattr(response_obj, "status_code", None)
        response_text = ""
        if response_obj is not None:
            try:
                response_text = str(response_obj.text or "").strip()
            except Exception:
                response_text = ""
        if response_text and len(response_text) > 500:
            response_text = response_text[:497] + "..."
        log_debug_event(
            "code_solver_castle HTTP error (createTask)",
            {
                "error": str(exc),
                "endpoint": endpoint,
                "status_code": response_status,
                "response_body": response_text,
                "payload": payload,
            },
        )
        detail = ""
        if response_status is not None:
            detail += f" status={response_status}"
        if response_text:
            detail += f" body={response_text}"
        if detail:
            detail = f" ({detail.strip()})"
        raise CastleSolverError(
            f"code_solver_castle createTask failed: {exc}{detail}"
        ) from exc

    try:
        create_data = response.json()
    except ValueError as exc:
        log_debug_event("code_solver_castle JSON hatasÄ± (createTask)", response.text)
        raise CastleSolverError("code_solver_castle createTask geÃ§ersiz JSON dÃ¶ndÃ¼rdÃ¼") from exc

    if not isinstance(create_data, dict):
        raise CastleSolverError("code_solver_castle createTask beklenmeyen yanÄ±t dÃ¶ndÃ¼rdÃ¼")

    if create_data.get("error"):
        error_code = str(create_data.get("error") or "").upper()
        task_type = str(create_data.get("taskType") or "").upper()
        message = create_data.get("message") or create_data
        raise CastleSolverError(f"code_solver_castle createTask hata: {error_code or task_type or message}")

    if not create_data.get("success"):
        detail = create_data.get("taskType") or create_data.get("message") or create_data
        raise CastleSolverError(f"code_solver_castle createTask hata: {detail}")

    session_id = str(create_data.get("sessionId") or create_data.get("session_id") or "").strip()
    if not session_id:
        raise CastleSolverError("code_solver_castle createTask sessionId boÅŸ dÃ¶ndÃ¼")

    get_url_template = service_config.get("get_endpoint") or f"{base_endpoint}/api/Token/getTask/{{session_id}}?apiKey={{api_key}}"
    get_url = get_url_template.format(session_id=session_id, api_key=api_key_value)
    poll_attempts = 0
    max_attempts = max(1, int(service_config.get("max_attempts", 60) or 60))
    while poll_attempts < max_attempts:
        poll_attempts += 1
        try:
            get_response = requests.get(get_url, timeout=max(5, timeout))
            get_response.raise_for_status()
        except requests.RequestException as exc:
            log_debug_event(
                "code_solver_castle HTTP hatasÄ± (getTask)",
                {"error": str(exc), "url": get_url},
            )
            raise CastleSolverError(f"code_solver_castle getTask baÅŸarÄ±sÄ±z: {exc}") from exc

        try:
            data = get_response.json()
        except ValueError as exc:
            log_debug_event("code_solver_castle JSON hatasÄ± (getTask)", get_response.text)
            raise CastleSolverError("code_solver_castle getTask geÃ§ersiz JSON dÃ¶ndÃ¼rdÃ¼") from exc

        if not isinstance(data, dict):
            raise CastleSolverError("code_solver_castle getTask beklenmeyen yanÄ±t dÃ¶ndÃ¼rdÃ¼")

        if data.get("error"):
            error_code = str(data.get("error") or "").upper()
            task_type = str(data.get("taskType") or "").upper()
            message = data.get("message") or data
            # PROCESSING ise beklemeye devam
            if task_type == "PROCESSING":
                time.sleep(1.0)
                continue
            raise CastleSolverError(f"code_solver_castle getTask hata: {error_code or task_type or message}")

        task_type = str(data.get("taskType") or "").strip().upper()
        token_value = str(data.get("token") or "").strip()

        if task_type == "PROCESSING" or (not token_value and data.get("success") is True):
            time.sleep(1.0)
            continue

        if not data.get("success"):
            detail = data.get("taskType") or data.get("message") or data
            raise CastleSolverError(f"code_solver_castle getTask hata: {detail}")

        if not token_value:
            raise CastleSolverError("code_solver_castle getTask token boÅŸ dÃ¶ndÃ¼")

        raw_solver_details = data.get("solverDetails") or data.get("solver_details")
        solver_details: Dict[str, Any] = (
            raw_solver_details if isinstance(raw_solver_details, dict) else {}
        )
        cuid_value = str(
            solver_details.get("__cuid") or solver_details.get("cuid") or ""
        )
        user_agent_value = ""
        for key in ("userAgent", "useragent", "user_agent", "UserAgent"):
            candidate = solver_details.get(key)
            if isinstance(candidate, str):
                candidate = candidate.strip()
                if candidate:
                    user_agent_value = candidate
                    break
        accept_language_value = ""
        for key in ("accept_language", "acceptLanguage", "accept-language"):
            candidate = solver_details.get(key)
            if isinstance(candidate, str):
                candidate = candidate.strip()
                if candidate:
                    accept_language_value = candidate
                    break

        task_password_token = ""
        for key in (
            "task_password_verification_castle",
            "taskPasswordVerificationCastle",
        ):
            candidate = solver_details.get(key)
            if isinstance(candidate, str):
                candidate = candidate.strip()
                if candidate:
                    task_password_token = candidate
                    break


        result = CastleSolveResult(
            tokens=[token_value],
            cuid=cuid_value,
            timestamp="",
            cost=None,
            balance=None,
            solves=None,
            raw=data,
            user_agent=user_agent_value or None,
            accept_lang=accept_language_value or None,
            task_password_verification_castle=task_password_token or None,
        )

        log_debug_event(
            "code_solver_castle token alÄ±ndÄ±",
            {"primary_token": token_value, "cuid": cuid_value},
        )

        return result

    raise CastleSolverError("code_solver_castle getTask zaman aÅŸÄ±mÄ±: PROCESSING beklemesi bitti")
