"""2captcha API v2 client — RecaptchaV2EnterpriseTaskProxyless."""
import time
from typing import Dict, Optional

import requests

from utils.logger import child

log = child("captcha")

API_BASE = "https://api.2captcha.com"


def solve_recaptcha_v2(
    client_key: str,
    website_url: str,
    website_key: str,
    *,
    enterprise: bool = True,
    invisible: bool = False,
    warmup_s: float = 6.0,
    poll_interval_s: float = 5.0,
    max_polls: int = 24,
) -> Dict:
    task_type = "RecaptchaV2EnterpriseTaskProxyless" if enterprise else "RecaptchaV2TaskProxyless"
    try:
        r = requests.post(
            f"{API_BASE}/createTask",
            json={
                "clientKey": client_key,
                "task": {
                    "type": task_type,
                    "websiteURL": website_url,
                    "websiteKey": website_key,
                    "isInvisible": bool(invisible),
                },
            },
            timeout=20,
        )
        data = r.json()
        if data.get("errorId"):
            return {"ok": False, "error": data.get("errorDescription") or "createTask error"}
        task_id = data.get("taskId")
        if not task_id:
            return {"ok": False, "error": "no taskId returned"}
    except Exception as e:
        return {"ok": False, "error": f"createTask: {e}"}

    log.info(f"2captcha task {task_id} created (type={task_type})")
    time.sleep(warmup_s)

    for i in range(max_polls):
        try:
            r = requests.post(
                f"{API_BASE}/getTaskResult",
                json={"clientKey": client_key, "taskId": task_id},
                timeout=20,
            )
            d = r.json()
            if d.get("errorId"):
                return {"ok": False, "error": d.get("errorDescription") or "getTaskResult error"}
            status = d.get("status")
            if status == "ready":
                sol = d.get("solution") or {}
                token = sol.get("gRecaptchaResponse") or sol.get("token")
                if not token:
                    return {"ok": False, "error": "empty token in ready solution"}
                log.info(f"2captcha solved task {task_id} in ~{warmup_s + (i+1)*poll_interval_s:.0f}s")
                return {"ok": True, "token": token}
            # status=processing → keep polling
        except Exception as e:
            log.warning(f"getTaskResult poll #{i}: {e}")
        time.sleep(poll_interval_s)

    return {"ok": False, "error": "2captcha timeout"}


_INJECT_JS_TEMPLATE = r"""
(function() {
  var tok = __TOKEN__;
  try {
    var tas = document.querySelectorAll('textarea[name="g-recaptcha-response"], #g-recaptcha-response, textarea[id^="g-recaptcha-response"]');
    tas.forEach(function (t) {
      t.value = tok; t.innerHTML = tok;
      t.dispatchEvent(new Event('change', { bubbles: true }));
      t.dispatchEvent(new Event('input',  { bubbles: true }));
    });
  } catch (e) {}
  try { if (typeof window.onChange === 'function') window.onChange(tok); } catch (e) {}
  try {
    document.querySelectorAll('[data-sitekey]').forEach(function (el) {
      var cbName = el.getAttribute('data-callback');
      if (cbName && typeof window[cbName] === 'function') {
        try { window[cbName](tok); } catch (e) {}
      }
    });
  } catch (e) {}
  function walkCfg(cfg) {
    try {
      if (!cfg || !cfg.clients) return;
      var seen = new WeakSet(); var stack = [cfg.clients];
      while (stack.length) {
        var o = stack.pop();
        if (!o || typeof o !== 'object' || seen.has(o)) continue;
        seen.add(o);
        for (var k in o) {
          try {
            var v = o[k];
            if (typeof v === 'function' && v.length >= 1 && /callback|recaptcha/i.test((v.toString()||'').slice(0, 200))) {
              try { v(tok); } catch (e) {}
            } else if (typeof v === 'object') stack.push(v);
          } catch (e) {}
        }
      }
    } catch (e) {}
  }
  walkCfg(window.___grecaptcha_cfg);
  walkCfg(window.___grecaptcha_enterprise_cfg);
})();
"""


def inject_token(sb, token: str) -> None:
    import json as _json
    # UC mode's CDP execute_script doesn't bind `arguments`. Bake the token
    # in as a JSON literal (handles quotes + newlines safely).
    js = _INJECT_JS_TEMPLATE.replace("__TOKEN__", _json.dumps(token))
    try:
        sb.execute_script(js)
    except Exception as e:
        log.warning(f"inject_token failed: {e}")


_DETECT_JS = r"""
(function() {
  var out = { _debug: [] };
  var iframes = document.querySelectorAll('iframe');
  out._debug.push('total iframes: ' + iframes.length);
  for (var i = 0; i < iframes.length; i++) {
    var raw = iframes[i].src || iframes[i].getAttribute('src') || '';
    if (raw.indexOf('recaptcha') === -1) continue;
    out._debug.push('iframe[' + i + '] has recaptcha; len=' + raw.length + ' rawSample=' + raw.slice(0, 60));
    // Some pages carry HTML-entity-encoded &amp; in src — normalise first.
    var src = raw.replace(/&amp;/g, '&').replace(/&#38;/g, '&');
    var key = null;
    try {
      var u = new URL(src);
      key = u.searchParams.get('k');
    } catch (e) {
      out._debug.push('URL parse error: ' + e.message);
    }
    if (!key) {
      var m = src.match(/[?&]k=([^&#]+)/);
      if (m) key = decodeURIComponent(m[1]);
    }
    if (key) {
      out._debug.push('key=' + key);
      out.siteKey = key;
      out.invisible = /size=invisible/.test(src);
      out.enterprise = /\/enterprise\//.test(src);
      return out;
    }
  }
  var widget = document.querySelector('[data-sitekey]');
  if (widget) {
    out.siteKey = widget.getAttribute('data-sitekey');
    out.invisible = widget.getAttribute('data-size') === 'invisible';
    out.enterprise = false;
    return out;
  }
  return out;
})();
"""


def detect(sb) -> Optional[Dict]:
    # Use sb.execute_script (CDP path) — raw driver.execute_script hits
    # chromedriver's HTTP port which is closed in UC mode.
    try:
        res = sb.execute_script(_DETECT_JS)
        if res and res.get("_debug"):
            log.info(f"detect debug: {res.get('_debug')}")
        if not res or not res.get("siteKey"):
            return None
        return res
    except Exception as e:
        log.warning(f"detect execute_script raised: {e}")
        return None
