import readline
import os
import time
from typing import Optional

from utils.display import Display
from cli.help import HelpManager
from core.transport import Transport
from proto.c2_pb2 import Command
from streams.udp_receiver import UDPReceiver
from streams.config import UDP_HOST, UDP_PORT, IMAGE_DIR, MULTILAT_AUTH_TOKEN
from web.server import UnifiedWebServer
from web.mlat import MlatEngine

DEV_MODE = True


class CLI:
    def __init__(self, registry, commands, groups, transport: Transport):
        self.registry = registry
        self.commands = commands
        self.groups = groups
        self.transport = transport
        self.help_manager = HelpManager(commands, DEV_MODE)
        self.active_commands = {}  # {request_id: {"device_id": ..., "command_name": ..., "start_time": ..., "status": "running"}}

        # Separate server instances
        self.web_server: Optional[UnifiedWebServer] = None
        self.udp_receiver: Optional[UDPReceiver] = None
        self.mlat_engine = MlatEngine()

        readline.parse_and_bind("tab: complete")
        readline.set_completer(self._complete)

    # ================= TAB COMPLETION =================

    def _complete(self, text, state):
        buffer = readline.get_line_buffer()
        parts = buffer.split()

        options = []

        if len(parts) == 1:
            options = ["send", "list", "modules", "group", "help", "clear", "exit", "active_commands", "web", "camera"]

        elif parts[0] == "send":
            if len(parts) == 2:  # Completing target (device ID, 'all', 'group')
                options = ["all", "group"] + self.registry.ids()
            elif len(parts) == 3 and parts[1] == "group":  # Completing group name after 'send group'
                options = list(self.groups.all_groups().keys())
            elif (len(parts) == 3 and parts[1] != "group") or (len(parts) == 4 and parts[1] == "group"):  # Completing command name
                options = self.commands.list()

        elif parts[0] == "web":
            if len(parts) == 2:
                options = ["start", "stop", "status"]

        elif parts[0] == "camera":
            if len(parts) == 2:
                options = ["start", "stop", "status"]

        elif parts[0] == "group":
            if len(parts) == 2:  # Completing group action
                options = ["add", "remove", "list", "show"]
            elif parts[1] == "add" and len(parts) >= 3:  # Completing device IDs for 'group add'
                # Suggest available device IDs that are not already in the group being added to
                group_name = parts[2] if len(parts) > 2 else ""
                current_group_members = self.groups.get(group_name) if group_name else []
                all_device_ids = set(self.registry.ids())
                options = sorted(list(all_device_ids - set(current_group_members)))
            elif parts[1] in ("remove", "show") and len(parts) == 3:  # Completing group names for 'group remove/show'
                options = list(self.groups.all_groups().keys())
            elif parts[1] == "remove" and len(parts) >= 4: # Completing device IDs for 'group remove'
                group_name = parts[2]
                options = self.groups.get(group_name)

        matches = [o for o in options if o.startswith(text)]
        return matches[state] if state < len(matches) else None

    # ================= MAIN LOOP =================

    def loop(self):
        while True:
            cmd = input(Display.cli_prompt()).strip()
            if not cmd:
                continue

            if cmd == "exit":
                return

            self.execute_command(cmd)

    def execute_command(self, cmd: str):
        """Execute a command string. Used by both CLI loop and TUI."""
        if not cmd:
            return

        parts = cmd.split()
        action = parts[0]

        if action == "help":
            self.help_manager.show(parts[1:])
            return

        if action == "exit":
            return

        if action == "clear":
            os.system("cls" if os.name == "nt" else "clear")
            return

        if action == "list":
            self._handle_list()
            return

        if action == "modules":
            self.help_manager.show_modules()
            return

        if action == "group":
            self._handle_group(parts[1:])
            return

        if action == "send":
            self._handle_send(parts)
            return

        if action == "active_commands":
            self._handle_active_commands()
            return

        if action == "web":
            self._handle_web(parts[1:])
            return

        if action == "camera":
            self._handle_camera(parts[1:])
            return

        Display.error("Unknown command")

    # ================= HANDLERS =================

    def _handle_list(self):
        now = time.time()
        active_devices = self.registry.all()

        if not active_devices:
            Display.system_message("No devices currently connected.")
            return

        Display.system_message("Connected Devices:")
        Display.print_table_header(["ID", "IP Address", "Status", "Connected For", "Last Seen"])

        for d in active_devices:
            connected_for = Display.format_duration(now - d.connected_at)
            last_seen_duration = Display.format_duration(now - d.last_seen)
            Display.print_table_row([d.id, d.address[0], d.status, connected_for, last_seen_duration])

    def _handle_send(self, parts):
        if len(parts) < 3:
            Display.error("Usage: send <id|all|group> <command> [args...]")
            return

        target_specifier = parts[1]
        command_parts = parts[2:]

        devices_to_target = []
        target_description = ""

        # Resolve devices based on target_specifier
        if target_specifier == "all":
            devices_to_target = self.registry.all()
            target_description = "all connected devices"
        elif target_specifier == "group":
            if len(command_parts) < 2:
                Display.error("Usage: send group <name> <command> [args...]")
                return
            group_name = command_parts[0]
            command_parts = command_parts[1:]
            group_members_ids = self.groups.get(group_name)
            if not group_members_ids:
                Display.error(f"Group '{group_name}' not found or is empty.")
                return
            
            active_group_devices = []
            for esp_id in group_members_ids:
                dev = self.registry.get(esp_id)
                if dev:
                    active_group_devices.append(dev)
                else:
                    Display.device_event(esp_id, f"Device in group '{group_name}' is not currently connected.")
            
            if not active_group_devices:
                Display.error(f"No active devices found in group '{group_name}'.")
                return
            
            devices_to_target = active_group_devices
            target_description = f"group '{group_name}' ({', '.join([d.id for d in devices_to_target])})"
        else:
            dev = self.registry.get(target_specifier)
            if dev:
                devices_to_target.append(dev)
                target_description = f"device '{target_specifier}'"
            else:
                Display.error(f"Device '{target_specifier}' not found.")
                return

        if not devices_to_target:
            Display.error("No target devices resolved for sending command.")
            return

        # Build Command
        cmd_name = command_parts[0]
        argv = command_parts[1:]

        request_id_base = f"req-{int(time.time())}"
        Display.system_message(f"Sending command '{cmd_name}' to {target_description}...")

        for i, d in enumerate(devices_to_target):
            cmd = Command()
            cmd.device_id = d.id
            cmd.command_name = cmd_name
            cmd.argv.extend(argv)
            
            request_id = f"{request_id_base}-{i}"
            cmd.request_id = request_id

            Display.command_sent(d.id, cmd_name, request_id)
            self.transport.send_command(d.sock, cmd)
            self.active_commands[request_id] = {
                "device_id": d.id,
                "command_name": cmd_name,
                "start_time": time.time(),
                "status": "running",
                "output": []
            }

    def handle_command_response(self, request_id: str, device_id: str, payload: str, eof: bool):
        if request_id in self.active_commands:
            command_info = self.active_commands[request_id]
            command_info["output"].append(payload)
            if eof:
                command_info["status"] = "completed"
                Display.command_response(request_id, device_id, f"Command completed in {Display.format_duration(time.time() - command_info['start_time'])}")
                # Optionally print full output here if not already streamed
                # Display.command_response(request_id, device_id, "\n".join(command_info["output"]))
                del self.active_commands[request_id]
            else:
                # For streaming output, Display.command_response already prints each line
                pass
        else:
            Display.device_event(device_id, f"Received response for unknown command {request_id}: {payload}")

    def _handle_group(self, parts):
        if not parts:
            Display.error("Usage: group <add|remove|list|show>")
            return

        cmd = parts[0]

        if cmd == "add" and len(parts) >= 3:
            group = parts[1]
            added_devices = []
            for esp_id in parts[2:]:
                if self.registry.get(esp_id): # Only add if device exists
                    self.groups.add_device(group, esp_id)
                    added_devices.append(esp_id)
                else:
                    Display.device_event(esp_id, "Device not found, skipping group add.")
            if added_devices:
                Display.system_message(f"Group '{group}' updated. Added: {', '.join(added_devices)}")
            else:
                Display.system_message(f"No valid devices to add to group '{group}'.")


        elif cmd == "remove" and len(parts) >= 3:
            group = parts[1]
            removed_devices = []
            for esp_id in parts[2:]:
                if esp_id in self.groups.get(group):
                    self.groups.remove_device(group, esp_id)
                    removed_devices.append(esp_id)
                else:
                    Display.device_event(esp_id, f"Device not in group '{group}', skipping remove.")
            if removed_devices:
                Display.system_message(f"Group '{group}' updated. Removed: {', '.join(removed_devices)}")
            else:
                Display.system_message(f"No specified devices found in group '{group}' to remove.")

        elif cmd == "list":
            all_groups = self.groups.all_groups()
            if not all_groups:
                Display.system_message("No groups defined.")
                return
            Display.system_message("Defined Groups:")
            for g, members in all_groups.items():
                Display.system_message(f"  {g}: {', '.join(members) if members else 'No members'}")

        elif cmd == "show" and len(parts) == 2:
            group_name = parts[1]
            members = self.groups.get(group_name)
            if members:
                Display.system_message(f"Members of group '{group_name}': {', '.join(members)}")
            else:
                Display.system_message(f"Group '{group_name}' not found or empty.")

        else:
            Display.error("Invalid group command usage. See 'help group' for details.")

    def _handle_active_commands(self):
        if not self.active_commands:
            Display.system_message("No commands are currently active.")
            return

        Display.system_message("Active Commands:")
        Display.print_table_header(["Request ID", "Device ID", "Command", "Status", "Elapsed Time"])

        now = time.time()
        for req_id, cmd_info in self.active_commands.items():
            elapsed_time = Display.format_duration(now - cmd_info["start_time"])
            Display.print_table_row([
                req_id,
                cmd_info["device_id"],
                cmd_info["command_name"],
                cmd_info["status"],
                elapsed_time
            ])

    def _handle_web(self, parts):
        """Handle web server commands (frontend + multilateration API)."""
        if not parts:
            Display.error("Usage: web <start|stop|status>")
            return

        cmd = parts[0]

        if cmd == "start":
            if self.web_server and self.web_server.is_running:
                Display.system_message("Web server is already running.")
                return

            self.web_server = UnifiedWebServer(
                device_registry=self.registry,
                mlat_engine=self.mlat_engine,
                multilat_token=MULTILAT_AUTH_TOKEN,
                camera_receiver=self.udp_receiver
            )

            if self.web_server.start():
                Display.system_message(f"Web server started at {self.web_server.get_url()}")
            else:
                Display.error("Web server failed to start")

        elif cmd == "stop":
            if not self.web_server or not self.web_server.is_running:
                Display.system_message("Web server is not running.")
                return

            self.web_server.stop()
            Display.system_message("Web server stopped.")
            self.web_server = None

        elif cmd == "status":
            Display.system_message("Web Server Status:")
            if self.web_server and self.web_server.is_running:
                Display.system_message(f"  Status: Running")
                Display.system_message(f"  URL: {self.web_server.get_url()}")
            else:
                Display.system_message(f"  Status: Stopped")

            # MLAT stats
            Display.system_message("MLAT Engine:")
            state = self.mlat_engine.get_state()
            Display.system_message(f"  Mode: {state.get('coord_mode', 'gps').upper()}")
            Display.system_message(f"  Scanners: {state['scanners_count']}")
            if state['target']:
                pos = state['target']['position']
                if 'lat' in pos:
                    Display.system_message(f"  Target: ({pos['lat']:.6f}, {pos['lon']:.6f})")
                else:
                    Display.system_message(f"  Target: ({pos['x']:.2f}m, {pos['y']:.2f}m)")
            else:
                Display.system_message(f"  Target: Not calculated")

        else:
            Display.error("Invalid web command. Use: start, stop, status")

    def _handle_camera(self, parts):
        """Handle camera UDP receiver commands."""
        if not parts:
            Display.error("Usage: camera <start|stop|status>")
            return

        cmd = parts[0]

        if cmd == "start":
            if self.udp_receiver and self.udp_receiver.is_running:
                Display.system_message("Camera UDP receiver is already running.")
                return

            self.udp_receiver = UDPReceiver(
                host=UDP_HOST,
                port=UDP_PORT,
                image_dir=IMAGE_DIR,
                device_registry=self.registry
            )

            if self.udp_receiver.start():
                Display.system_message(f"Camera UDP receiver started on {UDP_HOST}:{UDP_PORT}")
                # Update web server if running
                if self.web_server and self.web_server.is_running:
                    self.web_server.set_camera_receiver(self.udp_receiver)
                    Display.system_message("Web server updated with camera receiver")
            else:
                Display.error("Camera UDP receiver failed to start")

        elif cmd == "stop":
            if not self.udp_receiver or not self.udp_receiver.is_running:
                Display.system_message("Camera UDP receiver is not running.")
                return

            self.udp_receiver.stop()
            Display.system_message("Camera UDP receiver stopped.")
            self.udp_receiver = None
            # Update web server
            if self.web_server and self.web_server.is_running:
                self.web_server.set_camera_receiver(None)

        elif cmd == "status":
            Display.system_message("Camera UDP Receiver Status:")
            if self.udp_receiver and self.udp_receiver.is_running:
                stats = self.udp_receiver.get_stats()
                Display.system_message(f"  Status: Running on {UDP_HOST}:{UDP_PORT}")
                Display.system_message(f"  Packets received: {stats['packets_received']}")
                Display.system_message(f"  Frames decoded: {stats['frames_received']}")
                Display.system_message(f"  Decode errors: {stats['decode_errors']}")
                Display.system_message(f"  Invalid tokens: {stats['invalid_tokens']}")
                Display.system_message(f"  Active cameras: {stats['active_cameras']}")
            else:
                Display.system_message(f"  Status: Stopped")

        else:
            Display.error("Invalid camera command. Use: start, stop, status")
