"""Tests for Makefile-based deploy node."""

from unittest.mock import Mock, patch

import pytest

from fwauto.nodes.deploy import deploy_node
from fwauto.state import FirmwareState

# Skip all tests in this file - deploy_node now uses Python SSH operations
# via deploy_operations.py instead of Makefile-based approach
pytestmark = pytest.mark.skip(reason="Deploy node now uses deploy_operations.py, not Makefiles")


@pytest.fixture
def base_state() -> FirmwareState:
    """Create a base state for testing."""
    return {
        "user_prompt": "test deploy",
        "project_root": "/test/project",
        "project_initialized": True,
        "build_status": "success",
        "build_errors": [],
        "build_log": "",
        "deploy_status": "pending",
        "deploy_errors": [],
        "deploy_log": "",
        "iteration": 0,
        "max_retries": 3,
        "execution_mode": "deploy",
        "command": "deploy",
        "command_args": {},
        "mode_metadata": None,
        "command_error": None,
        "log_file_path": None,
        "log_analysis_result": None,
        "firmware_path": "/test/project/OBJ/Template.hex",
    }


def test_deploy_node_not_initialized(base_state):
    """Test deploy_node returns error when project not initialized."""
    base_state["project_initialized"] = False

    result = deploy_node(base_state)

    assert result["deploy_status"] == "error"
    assert "not initialized" in result["deploy_errors"][0].lower()


@patch("fwauto.nodes.deploy.load_project_config")
@patch("fwauto.utils.subprocess.Popen")
def test_deploy_node_success(mock_popen, mock_load_config, base_state):
    """Test successful deploy with Makefile."""
    # Mock config
    mock_config = Mock()
    mock_config.deploy_makefile = ".fwauto/deploy/Makefile"
    mock_config.deploy_target = "deploy"
    mock_load_config.return_value = mock_config

    # Mock process
    mock_process = Mock()
    mock_process.returncode = 0
    mock_process.communicate.return_value = ("Flash successful\n", None)
    mock_popen.return_value = mock_process

    result = deploy_node(base_state)

    assert result["deploy_status"] == "success"
    assert "燒錄成功" in result["deploy_log"]


@patch("fwauto.nodes.deploy.load_project_config")
def test_deploy_node_config_not_found(mock_load_config, base_state):
    """Test deploy_node returns error when config.toml not found."""
    from fwauto.config import ConfigError

    mock_load_config.side_effect = ConfigError("Config file not found")

    result = deploy_node(base_state)

    assert result["deploy_status"] == "error"
    assert "Config error" in result["deploy_errors"][0]


@patch("fwauto.nodes.deploy.load_project_config")
@patch("fwauto.utils.subprocess.Popen")
def test_deploy_node_makefile_not_found(mock_popen, mock_load_config, base_state):
    """Test deploy_node returns error when Makefile not found."""
    # Mock config
    mock_config = Mock()
    mock_config.deploy_makefile = ".fwauto/deploy/Makefile"
    mock_config.deploy_target = "deploy"
    mock_load_config.return_value = mock_config

    # Mock Popen to raise FileNotFoundError
    mock_popen.side_effect = FileNotFoundError("make: not found")

    result = deploy_node(base_state)

    assert result["deploy_status"] == "error"
    assert "Flash exception" in result["deploy_errors"][0]


@patch("fwauto.nodes.deploy.load_project_config")
@patch("fwauto.utils.subprocess.Popen")
def test_deploy_node_hex_file_not_in_state(mock_popen, mock_load_config, base_state):
    """Test deploy_node succeeds even without firmware_path (Makefile handles it)."""
    base_state["firmware_path"] = None

    # Mock config
    mock_config = Mock()
    mock_config.deploy_makefile = ".fwauto/deploy/Makefile"
    mock_config.deploy_target = "deploy"
    mock_load_config.return_value = mock_config

    # Mock process success (Makefile can handle finding HEX file itself)
    mock_process = Mock()
    mock_process.returncode = 0
    mock_process.communicate.return_value = ("Flash successful\n", None)
    mock_popen.return_value = mock_process

    result = deploy_node(base_state)

    # Current implementation doesn't validate firmware_path before flashing
    assert result["deploy_status"] == "success"


@patch("fwauto.nodes.deploy.load_project_config")
@patch("fwauto.utils.subprocess.Popen")
def test_deploy_node_hex_file_does_not_exist(mock_popen, mock_load_config, base_state):
    """Test deploy_node handles HEX file errors from Makefile."""
    # Mock config
    mock_config = Mock()
    mock_config.deploy_makefile = ".fwauto/deploy/Makefile"
    mock_config.deploy_target = "deploy"
    mock_load_config.return_value = mock_config

    # Mock process failure (Makefile reports HEX file not found)
    mock_process = Mock()
    mock_process.returncode = 2
    mock_process.communicate.return_value = ("HEX file not found\n", None)
    mock_popen.return_value = mock_process

    result = deploy_node(base_state)

    assert result["deploy_status"] == "error"
    assert "Flash failed with exit code 2" in result["deploy_errors"][0]


@patch("fwauto.nodes.deploy.load_project_config")
@patch("fwauto.utils.subprocess.Popen")
def test_deploy_node_deploy_command_failed(mock_popen, mock_load_config, base_state):
    """Test deploy_node handles flash command failure."""
    # Mock config
    mock_config = Mock()
    mock_config.deploy_makefile = ".fwauto/deploy/Makefile"
    mock_config.deploy_target = "deploy"
    mock_load_config.return_value = mock_config

    # Mock process failure
    mock_process = Mock()
    mock_process.returncode = 1
    mock_process.communicate.return_value = ("Flash error\n", None)
    mock_popen.return_value = mock_process

    result = deploy_node(base_state)

    assert result["deploy_status"] == "error"
    assert "Flash failed with exit code 1" in result["deploy_errors"][0]


@patch("fwauto.nodes.deploy.load_project_config")
@patch("fwauto.utils.subprocess.Popen")
def test_deploy_node_with_binary_args(mock_popen, mock_load_config, base_state):
    """Test deploy_node passes binary_args via environment variables."""
    # Add binary_args to state
    base_state["command_args"] = {"binary_args": "on"}

    # Mock config
    mock_config = Mock()
    mock_config.deploy_makefile = ".fwauto/deploy/Makefile"
    mock_config.deploy_target = "deploy"
    mock_config.board_ip = "192.168.1.100"
    mock_config.board_user = "root"
    mock_config.deploy_path = "/home/root"
    mock_config.ssh_options = "-o ConnectTimeout=5"
    mock_load_config.return_value = mock_config

    # Mock process success
    mock_process = Mock()
    mock_process.returncode = 0
    mock_process.communicate.return_value = ("Flash successful\n", None)
    mock_popen.return_value = mock_process

    result = deploy_node(base_state)

    assert result["deploy_status"] == "success"
    # Verify Popen was called with env containing BINARY_ARGS
    call_kwargs = mock_popen.call_args[1]
    assert "env" in call_kwargs
    assert call_kwargs["env"]["BINARY_ARGS"] == "on"


@patch("fwauto.nodes.deploy.load_project_config")
def test_deploy_node_rejects_dangerous_binary_args(mock_load_config, base_state):
    """Test deploy_node rejects binary_args with shell metacharacters."""
    # Mock config (won't be used since validation happens first)
    mock_config = Mock()
    mock_config.deploy_makefile = ".fwauto/deploy/Makefile"
    mock_config.deploy_target = "deploy"
    mock_load_config.return_value = mock_config

    dangerous_args = [
        "arg1; rm -rf /",
        "arg1 | cat /etc/passwd",
        "arg1 && malicious_cmd",
        "arg1 $MALICIOUS",
        "arg1 `cat /etc/passwd`",
    ]

    for dangerous_arg in dangerous_args:
        base_state["command_args"] = {"binary_args": dangerous_arg}

        result = deploy_node(base_state)

        assert result["deploy_status"] == "error"
        assert "dangerous" in result["deploy_errors"][0].lower()


@patch("fwauto.nodes.deploy.load_project_config")
@patch("fwauto.utils.subprocess.Popen")
def test_deploy_node_with_empty_binary_args(mock_popen, mock_load_config, base_state):
    """Test deploy_node handles empty binary_args correctly."""
    base_state["command_args"] = {"binary_args": ""}

    # Mock config
    mock_config = Mock()
    mock_config.deploy_makefile = ".fwauto/deploy/Makefile"
    mock_config.deploy_target = "deploy"
    mock_config.board_ip = "192.168.1.100"
    mock_config.board_user = "root"
    mock_config.deploy_path = "/home/root"
    mock_config.ssh_options = "-o ConnectTimeout=5"
    mock_load_config.return_value = mock_config

    # Mock process success
    mock_process = Mock()
    mock_process.returncode = 0
    mock_process.communicate.return_value = ("Flash successful\n", None)
    mock_popen.return_value = mock_process

    result = deploy_node(base_state)

    assert result["deploy_status"] == "success"
    # Verify BINARY_ARGS is not in env when empty
    call_kwargs = mock_popen.call_args[1]
    assert "BINARY_ARGS" not in call_kwargs["env"]
