# Copyright 2026 Espressif Systems (Shanghai) Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
import queue
import threading
from typing import Any, Dict, List

from esp_viewer.core.adapter import BaseFrontendAdapter
from esp_viewer.core.backend import DeviceService
from esp_viewer.core.firmware import FirmwareManager
from esp_viewer.core.protocol import CommandType, EventType, LogLevel, Packet

logger = logging.getLogger("esp_viewer.engine")


class EspViewerEngine:
    """
    The core backend engine.
    It manages the central queues and the DeviceService lifecycle.
    It routes events to registered Front-end Adapters.
    """

    def __init__(self, profile: Dict[str, Any]):
        self.profile = profile

        # Central IPC Queues
        self.cmd_queue = queue.Queue()
        self.event_queue = queue.Queue()

        # The actual device communication service running in a separate thread
        self.device_service = DeviceService(
            self.cmd_queue, self.event_queue, self.profile
        )

        # Firmware downloading and flashing manager
        self.firmware_manager = FirmwareManager(self)

        # Registered Adapters (UI, Web, CLI, etc.)
        self.adapters: List[BaseFrontendAdapter] = []

        # Dispatcher Thread
        self.running = False
        self._dispatch_thread = threading.Thread(
            target=self._dispatch_events, daemon=True
        )

    def add_adapter(self, adapter: BaseFrontendAdapter):
        """Registers a frontend adapter to receive events from this engine."""
        self.adapters.append(adapter)
        adapter.bind_engine(self)

    def start(self):
        """Starts the Engine's dispatch thread and the DeviceService thread."""
        self.running = True
        self.device_service.start()
        self._dispatch_thread.start()

    def stop(self):
        """Stops all threads safely."""
        self.running = False
        self.device_service.stop()
        if self._dispatch_thread.is_alive():
            self._dispatch_thread.join(timeout=1.0)

    def set_device_interface(self, interface):
        """Injects a physical or mock interface into the DeviceService."""
        self.device_service.set_device_interface(interface)

    def send_command(self, cmd_type: CommandType, payload: Any = None):
        """Allows adapters to send commands to the central cmd_queue."""
        # Intercept firmware commands directly in the engine level
        # since they bypass the DeviceService streaming loops
        if cmd_type == CommandType.DOWNLOAD_FIRMWARE:
            payload = payload or {}
            url = payload.get("url", "test")
            self.firmware_manager.start_download(url)
            return
        elif cmd_type == CommandType.FLASH_FIRMWARE:
            payload = payload or {}
            port = payload.get("port")
            firmware_path = payload.get("firmware_path")
            if port:
                self.firmware_manager.flash_firmware(port, firmware_path)
            else:
                logger.error("Flash command missing 'port' payload.")
            return

        self.cmd_queue.put(Packet(cmd_type, payload))

    def _dispatch_events(self):
        """
        Continuously reads from the central event_queue and fans out
        the packets to all registered adapters.
        """
        while self.running:
            try:
                # 0.01s timeout to allow clean shutdown checks
                packet = self.event_queue.get(timeout=0.01)
                for adapter in self.adapters:
                    try:
                        adapter.on_event_received(packet)
                    except Exception as e:
                        # Ensure one broken adapter doesn't crash the engine
                        logger.error(
                            f"Error dispatching to adapter {adapter.__class__.__name__}: {e}",
                            exc_info=True,
                        )
            except queue.Empty:
                pass
