"""
Generated RPC Client with Environment Detection

Auto-generated WebSocket RPC client - DO NOT EDIT
Generated by: django_cfg_rpc codegen
"""

import asyncio
import json
import logging
import os
from typing import Any, Dict, Optional, Literal
from websockets import connect, WebSocketClientProtocol
{% if models %}

from .models import (
{% for model_name in models | unique | sort %}    {{ model_name }},
{% endfor %})
{% endif %}

logger = logging.getLogger(__name__)

# Type-safe environment literal
Environment = Literal["development", "production", "staging", "testing"]


class RPCClientConfig:
    """Environment-aware RPC client configuration."""

{% if config %}
    # 🔧 GENERATED CONFIGURATION
    ENDPOINTS = {
        "development": "{{ config.development.websocket_url }}",
        "production": "{{ config.production.websocket_url }}",
{% if config.staging %}        "staging": "{{ config.staging.websocket_url }}",
{% endif %}{% if config.testing %}        "testing": "{{ config.testing.websocket_url }}",
{% endif %}    }
{% else %}
    # No configuration provided - using default localhost
    ENDPOINTS = {
        "development": "ws://localhost:8001/ws",
        "production": "ws://localhost:8001/ws",
        "staging": "ws://localhost:8001/ws",
        "testing": "ws://localhost:8001/ws",
    }
{% endif %}

    @classmethod
    def detect_environment(cls) -> Environment:
        """
        Detect current environment from environment variables.

        Priority:
        1. DJANGO_ENV
        2. ENV
        3. DEBUG flag
        4. Default: development

        Returns:
            Environment name
        """
        # Check DJANGO_ENV first
        env = os.getenv("DJANGO_ENV", "").lower().strip()

        if env in ("dev", "devel", "develop", "local"):
            return "development"
        elif env == "prod":
            return "production"
        elif env == "stage":
            return "staging"
        elif env == "test":
            return "testing"
        elif env in ("development", "production", "staging", "testing"):
            return env  # type: ignore

        # Check ENV fallback
        env = os.getenv("ENV", "").lower().strip()
        if env in ("development", "production", "staging", "testing"):
            return env  # type: ignore

        # Check DEBUG flag
        debug = os.getenv("DEBUG", "").lower().strip()
        if debug in ("false", "0", "no", "off"):
            return "production"
        elif debug in ("true", "1", "yes", "on"):
            return "development"

        # Default
        return "development"

    @classmethod
    def get_url(cls, environment: Optional[Environment] = None) -> str:
        """
        Get WebSocket URL for environment.

        Args:
            environment: Environment name (auto-detect if None)

        Returns:
            WebSocket URL

        Raises:
            ValueError: If environment not configured
        """
        if environment is None:
            environment = cls.detect_environment()

        url = cls.ENDPOINTS.get(environment)
        if not url:
            available = list(cls.ENDPOINTS.keys())
            raise ValueError(
                f"No URL configured for environment: {environment}. "
                f"Available: {', '.join(available)}"
            )

        return url


class RPCClient:
    """
    WebSocket RPC Client with Environment Detection

    Provides type-safe RPC methods over WebSocket connection.
    Includes automatic environment detection and URL selection.
    """

    def __init__(self, url: Optional[str] = None):
        """
        Create RPC client.

        Args:
            url: WebSocket URL (auto-detect from ENV if None)

        Example:
            >>> # Auto-detect from DJANGO_ENV
            >>> client = RPCClient()
            >>>
            >>> # Explicit URL
            >>> client = RPCClient("ws://localhost:8001/ws")
        """
        if url is None:
            url = RPCClientConfig.get_url()
            logger.info(f"[RPCClient] Auto-detected URL: {url}")

        self.url = url
        self.ws: Optional[WebSocketClientProtocol] = None
        self.correlation_id = 0
        self.pending_requests: Dict[str, asyncio.Future] = {}
        self._receive_task: Optional[asyncio.Task] = None

    @classmethod
    def from_env(cls, environment: Optional[Environment] = None) -> "RPCClient":
        """
        Create client from environment.

        Args:
            environment: Environment name (auto-detect if None)

        Returns:
            Configured RPCClient instance

        Example:
            >>> # Auto-detect from DJANGO_ENV
            >>> client = RPCClient.from_env()
            >>>
            >>> # Explicit environment
            >>> client = RPCClient.from_env("production")
        """
        url = RPCClientConfig.get_url(environment)
        return cls(url)

    async def connect(self) -> None:
        """Connect to WebSocket server."""
        self.ws = await connect(self.url)
        logger.info("[RPCClient] Connected")

        # Wait for auth response with server version
        auth_message = await self.ws.recv()
        try:
            auth_data = json.loads(auth_message)
            if auth_data.get('type') == 'auth' and auth_data.get('status') == 'ok':
                server_version = auth_data.get('server_version', 'unknown')
                logger.info(f"[RPCClient] Server version: {server_version}")
            else:
                logger.warning(f"[RPCClient] Unexpected auth response: {auth_data}")
        except Exception as e:
            logger.warning(f"[RPCClient] Failed to parse auth message: {e}")

        # Start message receiver
        self._receive_task = asyncio.create_task(self._receive_messages())

    async def disconnect(self) -> None:
        """Disconnect from WebSocket server."""
        if self._receive_task:
            self._receive_task.cancel()
            try:
                await self._receive_task
            except asyncio.CancelledError:
                pass

        if self.ws:
            await self.ws.close()
            self.ws = None
            logger.info("[RPCClient] Disconnected")

    async def _receive_messages(self) -> None:
        """Receive and handle WebSocket messages."""
        try:
            async for message in self.ws:
                await self._handle_message(message)
        except asyncio.CancelledError:
            pass
        except Exception as e:
            logger.error(f"[RPCClient] Error receiving messages: {e}")

    async def _handle_message(self, data: str) -> None:
        """Handle incoming WebSocket message."""
        try:
            message = json.loads(data)

            if message.get('type') == 'response' and 'correlation_id' in message:
                correlation_id = message['correlation_id']
                if correlation_id in self.pending_requests:
                    future = self.pending_requests.pop(correlation_id)
                    future.set_result(message.get('result'))

            elif message.get('type') == 'error' and 'correlation_id' in message:
                correlation_id = message['correlation_id']
                if correlation_id in self.pending_requests:
                    future = self.pending_requests.pop(correlation_id)
                    error_msg = message.get('error', {}).get('message', 'RPC error')
                    future.set_exception(Exception(error_msg))

        except Exception as e:
            logger.error(f"[RPCClient] Failed to parse message: {e}")

    async def _call(self, method: str, params: Any) -> Any:
        """
        Call RPC method.

        Args:
            method: Method name
            params: Method parameters

        Returns:
            Method result

        Raises:
            Exception: If WebSocket not connected or RPC call fails
        """
        if not self.ws:
            raise Exception("WebSocket not connected")

        self.correlation_id += 1
        correlation_id = str(self.correlation_id)

        message = {
            'type': 'rpc',
            'method': method,
            'params': params.dict() if hasattr(params, 'dict') else params,
            'correlation_id': correlation_id,
        }

        # Create future for response
        future = asyncio.Future()
        self.pending_requests[correlation_id] = future

        # Send request
        await self.ws.send(json.dumps(message))

        # Wait for response with timeout
        try:
            result = await asyncio.wait_for(future, timeout=30.0)
            return result
        except asyncio.TimeoutError:
            self.pending_requests.pop(correlation_id, None)
            raise Exception("RPC timeout")

    # ========== Generated RPC Methods ==========

{% for method in methods %}
    async def {{ method.name_python }}(self, params: {{ method.param_type }}) -> {{ method.return_type }}:
        """{{ method.docstring | replace('\n', '\n        ') }}

        Args:
            params: {{ method.param_type }} parameters

        Returns:
            {{ method.return_type }}
        """
        result = await self._call('{{ method.name }}', params)
        return {{ method.return_type }}(**result)

{% endfor %}
