"""
Firmware Updater for Somewear Nodes

Updates firmware on Somewear nodes via USB using newtmgr.
"""
import os
import subprocess
from typing import Optional
from dataclasses import dataclass

from celilo.usb_node_detector import USBNodeDetector, SomewearNode


@dataclass
class FirmwareUpdateResult:
    """Result of a firmware update operation."""
    success: bool
    message: str
    node: Optional[SomewearNode] = None
    upload_output: Optional[str] = None
    reset_output: Optional[str] = None


class FirmwareUpdater:
    """Handles firmware updates for Somewear nodes."""

    def __init__(self, mtu: int = 4096):
        """
        Initialize the firmware updater.

        Args:
            mtu: MTU size for newtmgr serial connection (default: 4096)
        """
        self.mtu = mtu
        self.detector = USBNodeDetector()

    def update_firmware(
        self,
        firmware_path: str,
        node: Optional[SomewearNode] = None
    ) -> FirmwareUpdateResult:
        """
        Update firmware on a Somewear node.

        Args:
            firmware_path: Path to the firmware binary file
            node: Specific node to update (if None, will auto-select)

        Returns:
            FirmwareUpdateResult with status and details
        """
        # Validate firmware file
        if not os.path.exists(firmware_path):
            return FirmwareUpdateResult(
                success=False,
                message=f"Firmware file not found: {firmware_path}"
            )

        # Get node if not provided
        if node is None:
            node = self._select_node()
            if node is None:
                return FirmwareUpdateResult(
                    success=False,
                    message="No Somewear nodes detected or selected"
                )

        # Upload firmware
        upload_result = self._upload_firmware(firmware_path, node)
        if not upload_result['success']:
            return FirmwareUpdateResult(
                success=False,
                message=f"Firmware upload failed: {upload_result['error']}",
                node=node,
                upload_output=upload_result.get('output')
            )

        # Reset node
        reset_result = self._reset_node(node)

        return FirmwareUpdateResult(
            success=True,
            message="Firmware update completed successfully",
            node=node,
            upload_output=upload_result.get('output'),
            reset_output=reset_result.get('output')
        )

    def _select_node(self) -> Optional[SomewearNode]:
        """
        Detect and select a node for firmware update.

        Returns:
            Selected SomewearNode or None if no nodes found
        """
        nodes = self.detector.detect_nodes()

        if not nodes:
            return None

        # If only one node, return it
        if len(nodes) == 1:
            return nodes[0]

        # Multiple nodes - return the first one for now
        # The caller can handle multiple node selection via fzf
        return nodes[0]

    def _build_connection_string(self, node: SomewearNode) -> str:
        """
        Build newtmgr connection string from node.

        Args:
            node: The SomewearNode to connect to

        Returns:
            Connection string for newtmgr (e.g., "/dev/cu.usbmodem2143101,mtu=4096")
        """
        return f"{node.device_path},mtu={self.mtu}"

    def _upload_firmware(
        self,
        firmware_path: str,
        node: SomewearNode
    ) -> dict:
        """
        Upload firmware to the node using newtmgr.

        Args:
            firmware_path: Path to firmware binary
            node: Target node

        Returns:
            Dictionary with 'success' status and 'output' or 'error'
        """
        connstring = self._build_connection_string(node)

        cmd = [
            'newtmgr',
            '--conntype', 'serial',
            '--connstring', connstring,
            'image', 'upload',
            firmware_path
        ]

        print("Executing command:", ' '.join(cmd))

        try:
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=300  # 5 minute timeout for upload
            )

            if result.returncode != 0:
                return {
                    'success': False,
                    'error': result.stderr or result.stdout,
                    'output': result.stdout
                }

            return {
                'success': True,
                'output': result.stdout
            }

        except FileNotFoundError:
            return {
                'success': False,
                'error': "newtmgr command not found. Please install newtmgr."
            }
        except subprocess.TimeoutExpired:
            return {
                'success': False,
                'error': "Firmware upload timed out after 5 minutes"
            }
        except Exception as e:
            return {
                'success': False,
                'error': str(e)
            }

    def _reset_node(self, node: SomewearNode) -> dict:
        """
        Reset the node using newtmgr.

        Args:
            node: Node to reset

        Returns:
            Dictionary with 'success' status and 'output' or 'error'
        """
        cmd = [
            'newtmgr',
            '--conntype', 'serial',
            '--connstring', node.device_path,
            'reset'
        ]

        try:
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=30
            )

            if result.returncode != 0:
                return {
                    'success': False,
                    'error': result.stderr or result.stdout,
                    'output': result.stdout
                }

            return {
                'success': True,
                'output': result.stdout
            }

        except FileNotFoundError:
            return {
                'success': False,
                'error': "newtmgr command not found"
            }
        except subprocess.TimeoutExpired:
            return {
                'success': False,
                'error': "Node reset timed out"
            }
        except Exception as e:
            return {
                'success': False,
                'error': str(e)
            }

    def get_available_nodes(self):
        """
        Get list of available Somewear nodes.

        Returns:
            List of SomewearNode objects
        """
        return self.detector.detect_nodes()
