"""
BLE Daemon server implementation.
"""

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

import orjson

try:
    import pywintypes
except ImportError:
    pywintypes = None

from airctl.common.logging import get_logger, setup_logging
from airctl.common.platform import Platform, get_platform, is_windows
from airctl.common.utils import get_timestamp
from airctl.config.manager import ConfigManager
from airctl.daemon.device_manager import DeviceManager
from airctl.daemon.event_manager import EventManager
from airctl.daemon.scanner import ScannerManager
from airctl.ipc.handlers import RequestHandlers
from airctl.ipc.protocol import IPCProtocol, JSONRPCRequest, JSONRPCResponse
from airctl.ipc.transport import get_default_socket_path, get_pid_file_path
from airctl.models.events import EventType
from airctl.models.protocol import JSONRPCError, JSONRPCException

logger = get_logger(__name__)

_stop_requested = False


def request_daemon_stop() -> None:
    """Request the daemon to stop."""
    global _stop_requested
    _stop_requested = True
    
    if is_windows():
        try:
            import win32file
            pipe_name = get_default_socket_path()
            pipe = win32file.CreateFile(
                pipe_name,
                win32file.GENERIC_READ | win32file.GENERIC_WRITE,
                0,
                None,
                win32file.OPEN_EXISTING,
                0,
                None,
            )
            win32file.CloseHandle(pipe)
        except Exception:
            pass


class BLEDaemon:
    """
    BLE Daemon server.
    
    Manages BLE connections and handles IPC requests.
    """

    def __init__(
        self,
        socket_path: Optional[str] = None,
        log_level: str = "INFO",
    ) -> None:
        """
        Initialize BLE daemon.

        Args:
            socket_path: Path for IPC socket
            log_level: Logging level
        """
        self.socket_path = socket_path or get_default_socket_path()
        self.log_level = log_level
        self.protocol = IPCProtocol()
        self.handlers = RequestHandlers()
        self.device_manager = DeviceManager()
        self.scanner_manager = ScannerManager()
        self.event_manager = EventManager()
        self.config_manager = ConfigManager()
        self._server: Optional[Any] = None
        self._clients: set = set()
        self._running = False

        self._setup_handlers()
        self._setup_signal_handlers()

    def _setup_handlers(self) -> None:
        """Setup IPC request handlers."""
        self.handlers.register("ble.scan", self._handle_scan)
        self.handlers.register("ble.connect", self._handle_connect)
        self.handlers.register("ble.disconnect", self._handle_disconnect)
        self.handlers.register("ble.list_devices", self._handle_list_devices)
        self.handlers.register("ble.get_services", self._handle_get_services)
        self.handlers.register("ble.get_characteristics", self._handle_get_characteristics)
        self.handlers.register("ble.read", self._handle_read)
        self.handlers.register("ble.write", self._handle_write)
        self.handlers.register("ble.notify.subscribe", self._handle_notify_subscribe)
        self.handlers.register("ble.notify.unsubscribe", self._handle_notify_unsubscribe)
        self.handlers.register("ble.events", self._handle_events)

    def _setup_signal_handlers(self) -> None:
        """Setup signal handlers for graceful shutdown."""
        signal.signal(signal.SIGINT, self._signal_handler)
        signal.signal(signal.SIGTERM, self._signal_handler)
        if not is_windows():
            signal.signal(signal.SIGUSR1, self._signal_handler)

    def _signal_handler(self, signum: int, frame: Any) -> None:
        """Handle shutdown signals."""
        logger.info(f"Received signal {signum}, shutting down...")
        request_daemon_stop()

    def _write_pid_file(self) -> None:
        """Write PID file."""
        pid_file = get_pid_file_path()
        with open(pid_file, "w") as f:
            f.write(str(os.getpid()))
        logger.debug(f"Wrote PID file: {pid_file}")

    def _remove_pid_file(self) -> None:
        """Remove PID file."""
        pid_file = get_pid_file_path()
        if os.path.exists(pid_file):
            os.remove(pid_file)
            logger.debug(f"Removed PID file: {pid_file}")

    async def _handle_client(
        self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
    ) -> None:
        """Handle a client connection."""
        addr = writer.get_extra_info("peername") or "unknown"
        logger.debug(f"Client connected: {addr}")
        self._clients.add(writer)

        try:
            while self._running:
                try:
                    data = await asyncio.wait_for(reader.readline(), timeout=1.0)
                except asyncio.TimeoutError:
                    continue

                if not data:
                    break

                try:
                    await self._process_message(data, writer)
                except Exception as e:
                    logger.exception(f"Error processing message: {e}")
                    error_response = self.protocol.create_error_response(
                        request_id=None,
                        code=-32603,
                        message=str(e),
                    )
                    writer.write(self.protocol.serialize(error_response))
                    await writer.drain()

        except Exception as e:
            logger.exception(f"Client handler error: {e}")
        finally:
            logger.debug(f"Client disconnected: {addr}")
            self._clients.discard(writer)
            writer.close()
            try:
                await writer.wait_closed()
            except Exception:
                pass

    async def _process_message(self, data: bytes, writer: asyncio.StreamWriter) -> None:
        """Process an incoming message."""
        try:
            message = self.protocol.deserialize(data)
        except Exception as e:
            error_response = self.protocol.create_error_response(
                request_id=None,
                code=-32700,
                message=f"Parse error: {e}",
            )
            writer.write(self.protocol.serialize(error_response))
            await writer.drain()
            return

        if isinstance(message, JSONRPCRequest):
            await self._handle_request(message, writer)

    async def _handle_request(
        self, request: JSONRPCRequest, writer: asyncio.StreamWriter
    ) -> None:
        """Handle a JSON-RPC request."""
        logger.debug(f"Handling request: {request.method}")

        try:
            result = await self.handlers.handle(request.method, request.params)
            response = self.protocol.create_response(request.id, result=result)
        except JSONRPCException as e:
            response = self.protocol.create_response(
                request.id,
                error=JSONRPCError(code=e.code, message=e.message, data=e.data),
            )
        except Exception as e:
            logger.exception(f"Handler error: {e}")
            response = self.protocol.create_error_response(
                request_id=request.id,
                code=-32603,
                message=str(e),
            )

        if request.id is not None:
            writer.write(self.protocol.serialize(response))
            await writer.drain()

    async def _handle_scan(self, params: dict[str, Any]) -> dict[str, Any]:
        """Handle scan request."""
        timeout = params.get("timeout", 10.0)
        service_uuids = params.get("service_uuids")

        devices = await self.scanner_manager.scan(
            timeout=timeout,
            service_uuids=service_uuids,
        )

        return {
            "devices": [
                {
                    "address": d.address,
                    "name": d.name,
                    "rssi": d.rssi,
                    "service_uuids": d.service_uuids,
                }
                for d in devices
            ]
        }

    async def _handle_connect(self, params: dict[str, Any]) -> dict[str, Any]:
        """Handle connect request."""
        address = params.get("address")
        timeout = params.get("timeout", 30.0)
        alias = params.get("alias")

        if not address:
            raise JSONRPCException(code=-32602, message="Missing address parameter")

        device = await self.device_manager.connect(
            address=address,
            timeout=timeout,
            alias=alias,
        )

        self.event_manager.emit({
            "type": EventType.DEVICE_CONNECTED.value,
            "timestamp": get_timestamp(),
            "device_address": device.address,
            "device_name": device.name,
        })

        return {
            "address": device.address,
            "name": device.name,
            "alias": device.alias,
            "mtu_size": device.mtu_size,
        }

    async def _handle_disconnect(self, params: dict[str, Any]) -> dict[str, Any]:
        """Handle disconnect request."""
        address = params.get("address")
        if not address:
            raise JSONRPCException(code=-32602, message="Missing address parameter")

        try:
            await self.device_manager.disconnect(address)
        except ValueError as e:
            raise JSONRPCException(code=-32602, message=str(e))

        self.event_manager.emit({
            "type": EventType.DEVICE_DISCONNECTED.value,
            "timestamp": get_timestamp(),
            "device_address": address,
        })

        return {"success": True, "message": f"Disconnected from {address}"}

    async def _handle_list_devices(self, params: dict[str, Any]) -> dict[str, Any]:
        """Handle list devices request."""
        from datetime import datetime
        
        devices = self.device_manager.list_devices()
        return {
            "devices": [
                {
                    "address": d.address,
                    "name": d.name,
                    "alias": d.alias,
                    "mtu_size": d.mtu_size,
                    "connected_at": datetime.fromtimestamp(d.connected_at).strftime("%Y-%m-%d %H:%M:%S"),
                }
                for d in devices
            ]
        }

    async def _handle_get_services(self, params: dict[str, Any]) -> dict[str, Any]:
        """Handle get services request."""
        address = params.get("address")
        if not address:
            raise JSONRPCException(code=-32602, message="Missing address parameter")

        services = await self.device_manager.get_services(address)
        return {
            "services": [
                {
                    "uuid": s.uuid,
                    "handle": s.handle,
                    "description": s.description,
                }
                for s in services
            ]
        }

    async def _handle_get_characteristics(self, params: dict[str, Any]) -> dict[str, Any]:
        """Handle get characteristics request."""
        address = params.get("address")
        service_uuid = params.get("service_uuid")

        if not address:
            raise JSONRPCException(code=-32602, message="Missing address parameter")

        characteristics = await self.device_manager.get_characteristics(
            address=address,
            service_uuid=service_uuid,
        )

        return {
            "characteristics": [
                {
                    "uuid": c.uuid,
                    "handle": c.handle,
                    "service_uuid": c.service_uuid,
                    "service_description": c.service_description,
                    "properties": [p.value for p in c.properties],
                    "description": c.description,
                }
                for c in characteristics
            ]
        }

    async def _handle_read(self, params: dict[str, Any]) -> dict[str, Any]:
        """Handle read characteristic request."""
        address = params.get("address")
        uuid = params.get("uuid")
        handle = params.get("handle")
        format_type = params.get("format", "hex")

        if not address:
            raise JSONRPCException(code=-32602, message="Missing address parameter")
        if not uuid and handle is None:
            raise JSONRPCException(code=-32602, message="Either uuid or handle must be specified")

        try:
            data = await self.device_manager.read_characteristic(address, uuid=uuid, handle=handle)
        except Exception as e:
            logger.exception(f"Failed to read characteristic: {e}")
            raise JSONRPCException(code=-32603, message=f"Failed to read characteristic: {e}")

        from airctl.common.utils import format_data_output

        return {
            "value": format_data_output(data, format_type),
            "format": format_type,
        }

    async def _handle_write(self, params: dict[str, Any]) -> dict[str, Any]:
        """Handle write characteristic request."""
        address = params.get("address")
        uuid = params.get("uuid")
        handle = params.get("handle")
        data_str = params.get("data")
        response = params.get("response", False)

        if not address:
            raise JSONRPCException(code=-32602, message="Missing address parameter")
        if not uuid and handle is None:
            raise JSONRPCException(code=-32602, message="Either uuid or handle must be specified")
        if not data_str:
            raise JSONRPCException(code=-32602, message="Missing data parameter")

        from airctl.common.utils import parse_data_input

        data = parse_data_input(data_str)
        await self.device_manager.write_characteristic(
            address=address,
            uuid=uuid,
            handle=handle,
            data=data,
            response=response,
        )

        return {"success": True}

    async def _handle_notify_subscribe(self, params: dict[str, Any]) -> dict[str, Any]:
        """Handle notification subscribe request."""
        address = params.get("address")
        uuid = params.get("uuid")

        if not address or not uuid:
            raise JSONRPCException(code=-32602, message="Missing address or uuid parameter")

        async def notification_callback(char_uuid: str, data: bytes) -> None:
            from airctl.common.utils import bytes_to_base64

            self.event_manager.emit({
                "type": EventType.NOTIFICATION.value,
                "timestamp": get_timestamp(),
                "device_address": address,
                "characteristic_uuid": char_uuid,
                "data": bytes_to_base64(data),
            })

        await self.device_manager.subscribe_notification(
            address=address,
            uuid=uuid,
            callback=notification_callback,
        )

        return {"success": True}

    async def _handle_notify_unsubscribe(self, params: dict[str, Any]) -> dict[str, Any]:
        """Handle notification unsubscribe request."""
        address = params.get("address")
        uuid = params.get("uuid")

        if not address or not uuid:
            raise JSONRPCException(code=-32602, message="Missing address or uuid parameter")

        await self.device_manager.unsubscribe_notification(address=address, uuid=uuid)
        return {"success": True}

    async def _handle_events(self, params: dict[str, Any]) -> None:
        """Handle events stream request - this is handled differently."""
        pass

    async def _broadcast_event(self, event: dict[str, Any]) -> None:
        """Broadcast an event to all connected clients."""
        message = orjson.dumps({"jsonrpc": "2.0", "method": "event", "params": event}) + b"\n"
        
        for writer in list(self._clients):
            try:
                writer.write(message)
                await writer.drain()
            except Exception:
                self._clients.discard(writer)

    async def run(self) -> None:
        """Run the daemon."""
        import tempfile
        log_file = os.path.join(tempfile.gettempdir(), "airctl-daemon.log")
        setup_logging(self.log_level, log_file=log_file)
        logger.info(f"Starting BLE daemon on {self.socket_path}")

        self._running = True

        platform = get_platform()

        try:
            if platform == Platform.WINDOWS:
                await self._run_windows()
            else:
                await self._run_unix()

        finally:
            self._running = False
            self._remove_pid_file()
            logger.info("BLE daemon stopped")

    async def _run_unix(self) -> None:
        """Run daemon on Unix (Linux/macOS)."""
        if os.path.exists(self.socket_path):
            os.remove(self.socket_path)

        self._server = await asyncio.start_unix_server(
            self._handle_client,
            path=self.socket_path,
        )

        os.chmod(self.socket_path, 0o666)
        
        self._write_pid_file()

        async with self._server:
            logger.info("BLE daemon running (Unix socket)")
            while self._running and not _stop_requested:
                await asyncio.sleep(0.5)

    async def _run_windows(self) -> None:
        """Run daemon on Windows (named pipe)."""
        import win32pipe
        import win32file
        import win32security

        self._write_pid_file()
        logger.info("BLE daemon running (named pipe)")

        sa = win32security.SECURITY_ATTRIBUTES()
        sa.SetSecurityDescriptorDacl(1, None, 0)

        while self._running and not _stop_requested:
            pipe = None
            try:
                pipe = win32pipe.CreateNamedPipe(
                    self.socket_path,
                    win32pipe.PIPE_ACCESS_DUPLEX,
                    win32pipe.PIPE_TYPE_BYTE | win32pipe.PIPE_READMODE_BYTE | win32pipe.PIPE_WAIT,
                    win32pipe.PIPE_UNLIMITED_INSTANCES,
                    65536,
                    65536,
                    0,
                    sa,
                )

                win32pipe.ConnectNamedPipe(pipe, None)

                while self._running and not _stop_requested:
                    try:
                        result, data = win32file.ReadFile(pipe, 65536)
                        if result != 0 or not data:
                            break

                        message = None
                        request_id = None
                        try:
                            message = self.protocol.deserialize(data)
                            if isinstance(message, JSONRPCRequest):
                                request_id = message.id
                                result_data = await self.handlers.handle(
                                    message.method, message.params
                                )
                                response = self.protocol.create_response(
                                    message.id, result=result_data
                                )
                                win32file.WriteFile(pipe, self.protocol.serialize(response))
                                await asyncio.sleep(0.05)
                        except JSONRPCException as e:
                            response = self.protocol.create_error_response(
                                request_id=request_id,
                                code=e.code,
                                message=e.message,
                            )
                            win32file.WriteFile(pipe, self.protocol.serialize(response))
                            await asyncio.sleep(0.05)
                        except Exception as e:
                            logger.exception(f"Error handling request: {e}")
                            response = self.protocol.create_error_response(
                                request_id=request_id,
                                code=-32603,
                                message=str(e),
                            )
                            win32file.WriteFile(pipe, self.protocol.serialize(response))
                            await asyncio.sleep(0.05)

                    except Exception as e:
                        if pywintypes and isinstance(e, pywintypes.error):
                            break
                        logger.debug(f"Pipe read error: {e}")
                        break

            except Exception as e:
                logger.exception(f"Windows pipe error: {e}")
                await asyncio.sleep(0.1)
            finally:
                if pipe:
                    try:
                        win32file.CloseHandle(pipe)
                    except Exception:
                        pass

    async def stop(self) -> None:
        """Stop the daemon."""
        logger.info("Stopping BLE daemon...")
        self._running = False

        await self.device_manager.disconnect_all()

        if self._server:
            self._server.close()
            await self._server.wait_closed()

        for writer in list(self._clients):
            try:
                writer.close()
            except Exception:
                pass


def run_daemon(log_level: str = "INFO") -> None:
    """Run the BLE daemon (entry point)."""
    global _stop_requested
    _stop_requested = False
    
    daemon = BLEDaemon(log_level=log_level)
    asyncio.run(daemon.run())
