from __future__ import annotations

import json
import os
import urllib.error
import urllib.request
from typing import Any, Dict, List, Optional

from pydantic import BaseModel, ConfigDict, Field

from aiel_sdk.errors import AielError


def _normalize_join(base_url: str, path: str) -> str:
    return base_url.rstrip("/") + "/" + path.lstrip("/")


def _try_get_keyring_token(service: str = "aiel", username: str = "default") -> str | None:
    try:
        import keyring  # type: ignore
    except Exception:
        return None
    try:
        return keyring.get_password(service, username)
    except Exception:
        return None


def _resolve_token(explicit: str | None) -> str | None:
    if explicit:
        return explicit

    # 1) Cloud/CI
    env_token = os.getenv("AIEL_TOKEN")
    if env_token:
        return env_token

    # 2) Local dev
    kr = _try_get_keyring_token()
    if kr:
        return kr

    return None


class IntegrationApiError(AielError):
    def __init__(self, status: int, message: str, body: Any | None = None):
        details = f"Integration API error ({status}): {message}"
        if body is not None:
            details += f" | body={body}"
        super().__init__(details)
        self.status = status
        self.body = body


class IntegrationConnector(BaseModel):
    model_config = ConfigDict(extra="allow")
    id: str
    version: str


class IntegrationAuth(BaseModel):
    model_config = ConfigDict(extra="allow")
    method: str
    resource_credential_id: Optional[str] = None
    oauth_client_profile_id: Optional[str] = None


class IntegrationConnection(BaseModel):
    model_config = ConfigDict(extra="allow")

    connection_id: str
    workspace_id: str
    project_id: str
    provider: str
    connector: IntegrationConnector
    resource_type: str
    display_name: str
    status: str
    status_reason: Optional[str] = None
    capabilities: List[str] = Field(default_factory=list)
    allowed_actions: List[str] = Field(default_factory=list)
    granted_actions: List[str] = Field(default_factory=list)
    auth: IntegrationAuth
    metadata: Dict[str, Any] = Field(default_factory=dict)


class IntegrationsClient:
    def __init__(
        self,
        base_url: str,
        api_key: str | None = None,
        timeout: float = 30.0,
        extra_headers: Optional[Dict[str, str]] = None,
        # optional migration fallback
        use_x_api_token_fallback: bool = False,
    ) -> None:
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self.extra_headers = extra_headers or {}
        self.use_x_api_token_fallback = use_x_api_token_fallback

        if not self.base_url:
            raise ValueError("base_url is required")

        # Resolve lazily but store once (predictable behavior)
        self._token = _resolve_token(api_key)

    def list(self, workspace_id: str, project_id: str) -> List[IntegrationConnection]:
        path = f"/v1/workspaces/{workspace_id}/projects/{project_id}/integrations"
        data = self._request("GET", path)
        return [IntegrationConnection.model_validate(item) for item in (data or [])]

    def get(self, workspace_id: str, project_id: str, connection_id: str) -> IntegrationConnection:
        path = f"/v1/workspaces/{workspace_id}/projects/{project_id}/integrations/{connection_id}"
        data = self._request("GET", path)
        return IntegrationConnection.model_validate(data)

    def invoke_action(
        self,
        workspace_id: str,
        project_id: str,
        connection_id: str,
        action: str,
        payload: Optional[Dict[str, Any]] = None,
    ) -> tuple[Dict[str, Any], int]:
        path = (
            f"/v1/workspaces/{workspace_id}/projects/{project_id}"
            f"/integrations/{connection_id}/action/{action}:invoke"
        )
        data = self._request("POST", path, json_body=payload or {})
        return data or {}

    def _build_headers(self) -> Dict[str, str]:
        headers = {"Content-Type": "application/json", **self.extra_headers}

        if self._token:
            # Preferred, standard
            headers["Authorization"] = f"Bearer {self._token}"

            # Optional transition (only if you still need it in CP today)
            if self.use_x_api_token_fallback:
                headers["X-API-Token"] = self._token

        return headers
    
    def _request(self, method: str, path: str, json_body: Any | None = None) -> Any:
        url = _normalize_join(self.base_url, path)
        headers = self._build_headers()

        data = None
        if json_body is not None:
            data = json.dumps(json_body).encode("utf-8")

        req = urllib.request.Request(url, data=data, headers=headers, method=method)
        try:
            with urllib.request.urlopen(req, timeout=self.timeout) as resp:
                raw = resp.read()
                if not raw:
                    return None
                try:
                    return json.loads(raw.decode("utf-8"))
                except json.JSONDecodeError:
                    return raw.decode("utf-8")
                # ✅ If server returned 200 but your envelope says it failed, raise anyway
            if isinstance(body, dict) and body.get("ok") is False:
                status = int(body.get("http_status") or 500)
                err = body.get("error") or {}
                msg = err.get("message") or "Request failed"
                raise IntegrationApiError(status, msg, body)

            return body
        except urllib.error.HTTPError as e:
            body = None
            try:
                raw = e.read()
                if raw:
                    body = json.loads(raw.decode("utf-8"))
            except Exception:
                body = None
            raise IntegrationApiError(e.code, e.reason, body) from e
