#!/usr/bin/env python
'''
@file fake_serial_entry.py

@brief FakeSerialEntry: fake implementation that extends SerialIoEntry for testing

This module provides a fake serial entry that inherits from SerialIoEntry
but overrides the serial communication to simulate HCI device responses
without requiring actual hardware.

Copyright (C) Atmosic 2025
'''
from __future__ import annotations
import sys
import os
import struct
import threading
from queue import Queue, Empty
from logging import getLogger

from deviceio.serial_io_entry import SerialIoEntry
from lib.bytes_utils import bytes_to_hex
from lib.fake_device.fake_device_status import FakeDeviceStatus

from packet.hci.hci_packet_implement import (
    HciProgInfoRsp, HciProgBlkClean, HciProgBaudrateSet, 
    HciProgDump, HciProgXorCheck, HciProgSha256Check,
    HciBootStatusEvent
)

logger = getLogger(__name__)
LOG_WRITE = "FSW"  # Fake Serial Write
LOG_READ = "FSR"   # Fake Serial Read

class FakeSerialObject:
    """Fake serial object that mimics pyserial interface."""

    def __init__(self, fake_entry):
        self._fake_entry = fake_entry
        self.is_open = False
        self.timeout = 1.0
        self.in_waiting = 0

    def read(self, size=1):
        return self._fake_entry._fake_read(size)

    def write(self, data):
        return self._fake_entry._fake_write(data)

    def close(self):
        self._fake_entry._fake_close()
        self.is_open = False


class FakeSerialEntry(SerialIoEntry):
    """
    Fake serial entry that extends SerialIoEntry for testing.
    
    This class inherits from SerialIoEntry but overrides the serial communication
    to simulate HCI device responses without requiring actual hardware.
    """
    
    def __init__(self, port, baudrate, rtscts=False, stream_type: str = "ascii",
                 timeout: float | None = None):
        # Initialize parent class
        super().__init__(port, baudrate, rtscts, stream_type, timeout)

        # Override _ser with our fake serial object
        self._fake_ser = FakeSerialObject(self)
        self._ser = self._fake_ser

        # Fake serial state
        self._is_open = False
        
        # Queues for communication
        self._write_queue = Queue()
        self._read_queue = Queue()
        
        # Response handlers for different opcodes
        self._response_handlers = {
            0xF870: self._create_prog_info_response,
            0xF871: self._create_prog_blk_clean_response,
            0xF872: self._create_prog_baudrate_set_response,
            0xF873: self._create_prog_write_data_response,
            0xF874: self._create_prog_apply_blk_response,
            0xF875: self._create_prog_xor_check_response,
            0xF876: self._create_prog_sha256_check_response,
            0xF877: self._create_prog_dump_response,
        }
        
        # Background thread for processing commands
        self._processor_thread = None
        self._stop_event = threading.Event()

    def open(self) -> bool:
        """Open the fake serial port."""
        with self._com_read_lock, self._com_write_lock:
            if self._is_open:
                return True

            self._is_open = True
            self._fake_ser.is_open = True
            self._stop_event.clear()

            # Start background processor
            self._processor_thread = threading.Thread(target=self._process_commands, daemon=True)
            self._processor_thread.start()

            # Send initial boot status event after a small delay to ensure receiver is ready
            threading.Timer(0.1, self._send_boot_status_event).start()

            logger.debug(f"[FakeSerial] Opened fake serial: {self._port}")
            return True



    def close(self):
        """Close the fake serial port."""
        with self._com_read_lock, self._com_write_lock:
            if not self._is_open:
                return
                
            logger.debug(f"[FakeSerial] Closing fake serial: {self._port}")
            self._is_open = False
            self._stop_event.set()
            
            # Clear queues to unblock any waiting threads
            try:
                while not self._write_queue.empty():
                    self._write_queue.get_nowait()
            except:
                pass
                
            try:
                while not self._read_queue.empty():
                    self._read_queue.get_nowait()
            except:
                pass
            
            # Force stop the processor thread
            if self._processor_thread and self._processor_thread.is_alive():
                logger.debug(f"[FakeSerial] Waiting for processor thread to stop for {self._port}")
                self._processor_thread.join(timeout=0.2)
                if self._processor_thread.is_alive():
                    logger.warning(f"[FakeSerial] Processor thread did not stop cleanly for {self._port}")
                    # Force terminate by setting daemon flag
                    self._processor_thread.daemon = True
                
            self._processor_thread = None
            logger.debug(f"[FakeSerial] Closed fake serial: {self._port}")

    def _fake_read(self, size=1):
        """Internal read method for FakeSerialObject."""
        if not self._is_open:
            return b''

        try:
            # Update in_waiting property first
            self._fake_ser.in_waiting = self._read_queue.qsize()
            logger.debug(f"[FakeSerial] _fake_read called with size={size}, in_waiting={self._fake_ser.in_waiting}")

            if size == 0:
                return b''

            # Try to get data from queue
            try:
                data = self._read_queue.get(timeout=0.1)
                if data:
                    logger.debug(f"[{LOG_READ}] {bytes_to_hex(data)}, len: {len(data)}")
                    return data
                else:
                    logger.debug(f"[{LOG_READ}] No data")
                    return b''
            except Empty:
                logger.debug(f"[{LOG_READ}] Queue empty")
                return b''
        except Exception as e:
            logger.debug(f"[FakeSerial] Read error: {e}")
            return b''

    def _fake_write(self, data):
        """Internal write method for FakeSerialObject."""
        if isinstance(data, str):
            if self._stream_type == "hex":
                ori = data
                data = bytes.fromhex(data)
                logger.debug(f"[{LOG_WRITE}] {bytes_to_hex(data)}, "
                               f"len: {len(data)} ({ori})")
            else:
                data = data.encode('utf-8')
                logger.debug(f"[{LOG_WRITE}] {bytes_to_hex(data)}, "
                               f"len: {len(data)}")
        else:
            logger.debug(f"[{LOG_WRITE}] {bytes_to_hex(data)}, "
                           f"len: {len(data)}")

        if not self._is_open:
            return 0

        # Add to write queue for processing
        self._write_queue.put(data)
        return len(data)

    def _fake_close(self):
        """Internal close method for FakeSerialObject."""
        self.close()

    def flush_input(self):
        """Flush input buffer."""
        try:
            while not self._read_queue.empty():
                self._read_queue.get_nowait()
        except:
            pass

    def flush_output(self):
        """Flush output buffer."""
        try:
            while not self._write_queue.empty():
                self._write_queue.get_nowait()
        except:
            pass

    def _send_boot_status_event(self):
        """Send initial boot status event."""
        if not FakeDeviceStatus.is_send_boot_status_event:
            return
        try:
            # Create HCI Vendor Specific Event for boot status
            # Format: 04 FF 08 70 41 74 6d 6f 73 69 63
            # 04 = HCI Event packet type
            # FF = Vendor Specific Event code
            # 08 = Parameter length (1 + 7 bytes for "Atmosic")
            # 70 = Sub-event code for boot status
            # 41 74 6d 6f 73 69 63 = "Atmosic" in ASCII

            boot_event = HciBootStatusEvent.EventVS()
            boot_event.pktype = 0x04  # HCI Event packet type
            boot_event.evtcode = 0xFF  # Vendor Specific Event
            boot_event.plen = 8  # 1 byte sub_event + 7 bytes "Atmosic"
            boot_event.sub_event = 0x70  # Boot status sub-event
            boot_event.app_ver = b"Atmosic"

            response_data = boot_event.to_bytes()
            self._read_queue.put(response_data)
            # Update in_waiting property
            self._fake_ser.in_waiting = self._read_queue.qsize()
            logger.debug(f"[FakeSerial] Sent boot status event: {bytes_to_hex(response_data)}, queue_size={self._read_queue.qsize()}")
        except Exception as e:
            logger.error(f"[FakeSerial] Failed to send boot status event: {e}")

    def _process_commands(self):
        """Background thread to process HCI commands and generate responses."""
        logger.debug(f"[FakeSerial] Command processor thread started for {self._port}")
        try:
            while not self._stop_event.is_set():
                try:
                    command_data = self._write_queue.get(timeout=0.1)
                    if FakeDeviceStatus.is_send_boot_status_event:
                        FakeDeviceStatus.is_send_boot_status_event = False
                        logger.debug(
                            "[FakeSerial] Get HCI command, then stop send" \
                            " boot status event")
                    if self._stop_event.is_set():
                        break
                    
                    # Process the command and generate response
                    response = self._process_hci_command(command_data)
                    if response:
                        self._read_queue.put(response)
                        # Update in_waiting property
                        self._fake_ser.in_waiting = self._read_queue.qsize()
                        logger.debug(f"[FakeSerial] Sent response for command, queue_size={self._read_queue.qsize()}")
                        
                except Empty:
                    continue
                except Exception as e:
                    logger.error(f"[FakeSerial] Error processing command: {e}")
        finally:
            logger.debug(f"[FakeSerial] Command processor thread exiting for {self._port}")

    def _process_hci_command(self, command_data: bytes) -> bytes | None:
        """Process HCI command and return appropriate response."""
        logger.debug(f"[FakeSerial] Processing command: {bytes_to_hex(command_data)}")

        if len(command_data) < 3:
            logger.warning(f"[FakeSerial] Command too short: {len(command_data)} bytes")
            return None

        # Parse HCI command header
        opcode = struct.unpack('<H', command_data[1:3])[0]
        logger.debug(f"[FakeSerial] Command opcode: 0x{opcode:04X}")

        # Get response handler
        handler = self._response_handlers.get(opcode)
        if handler:
            try:
                response = handler()
                logger.debug(f"[FakeSerial] Generated response: {bytes_to_hex(response) if response else 'None'}")
                return response
            except Exception as e:
                logger.error(f"[FakeSerial] Error in response handler for opcode 0x{opcode:04X}: {e}")

        logger.warning(f"[FakeSerial] No handler for opcode 0x{opcode:04X}")
        return None

    def _create_prog_info_response(self) -> bytes:
        """Create HciProgInfoRsp response."""
        response = HciProgInfoRsp.EventCC()
        response.pktype = 0x04  # HCI Event
        response.evtcode = 0x0E  # Command Complete
        response.plen = 16  # Parameter length
        response.num_hci_command_pkts = 1
        response.opcode = 0xF870
        response.status = 0
        response.app_ver = 0x0100  # Version 1.0
        response.protocol_ver = 0x0100  # Protocol version 1.0
        response.ram_buffer_start = 0x20000000
        response.ram_buffer_size = 0x10000
        response.num_flash = 1
        response.flash_ids = b'\x12\x34\x56\x78'  # Flash ID
        return response.to_bytes()

    def _create_prog_blk_clean_response(self) -> bytes:
        """Create HciProgBlkClean response."""
        response = HciProgBlkClean.EventCC()
        response.pktype = 0x04  # HCI Event
        response.evtcode = 0x0E  # Command Complete
        response.plen = 4  # Parameter length
        response.num_hci_command_pkts = 1
        response.opcode = 0xF871
        response.status = 0
        return response.to_bytes()

    def _create_prog_baudrate_set_response(self) -> bytes:
        """Create HciProgBaudrateSet response."""
        response = HciProgBaudrateSet.EventCC()
        response.pktype = 0x04  # HCI Event
        response.evtcode = 0x0E  # Command Complete
        response.plen = 4  # Parameter length
        response.num_hci_command_pkts = 1
        response.opcode = 0xF872
        response.status = 0
        return response.to_bytes()

    def _create_prog_write_data_response(self) -> bytes:
        """Create HciProgWriteData response."""
        from packet.hci.hci_packet_implement import HciProgWriteData
        response = HciProgWriteData.EventCC()
        response.pktype = 0x04  # HCI Event
        response.evtcode = 0x0E  # Command Complete
        response.plen = 5  # Parameter length
        response.num_hci_command_pkts = 1
        response.opcode = 0xF873
        response.status = 0
        response.seq_no = 1
        return response.to_bytes()

    def _create_prog_apply_blk_response(self) -> bytes:
        """Create HciProgApplyBlk response."""
        from packet.hci.hci_packet_implement import HciProgApplyBlk
        response = HciProgApplyBlk.EventCC()
        response.pktype = 0x04  # HCI Event
        response.evtcode = 0x0E  # Command Complete
        response.plen = 4  # Parameter length
        response.num_hci_command_pkts = 1
        response.opcode = 0xF874
        response.status = 0
        return response.to_bytes()

    def _create_prog_xor_check_response(self) -> bytes:
        """Create HciProgXorCheck response."""
        response = HciProgXorCheck.EventCC()
        response.pktype = 0x04  # HCI Event
        response.evtcode = 0x0E  # Command Complete
        response.plen = 5  # Parameter length
        response.num_hci_command_pkts = 1
        response.opcode = 0xF875
        response.status = 0
        response.xor = 0x42  # XOR checksum result
        return response.to_bytes()

    def _create_prog_sha256_check_response(self) -> bytes:
        """Create HciProgSha256Check response."""
        response = HciProgSha256Check.EventCC()
        response.pktype = 0x04  # HCI Event
        response.evtcode = 0x0E  # Command Complete
        response.plen = 20  # Parameter length (4 + 16 bytes SHA256)
        response.num_hci_command_pkts = 1
        response.opcode = 0xF876
        response.status = 0
        response.sha256_first_16_bytes = bytes.fromhex("12345678123456781234567812345678")
        return response.to_bytes()

    def _create_prog_dump_response(self) -> bytes:
        """Create HciProgDump response."""
        # Create dummy data for testing
        dummy_data = b'\x01\x02\x03\x04'
        xor_checksum = 0x42

        # Manually construct the response bytes to avoid field overlap issues
        # Format: pktype(1) + evtcode(1) + plen(1) + num_hci_command_pkts(1) + opcode(2) + status(1) + data_length(1) + data(n) + xor(1)
        response_bytes = bytearray()
        response_bytes.append(0x04)  # pktype: HCI Event
        response_bytes.append(0x0E)  # evtcode: Command Complete
        response_bytes.append(4 + 1 + len(dummy_data) + 1)  # plen: CC header + status + data_length + data + xor
        response_bytes.append(0x01)  # num_hci_command_pkts
        response_bytes.extend(struct.pack('<H', 0xF877))  # opcode (little-endian)
        response_bytes.append(0x00)  # status
        response_bytes.append(len(dummy_data))  # data_length
        response_bytes.extend(dummy_data)  # data
        response_bytes.append(xor_checksum)  # xor checksum

        return bytes(response_bytes)
