"""
IPC client for communicating with the BLE daemon.
"""

import asyncio
import os
import signal
import sys
from typing import Any, Iterator, Optional

from airctl.common.logging import get_logger
from airctl.common.platform import is_windows
from airctl.common.utils import generate_request_id
from airctl.ipc.protocol import (
    IPCError,
    IPCErrorCode,
    IPCProtocol,
)
from airctl.ipc.transport import create_transport, get_default_socket_path, get_pid_file_path

logger = get_logger(__name__)


class DaemonClient:
    """
    Client for communicating with the BLE daemon.
    
    Provides high-level methods for BLE operations.
    """

    def __init__(self, socket_path: Optional[str] = None, timeout: float = 30.0):
        """
        Initialize daemon client.

        Args:
            socket_path: Path to IPC socket (default: platform-specific)
            timeout: Default timeout for operations
        """
        self.socket_path = socket_path or get_default_socket_path()
        self.timeout = timeout
        self._transport = create_transport(self.socket_path)
        self._protocol = IPCProtocol()
        self._connected = False

    def is_running(self) -> bool:
        """Check if daemon is running."""
        pid_file = get_pid_file_path()
        if not os.path.exists(pid_file):
            return False

        try:
            with open(pid_file, "r") as f:
                pid = int(f.read().strip())

            if is_windows():
                import win32api
                import win32con
                import win32process

                try:
                    handle = win32api.OpenProcess(
                        win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_VM_READ,
                        False,
                        pid,
                    )
                    if not handle:
                        return False
                    
                    try:
                        exit_code = win32process.GetExitCodeProcess(handle)
                        if exit_code == 259:
                            return True
                        return False
                    finally:
                        win32api.CloseHandle(handle)
                except Exception:
                    return False
            else:
                try:
                    os.kill(pid, 0)
                    return True
                except OSError:
                    return False
        except Exception:
            return False

    def get_pid(self) -> Optional[int]:
        """Get daemon PID if running."""
        pid_file = get_pid_file_path()
        if not os.path.exists(pid_file):
            return None

        try:
            with open(pid_file, "r") as f:
                return int(f.read().strip())
        except Exception:
            return None

    async def _connect(self) -> None:
        """Connect to daemon."""
        if self._connected:
            return
        await self._transport.connect()
        self._connected = True

    async def _disconnect(self) -> None:
        """Disconnect from daemon."""
        if not self._connected:
            return
        await self._transport.disconnect()
        self._connected = False

    async def _send_request(
        self, method: str, params: Optional[dict[str, Any]] = None
    ) -> Any:
        """
        Send a request and wait for response.

        Args:
            method: Method name
            params: Method parameters

        Returns:
            Response result

        Raises:
            IPCError: On error response
        """
        try:
            await self._connect()

            request = self._protocol.create_request(method, params)
            data = self._protocol.serialize(request)
            await self._transport.send(data)

            response_data = await asyncio.wait_for(
                self._transport.receive(), timeout=self.timeout
            )
            response = self._protocol.deserialize(response_data)

            if response.error:
                raise IPCError(
                    code=response.error.code,
                    message=response.error.message,
                    data=response.error.data,
                )

            return response.result
        finally:
            await self._disconnect()

    def _call_sync(self, method: str, params: Optional[dict[str, Any]] = None) -> Any:
        """Synchronous wrapper for async request."""
        try:
            loop = asyncio.get_running_loop()
        except RuntimeError:
            loop = None

        if loop and loop.is_running():
            future = asyncio.ensure_future(self._send_request(method, params))
            return asyncio.run_coroutine_threadsafe(future, loop).result(self.timeout)
        else:
            return asyncio.run(self._send_request(method, params))

    def stop_daemon(self) -> bool:
        """Request daemon to stop."""
        try:
            result = self._call_sync("daemon.stop", {})
            return result.get("success", False)
        except Exception:
            return False

    def scan(
        self,
        timeout: float = 10.0,
        service_uuids: Optional[list[str]] = None,
    ) -> dict[str, Any]:
        """
        Scan for BLE devices.

        Args:
            timeout: Scan timeout in seconds
            service_uuids: Optional list of service UUIDs to filter

        Returns:
            Dict with 'devices' key containing list of device info
        """
        params = {"timeout": timeout}
        if service_uuids:
            params["service_uuids"] = service_uuids
        return self._call_sync("ble.scan", params)

    def connect(
        self,
        address: str,
        timeout: float = 30.0,
        alias: Optional[str] = None,
    ) -> dict[str, Any]:
        """
        Connect to a BLE device.

        Args:
            address: Device address or alias
            timeout: Connection timeout
            alias: Optional alias to assign

        Returns:
            Connection info
        """
        params = {"address": address, "timeout": timeout}
        if alias:
            params["alias"] = alias
        return self._call_sync("ble.connect", params)

    def disconnect(self, address: str) -> None:
        """
        Disconnect from a BLE device.

        Args:
            address: Device address or alias
        """
        self._call_sync("ble.disconnect", {"address": address})

    def list_devices(self) -> dict[str, Any]:
        """
        List connected devices.

        Returns:
            Dict with 'devices' key
        """
        return self._call_sync("ble.list_devices", {})

    def get_services(self, address: str) -> dict[str, Any]:
        """
        Get GATT services of a device.

        Args:
            address: Device address or alias

        Returns:
            Dict with 'services' key
        """
        return self._call_sync("ble.get_services", {"address": address})

    def get_characteristics(
        self, address: str, service_uuid: Optional[str] = None
    ) -> dict[str, Any]:
        """
        Get GATT characteristics of a device.

        Args:
            address: Device address or alias
            service_uuid: Optional service UUID filter

        Returns:
            Dict with 'characteristics' key
        """
        params = {"address": address}
        if service_uuid:
            params["service_uuid"] = service_uuid
        return self._call_sync("ble.get_characteristics", params)

    def read_characteristic(
        self, address: str, uuid: Optional[str] = None, handle: Optional[int] = None, format: str = "hex"
    ) -> dict[str, Any]:
        """
        Read a characteristic value.

        Args:
            address: Device address or alias
            uuid: Characteristic UUID
            handle: Characteristic handle (alternative to UUID)
            format: Output format (hex/base64/text)

        Returns:
            Dict with 'value' key
        """
        params = {"address": address, "format": format}
        if uuid:
            params["uuid"] = uuid
        if handle is not None:
            params["handle"] = handle
        return self._call_sync("ble.read", params)

    def write_characteristic(
        self,
        address: str,
        uuid: Optional[str] = None,
        handle: Optional[int] = None,
        data: str = "",
        response: bool = False,
    ) -> None:
        """
        Write to a characteristic.

        Args:
            address: Device address or alias
            uuid: Characteristic UUID
            handle: Characteristic handle (alternative to UUID)
            data: Data to write (hex:/text:/base64: prefix)
            response: Whether to request write response
        """
        params = {"address": address, "data": data, "response": response}
        if uuid:
            params["uuid"] = uuid
        if handle is not None:
            params["handle"] = handle
        self._call_sync("ble.write", params)

    def subscribe_notification(self, address: str, uuid: str) -> None:
        """
        Subscribe to characteristic notifications.

        Args:
            address: Device address or alias
            uuid: Characteristic UUID
        """
        self._call_sync("ble.notify.subscribe", {"address": address, "uuid": uuid})

    def unsubscribe_notification(self, address: str, uuid: str) -> None:
        """
        Unsubscribe from characteristic notifications.

        Args:
            address: Device address or alias
            uuid: Characteristic UUID
        """
        self._call_sync("ble.notify.unsubscribe", {"address": address, "uuid": uuid})

    def stream_events(
        self,
        address: Optional[str] = None,
        event_type: Optional[str] = None,
    ) -> Iterator[str]:
        """
        Stream events from daemon.

        Args:
            address: Optional device address filter
            event_type: Optional event type filter

        Yields:
            JSON event strings
        """
        params = {}
        if address:
            params["address"] = address
        if event_type:
            params["event_type"] = event_type

        async def _stream():
            await self._connect()
            
            request = self._protocol.create_request("ble.events", params)
            data = self._protocol.serialize(request)
            await self._transport.send(data)

            while True:
                try:
                    event_data = await self._transport.receive()
                    yield event_data.decode("utf-8").strip()
                except Exception:
                    break

        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        
        try:
            gen = _stream()
            while True:
                try:
                    event = loop.run_until_complete(gen.__anext__())
                    yield event
                except StopAsyncIteration:
                    break
        finally:
            loop.close()
