"""
LilyGo T-Embed communication controller
"""

import serial
import json
import time
import asyncio
from typing import Dict, Any, Optional, Callable, List
from loguru import logger


class TEmbedController:
    """Synchronous T-Embed controller"""

    def __init__(self, port: str = '/dev/ttyUSB0', baudrate: int = 115200):
        """
        Initialize T-Embed controller

        Args:
            port: Serial port path
            baudrate: Serial baudrate (default 115200)
        """
        self.port = port
        self.baudrate = baudrate
        self.serial = None
        self.request_id = 0
        self.connect()

    def connect(self):
        """Establish serial connection"""
        try:
            self.serial = serial.Serial(self.port, self.baudrate, timeout=1)
            time.sleep(2)  # Wait for device reset
            logger.info(f"Connected to T-Embed on {self.port}")
        except serial.SerialException as e:
            logger.error(f"Failed to connect to T-Embed: {e}")
            raise

    def disconnect(self):
        """Close serial connection"""
        if self.serial and self.serial.is_open:
            self.serial.close()
            logger.info("Disconnected from T-Embed")

    def send_command(self, cmd: str, params: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]:
        """
        Send command to T-Embed and wait for response

        Args:
            cmd: Command name
            params: Command parameters

        Returns:
            Response dictionary or None
        """
        self.request_id += 1
        message = {
            'cmd': cmd,
            'params': params or {},
            'id': self.request_id
        }

        try:
            # Send command
            self.serial.write(json.dumps(message).encode() + b'\n')
            logger.debug(f"Sent command: {cmd} (id={self.request_id})")

            # Wait for response
            line = self.serial.readline().decode().strip()
            if line:
                response = json.loads(line)
                logger.debug(f"Received response: {response.get('status')}")
                return response

        except (serial.SerialException, json.JSONDecodeError) as e:
            logger.error(f"Command failed: {e}")

        return None

    def start_scan(self, frequencies: List[int], duration: int = 60,
                   modulation: str = 'AUTO', rssi_threshold: int = -90) -> Optional[Dict[str, Any]]:
        """
        Start scanning for signals

        Args:
            frequencies: List of frequencies in Hz
            duration: Scan duration in seconds (0 = continuous)
            modulation: Modulation type (OOK, FSK, AUTO)
            rssi_threshold: Minimum signal strength in dBm

        Returns:
            Response with scan_id
        """
        return self.send_command('SCAN', {
            'frequencies': frequencies,
            'duration': duration,
            'modulation': modulation,
            'rssi_threshold': rssi_threshold
        })

    def stop_scan(self, scan_id: str) -> Optional[Dict[str, Any]]:
        """Stop active scan"""
        return self.send_command('STOP_SCAN', {'scan_id': scan_id})

    def capture_signal(self, frequency: int, duration: int = 5,
                       format: str = 'BOTH') -> Optional[Dict[str, Any]]:
        """
        Capture signal at specific frequency

        Args:
            frequency: Frequency in Hz
            duration: Capture duration in seconds
            format: Output format (RAW, DECODED, BOTH)

        Returns:
            Capture data with protocol and raw signal
        """
        return self.send_command('CAPTURE', {
            'frequency': frequency,
            'duration': duration,
            'format': format
        })

    def get_status(self) -> Optional[Dict[str, Any]]:
        """Get device status"""
        return self.send_command('STATUS')

    def configure(self, **settings) -> Optional[Dict[str, Any]]:
        """Configure device settings"""
        return self.send_command('CONFIG', settings)

    def list_files(self, path: str = '/sd/giglez/', pattern: str = '*.sub') -> Optional[Dict[str, Any]]:
        """List files on SD card"""
        return self.send_command('LIST_FILES', {
            'path': path,
            'pattern': pattern
        })

    def get_file(self, path: str) -> Optional[Dict[str, Any]]:
        """Retrieve file from SD card"""
        return self.send_command('GET_FILE', {'path': path})

    def replay(self, file_path: str, repeat: int = 1, delay_ms: int = 100) -> Optional[Dict[str, Any]]:
        """
        Replay a .sub file

        Args:
            file_path: Path to .sub file on SD card
            repeat: Number of times to repeat
            delay_ms: Delay between repeats in milliseconds

        Returns:
            Transmission result
        """
        return self.send_command('REPLAY', {
            'file': file_path,
            'repeat': repeat,
            'delay_ms': delay_ms
        })

    def listen_events(self, callback: Callable[[Dict[str, Any]], None], timeout: Optional[int] = None):
        """
        Listen for event notifications

        Args:
            callback: Function to call for each event
            timeout: Optional timeout in seconds
        """
        start_time = time.time()

        while True:
            if timeout and (time.time() - start_time) > timeout:
                break

            try:
                line = self.serial.readline().decode().strip()
                if line:
                    data = json.loads(line)
                    if 'event' in data:
                        callback(data)
            except (json.JSONDecodeError, KeyboardInterrupt):
                break

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.disconnect()


class AsyncTEmbedController:
    """Asynchronous T-Embed controller for better performance"""

    def __init__(self, port: str = '/dev/ttyUSB0', baudrate: int = 115200):
        self.port = port
        self.baudrate = baudrate
        self.serial = None
        self.request_id = 0
        self.pending_requests: Dict[int, asyncio.Future] = {}
        self.event_callbacks: List[Callable] = []

    async def connect(self):
        """Establish async serial connection"""
        try:
            import aioserial
            self.serial = aioserial.AioSerial(port=self.port, baudrate=self.baudrate)
            await asyncio.sleep(2)
            logger.info(f"Connected to T-Embed on {self.port}")

            # Start background listener
            asyncio.create_task(self._listen())

        except Exception as e:
            logger.error(f"Failed to connect to T-Embed: {e}")
            raise

    async def disconnect(self):
        """Close async serial connection"""
        if self.serial:
            self.serial.close()
            logger.info("Disconnected from T-Embed")

    async def send_command(self, cmd: str, params: Optional[Dict[str, Any]] = None,
                          timeout: int = 10) -> Optional[Dict[str, Any]]:
        """
        Send command and wait for response asynchronously

        Args:
            cmd: Command name
            params: Command parameters
            timeout: Response timeout in seconds

        Returns:
            Response dictionary
        """
        self.request_id += 1
        message = {
            'cmd': cmd,
            'params': params or {},
            'id': self.request_id
        }

        try:
            # Send command
            await self.serial.write_async(json.dumps(message).encode() + b'\n')
            logger.debug(f"Sent command: {cmd} (id={self.request_id})")

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

            # Wait for response with timeout
            response = await asyncio.wait_for(future, timeout=timeout)
            return response

        except asyncio.TimeoutError:
            logger.error(f"Command timeout: {cmd}")
            del self.pending_requests[self.request_id]
            return None

        except Exception as e:
            logger.error(f"Command failed: {e}")
            return None

    async def _listen(self):
        """Background task to listen for responses and events"""
        while self.serial and not self.serial.closed:
            try:
                line = await self.serial.readline_async()
                if line:
                    data = json.loads(line.decode().strip())

                    # Handle response
                    if 'id' in data and data['id'] in self.pending_requests:
                        self.pending_requests[data['id']].set_result(data)
                        del self.pending_requests[data['id']]

                    # Handle event
                    elif 'event' in data:
                        for callback in self.event_callbacks:
                            await callback(data)

            except json.JSONDecodeError:
                pass
            except Exception as e:
                logger.error(f"Listen error: {e}")
                await asyncio.sleep(0.1)

    def on_event(self, callback: Callable[[Dict[str, Any]], None]):
        """Register event callback"""
        self.event_callbacks.append(callback)

    async def start_scan(self, frequencies: List[int], **kwargs) -> Optional[Dict[str, Any]]:
        """Start scanning (async)"""
        params = {'frequencies': frequencies, **kwargs}
        return await self.send_command('SCAN', params)

    async def stop_scan(self, scan_id: str) -> Optional[Dict[str, Any]]:
        """Stop scan (async)"""
        return await self.send_command('STOP_SCAN', {'scan_id': scan_id})

    async def capture_signal(self, frequency: int, **kwargs) -> Optional[Dict[str, Any]]:
        """Capture signal (async)"""
        params = {'frequency': frequency, **kwargs}
        return await self.send_command('CAPTURE', params)

    async def get_status(self) -> Optional[Dict[str, Any]]:
        """Get device status (async)"""
        return await self.send_command('STATUS')

    async def __aenter__(self):
        await self.connect()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self.disconnect()
