# target_simulator/core/tftp_communicator.py

import io
import os
import traceback
from datetime import datetime
from typing import Dict, Any, List

from target_simulator.utils.tftp_client import TFTPClient, TFTPError
from target_simulator.core.communicator_interface import CommunicatorInterface
from target_simulator.core.models import Scenario
from target_simulator.core import command_builder
from target_simulator.utils.logger import get_logger
from target_simulator.config import DEBUG_CONFIG


class TFTPCommunicator(CommunicatorInterface):
    """A class to manage and abstract TFTP communication."""

    REMOTE_FILENAME = "MON:script.txt"

    def __init__(self):
        self.logger = get_logger(__name__)
        self.config: Dict[str, Any] | None = None
        self.debug_config = DEBUG_CONFIG
        self.temp_folder_path = os.path.join(
            os.getcwd(), self.debug_config.get("temp_folder_name", "Temp")
        )

    def _save_script_to_temp(self, content: str, prefix: str):
        """Saves the script content to a timestamped file in the Temp folder if debug is enabled."""
        if not self.debug_config.get("save_tftp_scripts", False):
            return

        try:
            if not os.path.exists(self.temp_folder_path):
                os.makedirs(self.temp_folder_path)

            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3]
            filename = f"{prefix}_{timestamp}.txt"
            filepath = os.path.join(self.temp_folder_path, filename)

            with open(filepath, "w", encoding="utf-8") as f:
                f.write(content)
            self.logger.debug(f"Saved debug script to '{filepath}'")
        except IOError as e:
            self.logger.error(f"Failed to save debug script: {e}")

    def connect(self, config: Dict[str, Any]) -> bool:
        # (Questo metodo rimane invariato)
        ip = config.get("ip")
        port = config.get("port")
        if not ip or not port:
            self.logger.error("Connection config failed: IP and Port are required.")
            return False
        try:
            config["port"] = int(port)
            self.config = config
            self.logger.info(f"TFTP communicator configured for server {ip}:{port}.")
            return True
        except (ValueError, TypeError):
            self.logger.error(f"Invalid port specified: {port}. Must be a number.")
            return False

    def disconnect(self) -> None:
        # (Questo metodo rimane invariato)
        if self.config:
            self.logger.info("TFTP communicator disconnected (config cleared).")
            self.config = None

    @property
    def is_open(self) -> bool:
        # (Questo metodo rimane invariato)
        return self.config is not None

    def send_scenario(self, scenario: Scenario) -> bool:
        """Generates a full initialization script (using tgtinit) and uploads it."""
        if not self.is_open:
            self.logger.error("Cannot send scenario: TFTP communicator not configured.")
            return False

        self.logger.info(f"Atomically sending scenario '{scenario.name}' via TFTP...")

        script_lines = [command_builder.build_pause(), command_builder.build_aclatch()]

        for target in scenario.get_all_targets():
            command = command_builder.build_tgtinit(target)
            script_lines.append(command)

        script_lines.extend(
            [command_builder.build_acunlatch(), command_builder.build_continue()]
        )

        script_content = "\n".join(script_lines) + "\n"

        self._save_script_to_temp(script_content, "scenario_init")

        return self._upload_content(script_content)

    def send_commands(self, commands: List[str]) -> bool:
        """Sends a batch of commands (e.g., multiple tgtset) for live updates."""
        if not self.is_open:
            self.logger.error("Batch command send failed: Communicator not configured.")
            return False

        if not commands:
            return True  # Nothing to send

        # Atomically wrap commands for safe application
        script_lines = [command_builder.build_pause(), command_builder.build_aclatch()]
        script_lines.extend(commands)
        script_lines.extend(
            [command_builder.build_acunlatch(), command_builder.build_continue()]
        )

        script_content = "\n".join(script_lines) + "\n"

        self._save_script_to_temp(script_content, "live_update")

        return self._upload_content(script_content, timeout=2)

    def send_command(self, command: str) -> bool:
        """Sends a single command by wrapping it in a list for the batch sender."""
        return self.send_commands([command])

    def _upload_content(self, content: str, timeout: int = 5) -> bool:
        """Helper method to upload a string content via TFTP."""
        in_memory_script = io.StringIO(content)
        try:
            client = TFTPClient(self.config["ip"], self.config["port"], timeout=timeout)
            success = client.upload(
                self.REMOTE_FILENAME, in_memory_script, mode="octet"
            )
            if not success:
                self.logger.warning(f"TFTP upload client returned False.")
            return success
        except TFTPError as e:
            self.logger.error(f"A TFTP error occurred during upload: {e}")
            return False
        except Exception as e:
            self.logger.error(
                f"An unexpected error during TFTP upload: {e}", exc_info=True
            )
            return False

    @staticmethod
    def test_connection(config: Dict[str, Any]) -> bool:
        """
        Tests TFTP connectivity by sending a RRQ (read request) for a random file.
        Logs the server response using the application's logger.
        If the server responds (even with error), it's reachable. Timeout = failure.
        """
        import socket
        import struct

        ip = config.get("ip")
        port = int(config.get("port", 69))
        logger = get_logger("TFTPCommunicator.test_connection")
        if not ip or not port:
            logger.error("Missing IP or port for TFTP test connection.")
            return False
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            sock.settimeout(3)
            # RRQ packet: opcode 1, filename, mode
            random_name = "".join(random.choices(string.ascii_lowercase, k=12))
            filename = f"{random_name}.tmp"
            mode = b"octet"
            rrq = struct.pack("!H", 1) + filename.encode("ascii") + b"\0" + mode + b"\0"
            logger.info(f"Sending TFTP RRQ to {ip}:{port} for file '{filename}'")
            sock.sendto(rrq, (ip, port))
            try:
                data, addr = sock.recvfrom(1024)
                logger.info(
                    f"Received response from {addr}: {data[:32].hex()} (first 32 bytes)"
                )
                if len(data) >= 2:
                    opcode = struct.unpack("!H", data[:2])[0]
                    logger.info(f"TFTP response opcode: {opcode}")
                    if opcode == 5 and len(data) >= 4:
                        error_code = struct.unpack("!H", data[2:4])[0]
                        # Messaggio di errore: da byte 4 fino al primo \0
                        error_msg = data[4:].split(b"\0", 1)[0].decode(errors="replace")
                        logger.error(f"TFTP Error {error_code}: {error_msg}")
                return True
            except socket.timeout:
                logger.warning("No response from TFTP server (timeout)")
                return False
            finally:
                sock.close()
        except Exception as e:
            logger.error(f"Exception during TFTP test connection: {e}")
            return False

    @staticmethod
    def list_available_ports() -> List[str]:
        """TFTP does not have discoverable ports like Serial, so return empty."""
        return []
