import os
import requests
from typing import List, Optional, Dict, Any

from nova_cli.nova_core.auth.storage import (
    get_access_token,
    get_refresh_token,
    update_tokens,
)
from nova_cli.nova_core.auth.client import NovaAuthClient


class BridgeyeAPIClient:
    """
    Thin HTTP client used by Nova CLI.
    This client contains NO AI logic.
    """

    def __init__(self, base_url: Optional[str] = None, timeout: int = 60):
        env_url = os.getenv("NOVA_API_BASE_URL")
        self.base_url = (base_url or env_url or "https://api.nova.bridgeye.com").rstrip("/")
        self.timeout = timeout
        self._last_refresh_error: Optional[Dict[str, Any]] = None

        self.user_agent = os.getenv("NOVA_USER_AGENT", "NovaCLI/1.0")
        # Set NOVA_DEBUG_AUTH=1 if you want verbose auth errors locally
        self.debug_auth = os.getenv("NOVA_DEBUG_AUTH", "").strip() in ("1", "true", "TRUE", "yes", "YES")

    def _headers(self) -> Dict[str, str]:
        headers: Dict[str, str] = {"User-Agent": self.user_agent}
        tok = get_access_token()
        if tok:
            headers["Authorization"] = f"Bearer {tok}"
        return headers

    def _debug(self, msg: str) -> None:
        if not self.debug_auth:
            return
        try:
            print(msg)
        except Exception:
            pass

    def _try_refresh(self) -> bool:
        rt = get_refresh_token()
        if not rt:
            self._last_refresh_error = {"kind": "no_refresh_token_on_disk"}
            return False

        auth = NovaAuthClient()
        resp = auth.refresh_access(rt)

        if not isinstance(resp, dict):
            self._last_refresh_error = {"kind": "refresh_bad_response_type", "type": str(type(resp))}
            self._debug(f"[auth] refresh failed: {self._last_refresh_error}")
            return False

        if resp.get("error"):
            self._last_refresh_error = {
                "kind": resp.get("error"),
                "status": resp.get("status"),
                "body": resp.get("body"),
            }
            self._debug(f"[auth] refresh failed: {self._last_refresh_error}")
            return False

        new_access = resp.get("access_token")
        new_refresh = resp.get("refresh_token")
        expires_in = resp.get("expires_in")
        issued_at = resp.get("issued_at")

        if not new_access or not new_refresh or not expires_in:
            self._last_refresh_error = {
                "kind": "refresh_missing_fields",
                "has_access": bool(new_access),
                "has_refresh": bool(new_refresh),
                "expires_in": expires_in,
            }
            self._debug(f"[auth] refresh failed: {self._last_refresh_error} resp={resp}")
            return False

        update_tokens(
            access_token=new_access,
            refresh_token=new_refresh,
            expires_in=int(expires_in),
            issued_at=float(issued_at) if issued_at else None,
        )

        self._last_refresh_error = None
        return True

    def _require_auth(self) -> str:
        tok = get_access_token()
        if tok:
            return tok

        if self._try_refresh():
            tok = get_access_token()
            if tok:
                return tok

        rt = get_refresh_token()
        if rt:
            raise RuntimeError("Session expired. Run: login")

        raise RuntimeError("Not logged in. Run: login")

    def _post_with_auth_retry(self, path: str, payload: Dict[str, Any]) -> requests.Response:
        self._require_auth()
        url = f"{self.base_url}{path}"

        try:
            resp = requests.post(
                url,
                json=payload,
                headers=self._headers(),
                timeout=self.timeout,
            )
        except requests.RequestException as e:
            raise RuntimeError(f"API request failed: {e}")

        if resp.status_code != 401:
            return resp

        if self._try_refresh():
            try:
                resp = requests.post(
                    url,
                    json=payload,
                    headers=self._headers(),
                    timeout=self.timeout,
                )
            except requests.RequestException as e:
                raise RuntimeError(f"API request failed after refresh: {e}")

        return resp

    def _get_with_auth_retry(self, path: str) -> requests.Response:
        self._require_auth()
        url = f"{self.base_url}{path}"

        try:
            resp = requests.get(
                url,
                headers=self._headers(),
                timeout=self.timeout,
            )
        except requests.RequestException as e:
            raise RuntimeError(f"API request failed: {e}")

        if resp.status_code != 401:
            return resp

        if self._try_refresh():
            try:
                resp = requests.get(
                    url,
                    headers=self._headers(),
                    timeout=self.timeout,
                )
            except requests.RequestException as e:
                raise RuntimeError(f"API request failed after refresh: {e}")

        return resp

    def _parse_json(self, resp: requests.Response) -> Dict[str, Any]:
        try:
            return resp.json()
        except Exception:
            body = (resp.text or "").strip()
            raise RuntimeError(f"API returned non-JSON response (status={resp.status_code}): {body[:500]}")

    def health(self) -> bool:
        """
        Simple reachability check for NOVA_API.
        Assumes NOVA_API exposes GET /health -> 200 OK.
        """
        url = f"{self.base_url}/health"
        try:
            r = requests.get(url, timeout=10, headers={"User-Agent": self.user_agent})
            return r.status_code == 200
        except Exception:
            return False

    def chat(self, prompt: str, context: Optional[Dict[str, str]], model: str, provider: str) -> str:
        """
        Calls NOVA_API chat endpoint.

        Server-side route has varied across versions; we support both:
          - POST /chat
          - POST /chat/run

        Payload matches nova_api.schemas.ChatRequest:
          { prompt: str, context: {path: content}, model: str, provider: str }

        Returns plain string output (AIResponse.output).
        """
        payload = {
            "prompt": prompt,
            "context": context or {},
            "model": model,
            "provider": provider,
        }

        tried: List[str] = []
        last_error: Optional[str] = None

        for path in ("/chat", "/chat/run"):
            tried.append(path)
            resp = self._post_with_auth_retry(path, payload)

            if resp.status_code == 401:
                raise RuntimeError("Unauthorized. Run: login")

            if resp.status_code == 404:
                last_error = f"404 on {path}"
                continue

            resp.raise_for_status()
            data = self._parse_json(resp)

            if not data.get("success"):
                raise RuntimeError(data.get("error") or "Chat failed")

            return data.get("output", "") or ""

        raise RuntimeError(
            f"Chat endpoint not found. Tried: {', '.join(tried)}. Last error: {last_error or 'unknown'}"
        )

    def refactor(self, filename: str, content: str, model: str, provider: str) -> str:
        resp = self._post_with_auth_retry(
            "/janitor/refactor",
            {"filename": filename, "content": content, "model": model, "provider": provider},
        )

        if resp.status_code == 401:
            raise RuntimeError("Unauthorized. Run: login")

        resp.raise_for_status()
        data = self._parse_json(resp)

        if not data.get("success"):
            raise RuntimeError(data.get("error") or "Janitor failed")

        return data.get("output", "")

    def run_with_healing(
        self,
        command: List[str],
        cwd: str,
        model: str,
        provider: str,
        exit_code: int,
        stdout: str,
        stderr: str,
        context: Optional[dict] = None,
        repo_map: Optional[str] = None,
        healing_history: Optional[List[str]] = None,
    ) -> str:
        payload = {
            "command": command,
            "cwd": cwd,
            "model": model,
            "provider": provider,
            "exit_code": exit_code,
            "stdout": stdout or "",
            "stderr": stderr or "",
            "context": context or {},
            "repo_map": repo_map,
            "healing_history": healing_history or [],
        }

        tried: List[str] = []
        last_error: Optional[str] = None

        for path in ("/healer/run", "/healer"):
            tried.append(path)
            resp = self._post_with_auth_retry(path, payload)

            if resp.status_code == 401:
                raise RuntimeError("Unauthorized. Run: login")

            if resp.status_code == 404:
                last_error = f"404 on {path}"
                continue

            resp.raise_for_status()
            data = self._parse_json(resp)

            if not data.get("success"):
                raise RuntimeError(data.get("error") or "Healer failed")

            return data.get("output", "") or ""

        raise RuntimeError(
            f"Healer endpoint not found. Tried: {', '.join(tried)}. Last error: {last_error or 'unknown'}"
        )
