from __future__ import annotations

import asyncio
import time
from typing import Any, Dict, Optional

import httpx

from .types import (
    ApprovalRequest,
    EvaluationResult,
    ExecutionStep,
    ExecutionTrace,
    IntentCloseResult,
    IntentShift,
    IntentTrace,
    WebhookEndpoint,
    WebhookDelivery,
    OrgMember,
    AuditLogEntry,
    RetentionPolicy,
    Subscription,
)

DEFAULT_BASE_URL = "https://agentosapi-production.up.railway.app"
MAX_RETRIES = 3


class VantageClient:
    def __init__(
        self,
        api_key: str,
        agent_id: str,
        base_url: str = DEFAULT_BASE_URL,
        debug: bool = False,
    ) -> None:
        self._api_key = api_key
        self._agent_id = agent_id
        self._base_url = base_url.rstrip("/")
        self._debug = debug

    def _headers(self) -> Dict[str, str]:
        return {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {self._api_key}",
        }

    async def _request(
        self,
        method: str,
        path: str,
        body: Optional[Dict[str, Any]] = None,
        fallback: Any = None,
    ) -> Any:
        url = f"{self._base_url}{path}"
        for attempt in range(MAX_RETRIES):
            try:
                if attempt > 0:
                    await asyncio.sleep(2 ** attempt * 0.1)

                async with httpx.AsyncClient() as http:
                    response = await http.request(
                        method,
                        url,
                        json=body,
                        headers=self._headers(),
                        timeout=30.0,
                    )

                if response.status_code >= 400:
                    raise httpx.HTTPStatusError(
                        f"HTTP {response.status_code}: {response.text}",
                        request=response.request,
                        response=response,
                    )

                if not response.text:
                    return fallback
                try:
                    data = response.json()
                except Exception:
                    data = response.text
                if self._debug:
                    print(f"[vantage] {method} {path}", data)
                return data

            except Exception as err:
                if attempt == MAX_RETRIES - 1:
                    print(f"[vantage] failed {method} {path} after retries: {err}")
                    if fallback is not None:
                        return fallback
                    raise
                if self._debug:
                    print(f"[vantage] retry {attempt + 1}/{MAX_RETRIES}: {err}")

        if fallback is not None:
            return fallback
        raise RuntimeError("Unreachable")

    # ── Events ─────────────────────────────────────────────────

    async def log_event(
        self,
        type: str,
        input: Optional[Dict[str, Any]] = None,
        output: Optional[Dict[str, Any]] = None,
        metadata: Optional[Dict[str, Any]] = None,
        risk_level: Optional[str] = None,
    ) -> Dict[str, Any]:
        payload: Dict[str, Any] = {"type": type, "agentId": self._agent_id}
        if input is not None:
            payload["input"] = input
        if output is not None:
            payload["output"] = output
        if metadata is not None:
            payload["metadata"] = metadata
        if risk_level is not None:
            payload["riskLevel"] = risk_level
        return await self._request(
            "POST",
            "/v1/events",
            payload,
            fallback={"id": "", "timestamp": _now_iso()},
        )

    # ── Execution Trace ────────────────────────────────────────

    async def start_execution(
        self,
        session_id: Optional[str] = None,
        agent_id: Optional[str] = None,
    ) -> ExecutionTrace:
        data = await self._request(
            "POST",
            "/v1/execution-traces",
            {
                "agentId": agent_id or self._agent_id,
                "sessionId": session_id,
            },
        )
        return ExecutionTrace.from_dict(data)

    async def log_step(
        self,
        trace_id: str,
        step: Dict[str, Any],
    ) -> ExecutionTrace:
        data = await self._request(
            "PATCH",
            f"/v1/execution-traces/{trace_id}",
            {"step": step},
        )
        return ExecutionTrace.from_dict(data)

    async def complete_execution(
        self,
        trace_id: str,
        status: str = "complete",
    ) -> ExecutionTrace:
        data = await self._request(
            "POST",
            f"/v1/execution-traces/{trace_id}/complete",
            {"status": status},
        )
        return ExecutionTrace.from_dict(data)

    # ── Intent Trace ───────────────────────────────────────────

    async def start_intent(
        self,
        declared_intent: str,
        execution_trace_id: Optional[str] = None,
        agent_id: Optional[str] = None,
    ) -> IntentTrace:
        data = await self._request(
            "POST",
            "/v1/intent-traces",
            {
                "agentId": agent_id or self._agent_id,
                "declaredIntent": declared_intent,
                "executionTraceId": execution_trace_id,
            },
        )
        return IntentTrace.from_dict(data)

    async def log_shift(
        self,
        intent_id: str,
        observed_intent: str,
        severity: str = "medium",
        shift_reason: Optional[str] = None,
    ) -> IntentShift:
        payload: Dict[str, Any] = {
            "observedIntent": observed_intent,
            "severity": severity,
        }
        if shift_reason is not None:
            payload["shiftReason"] = shift_reason
        data = await self._request(
            "POST",
            f"/v1/intent-traces/{intent_id}/shifts",
            payload,
        )
        return IntentShift.from_dict(data)

    async def close_intent(
        self,
        intent_id: str,
        resolved_intent: str,
    ) -> IntentCloseResult:
        data = await self._request(
            "POST",
            f"/v1/intent-traces/{intent_id}/close",
            {"resolvedIntent": resolved_intent},
        )
        return IntentCloseResult.from_dict(data)

    # ── Policy Engine ──────────────────────────────────────────

    async def evaluate(
        self,
        action_type: str,
        action_payload: Optional[Dict[str, Any]] = None,
        execution_trace_id: Optional[str] = None,
        agent_id: Optional[str] = None,
    ) -> EvaluationResult:
        data = await self._request(
            "POST",
            "/v1/evaluate",
            {
                "agentId": agent_id or self._agent_id,
                "actionType": action_type,
                "actionPayload": action_payload or {},
                "executionTraceId": execution_trace_id,
            },
        )
        return EvaluationResult.from_dict(data)

    async def wait_for_approval(
        self,
        approval_request_id: str,
        timeout_ms: int = 60_000,
        poll_interval_ms: int = 2_000,
    ) -> ApprovalRequest:
        deadline = time.monotonic() + timeout_ms / 1000.0

        while time.monotonic() < deadline:
            data = await self._request(
                "GET",
                f"/v1/approvals/{approval_request_id}",
            )
            approval = ApprovalRequest.from_dict(data)
            if approval.status in ("approved", "rejected"):
                return approval

            remaining = deadline - time.monotonic()
            if remaining <= 0:
                break
            await asyncio.sleep(min(poll_interval_ms / 1000.0, remaining))

        raise TimeoutError(
            f"Approval request {approval_request_id} timed out after {timeout_ms}ms"
        )

    # ── Webhooks ─────────────────────────────────────────────

    async def create_webhook(
        self,
        url: str,
        events: list[str],
        secret: Optional[str] = None,
    ) -> WebhookEndpoint:
        payload: Dict[str, Any] = {"url": url, "events": events}
        if secret:
            payload["secret"] = secret
        data = await self._request("POST", "/v1/webhooks", payload)
        return WebhookEndpoint.from_dict(data)

    async def list_webhooks(self) -> Dict[str, Any]:
        return await self._request("GET", "/v1/webhooks")

    async def delete_webhook(self, webhook_id: str) -> None:
        await self._request("DELETE", f"/v1/webhooks/{webhook_id}", fallback=None)

    async def test_webhook(self, webhook_id: str) -> WebhookDelivery:
        data = await self._request("POST", f"/v1/webhooks/{webhook_id}/test")
        return WebhookDelivery.from_dict(data)

    async def list_webhook_deliveries(self, webhook_id: str) -> Dict[str, Any]:
        return await self._request("GET", f"/v1/webhooks/{webhook_id}/deliveries")

    # ── Members & Audit Log ──────────────────────────────────

    async def list_members(self) -> Dict[str, Any]:
        return await self._request("GET", "/v1/members")

    async def get_member_me(self) -> OrgMember:
        data = await self._request("GET", "/v1/members/me")
        return OrgMember.from_dict(data)

    async def invite_member(self, email: str, name: str, role: str) -> OrgMember:
        data = await self._request(
            "POST", "/v1/members/invite", {"email": email, "name": name, "role": role}
        )
        return OrgMember.from_dict(data)

    async def update_member_role(self, member_id: str, role: str) -> OrgMember:
        data = await self._request("PATCH", f"/v1/members/{member_id}", {"role": role})
        return OrgMember.from_dict(data)

    async def remove_member(self, member_id: str) -> None:
        await self._request("DELETE", f"/v1/members/{member_id}", fallback=None)

    async def get_audit_log(self, action: Optional[str] = None) -> Dict[str, Any]:
        if action:
            return await self._request("GET", f"/v1/audit-log?action={action}")
        return await self._request("GET", "/v1/audit-log")

    # ── Retention Settings ───────────────────────────────────

    async def get_retention_policy(self) -> RetentionPolicy:
        data = await self._request("GET", "/v1/settings/retention")
        retention = data.get("retention", data)
        return RetentionPolicy.from_dict(retention)

    async def update_retention_policy(
        self,
        events_retention_days: Optional[int] = None,
        traces_retention_days: Optional[int] = None,
        audit_log_retention_days: Optional[int] = None,
    ) -> RetentionPolicy:
        payload: Dict[str, Any] = {}
        if events_retention_days is not None:
            payload["eventsRetentionDays"] = events_retention_days
        if traces_retention_days is not None:
            payload["tracesRetentionDays"] = traces_retention_days
        if audit_log_retention_days is not None:
            payload["auditLogRetentionDays"] = audit_log_retention_days
        data = await self._request("PATCH", "/v1/settings/retention", payload)
        retention = data.get("retention", data)
        return RetentionPolicy.from_dict(retention)

    # ── Billing ──────────────────────────────────────────────

    async def get_billing_subscription(self) -> Dict[str, Any]:
        return await self._request("GET", "/v1/billing/subscription")

    async def get_billing_usage(self) -> Dict[str, Any]:
        return await self._request("GET", "/v1/billing/usage")

    async def create_billing_upgrade(self, plan: str = "pro") -> Dict[str, Any]:
        return await self._request("POST", "/v1/billing/upgrade", {"plan": plan})

    async def create_billing_portal(self) -> Dict[str, Any]:
        return await self._request("POST", "/v1/billing/portal", {})

    async def send_billing_webhook(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        return await self._request("POST", "/v1/billing/webhook", payload)


def _now_iso() -> str:
    from datetime import datetime, timezone
    return datetime.now(timezone.utc).isoformat()


class VantageClientSync:
    """Synchronous wrapper around VantageClient for non-async codebases."""

    def __init__(
        self,
        api_key: str,
        agent_id: str,
        base_url: str = DEFAULT_BASE_URL,
        debug: bool = False,
    ) -> None:
        self._async = VantageClient(
            api_key=api_key,
            agent_id=agent_id,
            base_url=base_url,
            debug=debug,
        )

    def _run(self, coro):
        try:
            loop = asyncio.get_event_loop()
            if loop.is_running():
                import concurrent.futures
                with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
                    future = pool.submit(asyncio.run, coro)
                    return future.result()
            return loop.run_until_complete(coro)
        except RuntimeError:
            return asyncio.run(coro)

    def log_event(self, type: str, **kwargs) -> Dict[str, Any]:
        return self._run(self._async.log_event(type=type, **kwargs))

    def start_execution(self, **kwargs) -> ExecutionTrace:
        return self._run(self._async.start_execution(**kwargs))

    def log_step(self, trace_id: str, step: Dict[str, Any]) -> ExecutionTrace:
        return self._run(self._async.log_step(trace_id, step))

    def complete_execution(self, trace_id: str, status: str = "complete") -> ExecutionTrace:
        return self._run(self._async.complete_execution(trace_id, status))

    def start_intent(self, declared_intent: str, **kwargs) -> IntentTrace:
        return self._run(self._async.start_intent(declared_intent, **kwargs))

    def log_shift(self, intent_id: str, observed_intent: str, **kwargs) -> IntentShift:
        return self._run(self._async.log_shift(intent_id, observed_intent, **kwargs))

    def close_intent(self, intent_id: str, resolved_intent: str) -> IntentCloseResult:
        return self._run(self._async.close_intent(intent_id, resolved_intent))

    def evaluate(self, action_type: str, **kwargs) -> EvaluationResult:
        return self._run(self._async.evaluate(action_type, **kwargs))

    def wait_for_approval(self, approval_request_id: str, **kwargs) -> ApprovalRequest:
        return self._run(self._async.wait_for_approval(approval_request_id, **kwargs))

    def create_webhook(self, url: str, events: list[str], secret: Optional[str] = None) -> WebhookEndpoint:
        return self._run(self._async.create_webhook(url, events, secret))

    def list_webhooks(self) -> Dict[str, Any]:
        return self._run(self._async.list_webhooks())

    def delete_webhook(self, webhook_id: str) -> None:
        return self._run(self._async.delete_webhook(webhook_id))

    def test_webhook(self, webhook_id: str) -> WebhookDelivery:
        return self._run(self._async.test_webhook(webhook_id))

    def list_webhook_deliveries(self, webhook_id: str) -> Dict[str, Any]:
        return self._run(self._async.list_webhook_deliveries(webhook_id))

    def list_members(self) -> Dict[str, Any]:
        return self._run(self._async.list_members())

    def get_member_me(self) -> OrgMember:
        return self._run(self._async.get_member_me())

    def invite_member(self, email: str, name: str, role: str) -> OrgMember:
        return self._run(self._async.invite_member(email, name, role))

    def update_member_role(self, member_id: str, role: str) -> OrgMember:
        return self._run(self._async.update_member_role(member_id, role))

    def remove_member(self, member_id: str) -> None:
        return self._run(self._async.remove_member(member_id))

    def get_audit_log(self, action: Optional[str] = None) -> Dict[str, Any]:
        return self._run(self._async.get_audit_log(action))

    def get_retention_policy(self) -> RetentionPolicy:
        return self._run(self._async.get_retention_policy())

    def update_retention_policy(
        self,
        events_retention_days: Optional[int] = None,
        traces_retention_days: Optional[int] = None,
        audit_log_retention_days: Optional[int] = None,
    ) -> RetentionPolicy:
        return self._run(
            self._async.update_retention_policy(
                events_retention_days,
                traces_retention_days,
                audit_log_retention_days,
            )
        )

    def get_billing_subscription(self) -> Dict[str, Any]:
        return self._run(self._async.get_billing_subscription())

    def get_billing_usage(self) -> Dict[str, Any]:
        return self._run(self._async.get_billing_usage())

    def create_billing_upgrade(self, plan: str = "pro") -> Dict[str, Any]:
        return self._run(self._async.create_billing_upgrade(plan))

    def create_billing_portal(self) -> Dict[str, Any]:
        return self._run(self._async.create_billing_portal())

    def send_billing_webhook(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        return self._run(self._async.send_billing_webhook(payload))
