"""Synchronous client for the Danube SDK."""

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

from danube.async_client import AsyncDanubeClient
from danube.models import (
    AgentFundResult,
    AgentInfo,
    AgentRegistration,
    AgentSite,
    AgentSiteListItem,
    APIKeyResponse,
    APIKeyWithSecret,
    Contact,
    CredentialStoreResult,
    DeviceCode,
    DeviceToken,
    Identity,
    RatingAggregate,
    Service,
    ServiceToolsResult,
    Skill,
    SkillContent,
    SpendingLimits,
    Tool,
    ToolRating,
    ToolResult,
    WalletBalance,
    WalletTransaction,
    WebhookCreateResponse,
    WebhookDeliveryResponse,
    WebhookResponse,
    Workflow,
    WorkflowExecution,
)


class SyncAgentsResource:
    """Synchronous operations on autonomous agents."""

    def __init__(self, client: "DanubeClient"):
        self._client = client

    def register(
        self,
        name: str,
        operator_email: str,
    ) -> AgentRegistration:
        """Register a new autonomous agent.

        Creates a user profile, API key, and USDC wallet in one call.
        The API key is returned only once.

        Args:
            name: Display name for the agent.
            operator_email: Contact email for the operator.

        Returns:
            AgentRegistration with agent_id, api_key (one-time), wallet_id, etc.
        """
        return self._client._run(
            self._client._async_client.agents.register(
                name=name, operator_email=operator_email
            )
        )

    def get_info(self) -> AgentInfo:
        """Get the current agent's profile and wallet balance.

        Returns:
            AgentInfo with wallet balance, deposit address, etc.
        """
        return self._client._run(self._client._async_client.agents.get_info())

    def fund_wallet(
        self,
        method: str,
        amount_cents: Optional[int] = None,
    ) -> AgentFundResult:
        """Fund the agent's wallet.

        Args:
            method: "card_checkout" or "crypto".
            amount_cents: Amount in cents (required for card_checkout, min 100).

        Returns:
            AgentFundResult with checkout_url or deposit_address.
        """
        return self._client._run(
            self._client._async_client.agents.fund_wallet(
                method=method, amount_cents=amount_cents
            )
        )


class SyncServicesResource:
    """Synchronous operations on services."""

    def __init__(self, client: "DanubeClient"):
        self._client = client

    def list(
        self,
        query: str = "",
        limit: int = 10,
    ) -> List[Service]:
        """List or search available services.

        Args:
            query: Optional search query to filter by name/description.
            limit: Maximum number of results (default 10).

        Returns:
            List of matching Service objects.
        """
        return self._client._run(
            self._client._async_client.services.list(query=query, limit=limit)
        )

    def get(self, service_id: str) -> Service:
        """Get a service by ID.

        Args:
            service_id: The service UUID.

        Returns:
            Service details.

        Raises:
            NotFoundError: If service doesn't exist.
        """
        return self._client._run(self._client._async_client.services.get(service_id))

    def get_tools(
        self,
        service_id: str,
        limit: int = 50,
    ) -> ServiceToolsResult:
        """Get all tools for a service.

        Args:
            service_id: The service UUID.
            limit: Maximum tools to return (default 50).

        Returns:
            ServiceToolsResult with tools and configuration status.
        """
        return self._client._run(
            self._client._async_client.services.get_tools(
                service_id=service_id, limit=limit
            )
        )


class SyncToolsResource:
    """Synchronous operations on tools."""

    def __init__(self, client: "DanubeClient"):
        self._client = client

    def search(
        self,
        query: str,
        service_id: Optional[str] = None,
        limit: int = 10,
    ) -> List[Tool]:
        """Search for tools by name or description.

        Uses semantic search to find the most relevant tools.

        Args:
            query: Search query (semantic search).
            service_id: Optional filter by service.
            limit: Maximum results (default 10).

        Returns:
            List of matching Tool objects.
        """
        return self._client._run(
            self._client._async_client.tools.search(
                query=query, service_id=service_id, limit=limit
            )
        )

    def get(self, tool_id: str) -> Tool:
        """Get a tool by ID.

        Args:
            tool_id: The tool UUID.

        Returns:
            Tool details.

        Raises:
            NotFoundError: If tool doesn't exist.
        """
        return self._client._run(self._client._async_client.tools.get(tool_id))

    def execute(
        self,
        tool_id: Optional[str] = None,
        tool_name: Optional[str] = None,
        parameters: Optional[Dict[str, Any]] = None,
    ) -> ToolResult:
        """Execute a tool by ID or name.

        Args:
            tool_id: Tool UUID (preferred, faster).
            tool_name: Tool name (will search for match).
            parameters: Parameters to pass to the tool.

        Returns:
            ToolResult with execution result or error.

        Raises:
            ValidationError: If neither tool_id nor tool_name provided.
            NotFoundError: If tool cannot be found.
            ExecutionError: If tool execution fails.
        """
        return self._client._run(
            self._client._async_client.tools.execute(
                tool_id=tool_id, tool_name=tool_name, parameters=parameters
            )
        )

    def batch_execute(
        self,
        calls: List[Dict[str, Any]],
    ) -> List[Dict[str, Any]]:
        """Execute multiple tools in a single request.

        Args:
            calls: List of dicts, each with ``tool_id`` and optional ``tool_input``.
                   Maximum 10 calls per batch.

        Returns:
            List of result dicts, each with ``tool_id``, ``success``, ``result``, ``error``.
        """
        return self._client._run(
            self._client._async_client.tools.batch_execute(calls=calls)
        )


class SyncSkillsResource:
    """Synchronous operations on skills."""

    def __init__(self, client: "DanubeClient"):
        self._client = client

    def search(
        self,
        query: str,
        limit: int = 10,
    ) -> List[Skill]:
        """Search for skills using semantic search.

        Skills are reusable instructions that teach AI agents
        how to perform specific tasks.

        Args:
            query: Search query.
            limit: Maximum results (default 10).

        Returns:
            List of matching Skill objects (summaries).
        """
        return self._client._run(
            self._client._async_client.skills.search(query=query, limit=limit)
        )

    def get(
        self,
        skill_id: Optional[str] = None,
        skill_name: Optional[str] = None,
    ) -> SkillContent:
        """Get a skill with full content.

        Args:
            skill_id: Skill UUID (preferred).
            skill_name: Skill name.

        Returns:
            Full SkillContent including SKILL.md, scripts, etc.

        Raises:
            ValidationError: If neither skill_id nor skill_name provided.
            NotFoundError: If skill cannot be found.
        """
        return self._client._run(
            self._client._async_client.skills.get(
                skill_id=skill_id, skill_name=skill_name
            )
        )


class SyncIdentityResource:
    """Synchronous operations on user identity."""

    def __init__(self, client: "DanubeClient"):
        self._client = client

    def get(self) -> Identity:
        """Get the current user's identity.

        Returns:
            User identity with profile, contacts, preferences.
        """
        return self._client._run(self._client._async_client.identity.get())



class SyncWorkflowsResource:
    """Synchronous operations on workflows."""

    def __init__(self, client: "DanubeClient"):
        self._client = client

    def list(
        self,
        query: str = "",
        limit: int = 10,
    ) -> List[Workflow]:
        """List public workflows.

        Args:
            query: Optional search query to filter by name/description.
            limit: Maximum number of results (default 10).

        Returns:
            List of Workflow objects.
        """
        return self._client._run(
            self._client._async_client.workflows.list(query=query, limit=limit)
        )

    def get(self, workflow_id: str) -> Workflow:
        """Get a workflow by ID.

        Args:
            workflow_id: The workflow UUID.

        Returns:
            Workflow details.
        """
        return self._client._run(
            self._client._async_client.workflows.get(workflow_id)
        )

    def execute(
        self,
        workflow_id: str,
        inputs: Optional[Dict[str, Any]] = None,
    ) -> WorkflowExecution:
        """Execute a workflow.

        Args:
            workflow_id: The workflow UUID.
            inputs: Optional dict of input values.

        Returns:
            WorkflowExecution with step results.
        """
        return self._client._run(
            self._client._async_client.workflows.execute(
                workflow_id=workflow_id, inputs=inputs
            )
        )

    def get_execution(self, execution_id: str) -> WorkflowExecution:
        """Get a workflow execution result.

        Args:
            execution_id: The execution UUID.

        Returns:
            WorkflowExecution with full results.
        """
        return self._client._run(
            self._client._async_client.workflows.get_execution(execution_id)
        )

    def create(
        self,
        name: str,
        steps: List[Dict[str, Any]],
        description: str = "",
        visibility: str = "private",
        tags: Optional[List[str]] = None,
    ) -> Workflow:
        """Create a new workflow.

        Args:
            name: Workflow name.
            steps: List of step dicts with ``step_number``, ``tool_id``, optional ``tool_name``,
                   ``description``, ``input_mapping``.
            description: Optional workflow description.
            visibility: ``"private"`` or ``"public"`` (default ``"private"``).
            tags: Optional list of tags.

        Returns:
            The created Workflow.
        """
        return self._client._run(
            self._client._async_client.workflows.create(
                name=name,
                steps=steps,
                description=description,
                visibility=visibility,
                tags=tags,
            )
        )

    def update(
        self,
        workflow_id: str,
        **updates: Any,
    ) -> Workflow:
        """Update a workflow (owner only).

        Args:
            workflow_id: The workflow UUID.
            **updates: Fields to update (name, description, steps, visibility, tags).

        Returns:
            The updated Workflow.
        """
        return self._client._run(
            self._client._async_client.workflows.update(
                workflow_id=workflow_id, **updates
            )
        )

    def delete(self, workflow_id: str) -> bool:
        """Delete a workflow (owner only).

        Args:
            workflow_id: The workflow UUID.

        Returns:
            True on success.
        """
        return self._client._run(
            self._client._async_client.workflows.delete(workflow_id)
        )


class SyncSitesResource:
    """Synchronous operations on Agent Web sites.

    Agent Web crawls websites across multiple pages and extracts rich structured
    data (identity, products, team, blog, jobs, contact, about, services, docs,
    pricing, faq, legal, navigation) for AI agent consumption.
    """

    def __init__(self, client: "DanubeClient"):
        self._client = client

    def search(
        self,
        query: str = "",
        category: Optional[str] = None,
        limit: int = 10,
    ) -> List[AgentSiteListItem]:
        """Search the Agent Web directory.

        Args:
            query: Search query to filter sites.
            category: Optional category filter.
            limit: Maximum number of results (default 10).

        Returns:
            List of AgentSiteListItem objects.
        """
        return self._client._run(
            self._client._async_client.sites.search(
                query=query, category=category, limit=limit
            )
        )

    def get(self, site_id: str) -> AgentSite:
        """Get a site by ID.

        Args:
            site_id: The site UUID.

        Returns:
            Full AgentSite with components.
        """
        return self._client._run(
            self._client._async_client.sites.get(site_id)
        )

    def get_by_domain(self, domain: str) -> AgentSite:
        """Get a site by domain.

        Args:
            domain: The domain (e.g. 'stripe.com').

        Returns:
            Full AgentSite with components.
        """
        return self._client._run(
            self._client._async_client.sites.get_by_domain(domain)
        )


class SyncWalletResource:
    """Synchronous operations on wallet."""

    def __init__(self, client: "DanubeClient"):
        self._client = client

    def get_balance(self) -> WalletBalance:
        """Get the current user's wallet balance."""
        return self._client._run(self._client._async_client.wallet.get_balance())

    def get_transactions(
        self,
        limit: int = 50,
        offset: int = 0,
    ) -> List[WalletTransaction]:
        """Get wallet transaction history."""
        return self._client._run(
            self._client._async_client.wallet.get_transactions(
                limit=limit, offset=offset
            )
        )

    def get_spending_limits(self) -> SpendingLimits:
        """Get the current user's USDC spending limits."""
        return self._client._run(
            self._client._async_client.wallet.get_spending_limits()
        )

    def update_spending_limits(
        self,
        max_per_call_atomic: Optional[int] = None,
        daily_limit_atomic: Optional[int] = None,
    ) -> SpendingLimits:
        """Update the current user's USDC spending limits."""
        return self._client._run(
            self._client._async_client.wallet.update_spending_limits(
                max_per_call_atomic=max_per_call_atomic,
                daily_limit_atomic=daily_limit_atomic,
            )
        )


class SyncRatingsResource:
    """Synchronous operations on ratings."""

    def __init__(self, client: "DanubeClient"):
        self._client = client

    def submit(
        self,
        tool_id: str,
        rating: int,
        comment: Optional[str] = None,
    ) -> ToolRating:
        """Submit or update a rating for a tool."""
        return self._client._run(
            self._client._async_client.ratings.submit(
                tool_id=tool_id, rating=rating, comment=comment
            )
        )

    def get_mine(self, tool_id: str) -> Optional[ToolRating]:
        """Get the current user's rating for a tool."""
        return self._client._run(
            self._client._async_client.ratings.get_mine(tool_id)
        )

    def get_tool_ratings(self, tool_id: str) -> RatingAggregate:
        """Get aggregated ratings for a tool (public)."""
        return self._client._run(
            self._client._async_client.ratings.get_tool_ratings(tool_id)
        )


class SyncCredentialsResource:
    """Synchronous operations on credentials."""

    def __init__(self, client: "DanubeClient"):
        self._client = client

    def store(
        self,
        service_id: str,
        credential_type: str,
        credential_value: str,
    ) -> CredentialStoreResult:
        """Store a credential for a service."""
        return self._client._run(
            self._client._async_client.credentials.store(
                service_id=service_id,
                credential_type=credential_type,
                credential_value=credential_value,
            )
        )


class SyncDeviceAuthResource:
    """Synchronous operations for device code auth flow."""

    def __init__(self, client: "DanubeClient"):
        self._client = client

    def request_code(
        self,
        client_name: str = "Python SDK",
    ) -> DeviceCode:
        """Start a device authorization flow."""
        return self._client._run(
            self._client._async_client.device_auth.request_code(
                client_name=client_name
            )
        )

    def poll_token(self, device_code: str) -> DeviceToken:
        """Poll for a device token after user authorization."""
        return self._client._run(
            self._client._async_client.device_auth.poll_token(device_code)
        )


class SyncAPIKeysResource:
    """Synchronous operations on API keys."""

    def __init__(self, client: "DanubeClient"):
        self._client = client

    def list(self) -> List[APIKeyResponse]:
        """List all active API keys for the current user."""
        return self._client._run(self._client._async_client.api_keys.list())

    def create(
        self,
        name: str,
        permissions: Optional[Dict[str, Any]] = None,
    ) -> APIKeyWithSecret:
        """Create a new API key. The plaintext key is returned only once."""
        return self._client._run(
            self._client._async_client.api_keys.create(
                name=name, permissions=permissions
            )
        )

    def rotate(self, key_id: str) -> APIKeyWithSecret:
        """Rotate an API key, generating new key material."""
        return self._client._run(
            self._client._async_client.api_keys.rotate(key_id)
        )

    def revoke(self, key_id: str) -> bool:
        """Revoke (soft-delete) an API key."""
        return self._client._run(
            self._client._async_client.api_keys.revoke(key_id)
        )


class SyncWebhooksResource:
    """Synchronous operations on webhooks."""

    def __init__(self, client: "DanubeClient"):
        self._client = client

    def list(self) -> List[WebhookResponse]:
        """List all webhooks for the current user."""
        return self._client._run(self._client._async_client.webhooks.list())

    def create(
        self,
        url: str,
        events: List[str],
        description: Optional[str] = None,
    ) -> WebhookCreateResponse:
        """Create a new webhook. The signing secret is returned only once."""
        return self._client._run(
            self._client._async_client.webhooks.create(
                url=url, events=events, description=description
            )
        )

    def update(
        self,
        webhook_id: str,
        url: Optional[str] = None,
        events: Optional[List[str]] = None,
        is_active: Optional[bool] = None,
        description: Optional[str] = None,
    ) -> WebhookResponse:
        """Update a webhook's URL, events, or active status."""
        return self._client._run(
            self._client._async_client.webhooks.update(
                webhook_id=webhook_id,
                url=url,
                events=events,
                is_active=is_active,
                description=description,
            )
        )

    def delete(self, webhook_id: str) -> bool:
        """Delete a webhook."""
        return self._client._run(
            self._client._async_client.webhooks.delete(webhook_id)
        )

    def get_deliveries(
        self,
        webhook_id: str,
        limit: int = 20,
    ) -> List[WebhookDeliveryResponse]:
        """Get recent deliveries for a webhook."""
        return self._client._run(
            self._client._async_client.webhooks.get_deliveries(
                webhook_id=webhook_id, limit=limit
            )
        )


class DanubeClient:
    """Synchronous client for the Danube AI API.

    This is a convenience wrapper around AsyncDanubeClient for use
    in synchronous code. For better performance in async applications,
    use AsyncDanubeClient directly.

    Example:
        with DanubeClient(api_key="dk_...") as client:
            services = client.services.list()
            result = client.tools.execute("tool-id", parameters={"key": "value"})
    """

    def __init__(
        self,
        api_key: Optional[str] = None,
        base_url: Optional[str] = None,
        timeout: float = 30.0,
        max_retries: int = 3,
    ):
        """Initialize the Danube synchronous client.

        Args:
            api_key: Danube API key. Falls back to DANUBE_API_KEY env var.
            base_url: API base URL. Defaults to https://api.danubeai.com
            timeout: Request timeout in seconds.
            max_retries: Number of retries for failed requests.
        """
        self._async_client = AsyncDanubeClient(
            api_key=api_key,
            base_url=base_url,
            timeout=timeout,
            max_retries=max_retries,
        )
        self._loop: Optional[asyncio.AbstractEventLoop] = None

        # Initialize resource handlers (lazy)
        self._agents: Optional[SyncAgentsResource] = None
        self._services: Optional[SyncServicesResource] = None
        self._tools: Optional[SyncToolsResource] = None
        self._skills: Optional[SyncSkillsResource] = None
        self._identity: Optional[SyncIdentityResource] = None
        self._workflows: Optional[SyncWorkflowsResource] = None
        self._sites: Optional[SyncSitesResource] = None
        self._wallet: Optional[SyncWalletResource] = None
        self._ratings: Optional[SyncRatingsResource] = None
        self._credentials: Optional[SyncCredentialsResource] = None
        self._device_auth: Optional[SyncDeviceAuthResource] = None
        self._api_keys: Optional[SyncAPIKeysResource] = None
        self._webhooks: Optional[SyncWebhooksResource] = None

    def _get_loop(self) -> asyncio.AbstractEventLoop:
        """Get or create an event loop.

        Returns:
            An asyncio event loop.
        """
        if self._loop is None or self._loop.is_closed():
            try:
                self._loop = asyncio.get_event_loop()
                if self._loop.is_closed():
                    raise RuntimeError("Event loop is closed")
            except RuntimeError:
                self._loop = asyncio.new_event_loop()
                asyncio.set_event_loop(self._loop)
        return self._loop

    def _run(self, coro: Any) -> Any:
        """Run a coroutine synchronously.

        Args:
            coro: The coroutine to run.

        Returns:
            The result of the coroutine.
        """
        return self._get_loop().run_until_complete(coro)

    @property
    def agents(self) -> SyncAgentsResource:
        """Access agents resource."""
        if self._agents is None:
            self._agents = SyncAgentsResource(self)
        return self._agents

    @property
    def services(self) -> SyncServicesResource:
        """Access services resource."""
        if self._services is None:
            self._services = SyncServicesResource(self)
        return self._services

    @property
    def tools(self) -> SyncToolsResource:
        """Access tools resource."""
        if self._tools is None:
            self._tools = SyncToolsResource(self)
        return self._tools

    @property
    def skills(self) -> SyncSkillsResource:
        """Access skills resource."""
        if self._skills is None:
            self._skills = SyncSkillsResource(self)
        return self._skills

    @property
    def identity(self) -> SyncIdentityResource:
        """Access identity resource."""
        if self._identity is None:
            self._identity = SyncIdentityResource(self)
        return self._identity

    @property
    def workflows(self) -> SyncWorkflowsResource:
        """Access workflows resource."""
        if self._workflows is None:
            self._workflows = SyncWorkflowsResource(self)
        return self._workflows

    @property
    def sites(self) -> SyncSitesResource:
        """Access agent-friendly sites resource."""
        if self._sites is None:
            self._sites = SyncSitesResource(self)
        return self._sites

    @property
    def wallet(self) -> SyncWalletResource:
        """Access wallet resource."""
        if self._wallet is None:
            self._wallet = SyncWalletResource(self)
        return self._wallet

    @property
    def ratings(self) -> SyncRatingsResource:
        """Access ratings resource."""
        if self._ratings is None:
            self._ratings = SyncRatingsResource(self)
        return self._ratings

    @property
    def credentials(self) -> SyncCredentialsResource:
        """Access credentials resource."""
        if self._credentials is None:
            self._credentials = SyncCredentialsResource(self)
        return self._credentials

    @property
    def device_auth(self) -> SyncDeviceAuthResource:
        """Access device auth resource."""
        if self._device_auth is None:
            self._device_auth = SyncDeviceAuthResource(self)
        return self._device_auth

    @property
    def api_keys(self) -> SyncAPIKeysResource:
        """Access API keys resource."""
        if self._api_keys is None:
            self._api_keys = SyncAPIKeysResource(self)
        return self._api_keys

    @property
    def webhooks(self) -> SyncWebhooksResource:
        """Access webhooks resource."""
        if self._webhooks is None:
            self._webhooks = SyncWebhooksResource(self)
        return self._webhooks

    def verify_connection(self) -> bool:
        """Verify the connection to the Danube API.

        Returns:
            True if connection is successful and API key is valid.
        """
        return self._run(self._async_client.verify_connection())

    def close(self) -> None:
        """Close the client and release resources."""
        self._run(self._async_client.close())

    def __enter__(self) -> "DanubeClient":
        """Context manager entry."""
        return self

    def __exit__(self, *args: Any) -> None:
        """Context manager exit."""
        self.close()
