"""
Low-level HTTP client for the LITCOIN coordinator API.
"""

import time
import logging
import requests as _requests

log = logging.getLogger("litcoin")

COORDINATOR_URL = "https://api.litcoiin.xyz"
SDK_VERSION = "4.0.0"
BACKOFF = [2, 4, 8, 16, 30, 60]


class APIError(Exception):
    """Raised when the coordinator returns an error."""
    def __init__(self, status, message, body=None):
        self.status = status
        self.message = message
        self.body = body
        super().__init__(f"HTTP {status}: {message}")


class CoordinatorAPI:
    """HTTP client wrapping all coordinator endpoints."""

    def __init__(self, base_url=None):
        self.base_url = (base_url or COORDINATOR_URL).rstrip("/")
        self.session = _requests.Session()
        self.session.headers["Content-Type"] = "application/json"
        self.session.headers["X-Litcoin-SDK"] = SDK_VERSION

    def _request(self, method, path, retries=3, **kwargs):
        url = f"{self.base_url}{path}"
        for attempt in range(retries):
            try:
                r = self.session.request(method, url, timeout=30, **kwargs)
                if r.status_code == 429:
                    wait = BACKOFF[min(attempt, len(BACKOFF) - 1)]
                    log.warning(f"Rate limited on {path}, retry in {wait}s")
                    time.sleep(wait)
                    continue
                return r
            except Exception as e:
                if attempt < retries - 1:
                    wait = BACKOFF[min(attempt, len(BACKOFF) - 1)]
                    log.warning(f"Request failed ({e}), retry in {wait}s")
                    time.sleep(wait)
                else:
                    raise APIError(0, f"All {retries} retries exhausted: {e}")
        raise APIError(429, "Rate limited after all retries")

    def _get(self, path, **kwargs):
        return self._request("GET", path, **kwargs)

    def _post(self, path, **kwargs):
        return self._request("POST", path, **kwargs)

    # ─── Auth ─────────────────────────────────────────────────────────────────

    def get_nonce(self, wallet):
        r = self._post("/v1/auth/nonce", json={"miner": wallet})
        if r.status_code != 200:
            raise APIError(r.status_code, "Nonce failed", r.text)
        return r.json()["message"]

    def verify_auth(self, wallet, message, signature):
        r = self._post("/v1/auth/verify", json={
            "miner": wallet, "message": message, "signature": signature
        })
        if r.status_code != 200:
            raise APIError(r.status_code, "Auth verify failed", r.text)
        return r.json()["token"]

    # ─── Mining ───────────────────────────────────────────────────────────────

    def get_challenge(self, nonce, auth_token):
        r = self._get("/v1/challenge", params={"nonce": nonce},
                       headers={"Authorization": f"Bearer {auth_token}"})
        if r.status_code == 401:
            raise APIError(401, "Auth expired")
        if r.status_code == 403:
            raise APIError(403, r.json().get("error", "Forbidden"), r.text)
        if r.status_code != 200:
            raise APIError(r.status_code, "Challenge failed", r.text)
        return r.json()

    def submit_solution(self, challenge_id, artifact, nonce, auth_token):
        r = self._post("/v1/submit", json={
            "challengeId": challenge_id, "artifact": artifact, "nonce": nonce
        }, headers={"Authorization": f"Bearer {auth_token}"})
        if r.status_code == 401:
            raise APIError(401, "Auth expired")
        if r.status_code not in (200, 201):
            raise APIError(r.status_code, "Submit failed", r.text)
        return r.json()

    # ─── Claims ───────────────────────────────────────────────────────────────

    def claims_status(self, wallet):
        r = self._get("/v1/claims/status", params={"wallet": wallet})
        if r.status_code != 200:
            raise APIError(r.status_code, "Claims status failed", r.text)
        return r.json()

    def claims_sign(self, wallet):
        r = self._post("/v1/claims/sign", json={"wallet": wallet})
        if r.status_code != 200:
            raise APIError(r.status_code, "Claims sign failed", r.text)
        return r.json()

    def claims_bankr(self, wallet):
        """Claim via coordinator (for Bankr smart wallets)."""
        r = self._post("/v1/claims/bankr", json={"wallet": wallet})
        if r.status_code != 200:
            raise APIError(r.status_code, "Bankr claim failed", r.text)
        return r.json()

    # ─── Stats ────────────────────────────────────────────────────────────────

    def network_stats(self):
        r = self._get("/v1/claims/stats")
        return r.json()

    def leaderboard(self, limit=20):
        r = self._get("/v1/claims/leaderboard", params={"limit": limit})
        return r.json()

    def health(self):
        r = self._get("/v1/health")
        return r.json()

    # ─── Staking ──────────────────────────────────────────────────────────────

    def get_boost(self, wallet):
        r = self._get("/v1/boost", params={"wallet": wallet})
        if r.status_code != 200:
            raise APIError(r.status_code, "Boost check failed", r.text)
        return r.json()

    def register_staker(self, wallet):
        r = self._post("/v1/staking/register", json={"wallet": wallet})
        return r.json()

    def staking_yield(self, wallet):
        r = self._get("/v1/staking/yield", params={"wallet": wallet})
        return r.json()

    def staking_stats(self):
        r = self._get("/v1/staking/stats")
        return r.json()

    # ─── Compute ──────────────────────────────────────────────────────────────

    def compute_request(self, prompt, model=None, max_tokens=4096, wallet=None):
        payload = {"prompt": prompt, "maxTokens": max_tokens}
        if model:
            payload["model"] = model
        if wallet:
            payload["wallet"] = wallet
        r = self._post("/v1/compute/request", json=payload)
        if r.status_code != 200:
            raise APIError(r.status_code, "Compute request failed", r.text)
        return r.json()

    def compute_health(self):
        r = self._get("/v1/compute/health")
        return r.json()

    def compute_providers(self):
        r = self._get("/v1/compute/providers")
        return r.json()

    def compute_stats(self):
        r = self._get("/v1/compute/stats")
        return r.json()

    # ─── Research ──────────────────────────────────────────────────────────

    def research_list_tasks(self, task_type=None):
        params = {}
        if task_type:
            params["type"] = task_type
        r = self._get("/v1/research/tasks", params=params)
        return r.json()

    def research_get_task(self, task_id):
        r = self._get(f"/v1/research/task/{task_id}")
        if r.status_code != 200:
            raise APIError(r.status_code, "Task not found", r.text)
        return r.json()

    def research_fetch_task(self, task_type=None, miner=None):
        payload = {}
        if task_type:
            payload["type"] = task_type
        if miner:
            payload["miner"] = miner
        r = self._post("/v1/research/task", json=payload)
        if r.status_code != 200:
            raise APIError(r.status_code, r.json().get("error", "No tasks"), r.text)
        return r.json()

    def research_submit(self, task_id, miner, code, model=None, model_provider=None):
        payload = {"taskId": task_id, "miner": miner, "code": code}
        if model:
            payload["model"] = model
        if model_provider:
            payload["modelProvider"] = model_provider

        r = self._post("/v1/research/submit", json=payload, retries=1)

        if r.status_code == 202:
            # Async mode — poll for result
            data = r.json()
            sub_id = data.get("submissionId")
            if not sub_id:
                raise APIError(202, "Queued but no submissionId returned")

            import time as _time
            poll_start = _time.time()
            while _time.time() - poll_start < 600:  # 10 min max (5 min block + verification)
                _time.sleep(5)
                try:
                    pr = self._get(f"/v1/research/submission-status/{sub_id}")
                    if pr.status_code == 404:
                        raise APIError(404, "Submission expired or not found")
                    pd = pr.json()
                    status = pd.get("status", "unknown")
                    if status in ("pending", "verifying"):
                        continue
                    if status in ("complete", "failed"):
                        return pd
                except APIError:
                    raise
                except Exception:
                    continue
            raise APIError(408, "Timed out waiting for verification result")

        if r.status_code not in (200, 429):
            raise APIError(r.status_code, "Submit failed", r.text)
        return r.json()

    def research_results(self, task_id=None, miner=None, limit=50):
        params = {"limit": limit}
        if task_id:
            params["taskId"] = task_id
        if miner:
            params["miner"] = miner
        r = self._get("/v1/research/results", params=params)
        return r.json()

    def research_leaderboard(self, task_id=None):
        params = {}
        if task_id:
            params["taskId"] = task_id
        r = self._get("/v1/research/leaderboard", params=params)
        return r.json()

    def research_stats(self):
        r = self._get("/v1/research/stats")
        return r.json()

    def research_history(self, miner, task_id=None):
        params = {"miner": miner}
        if task_id:
            params["taskId"] = task_id
        r = self._get("/v1/research/history", params=params)
        return r.json()

    def research_post_bounty(self, title, task_type, description, baseline, constraints, reward_base=None, tags=None, poster=None):
        payload = {
            "title": title, "type": task_type,
            "description": description, "baseline": baseline,
            "constraints": constraints,
        }
        if reward_base:
            payload["reward_base"] = reward_base
        if tags:
            payload["tags"] = tags
        if poster:
            payload["poster"] = poster
        r = self._post("/v1/research/bounty", json=payload)
        if r.status_code != 200:
            raise APIError(r.status_code, "Bounty post failed", r.text)
        return r.json()
