from __future__ import annotations

import os
from unittest.mock import MagicMock, patch
import pytest

from actions.pipeline import (
    handle_pipeline_run,
    _step_run_command,
    resolve_binary,
)


def test_command_length_limit():
    # command > 1000 chars should fail immediately
    long_cmd = "a" * 1001
    step = {"type": "run-command", "config": {"command": long_cmd}}
    res = _step_run_command(step, {})
    assert res["status"] == "failed"
    assert "exceeds maximum length" in res["output"]


@patch("subprocess.run")
def test_run_command_shlex_split_and_shell_false(mock_run):
    mock_run.return_value = MagicMock(returncode=0, stdout="hello", stderr="")
    
    step = {"type": "run-command", "config": {"command": "echo 'hello world'"}}
    res = _step_run_command(step, {"repo_path": "/var/www"})
    
    assert res["status"] == "succeeded"
    mock_run.assert_called_once()
    # The call should have shell=False, cwd="/var/www", and command arguments list parsed correctly
    args, kwargs = mock_run.call_args
    assert args[0] == [resolve_binary("echo"), "hello world"]
    assert kwargs["shell"] is False
    assert kwargs["cwd"] == "/var/www"


@patch("subprocess.run")
def test_run_command_shell_injection_protection(mock_run):
    mock_run.return_value = MagicMock(returncode=0, stdout="test", stderr="")
    
    # Passing a command with shell operators like ; or &&
    step = {"type": "run-command", "config": {"command": "echo; rm -rf /"}}
    res = _step_run_command(step, {})
    
    args, kwargs = mock_run.call_args
    # shlex.split should split it properly and it should be executed with shell=False
    assert args[0] == [resolve_binary("echo;"), "rm", "-rf", "/"]
    assert kwargs["shell"] is False


@patch("actions.pipeline.load_agent_token", return_value="dummy_token")
def test_pipeline_step_conditions_on_success(mock_load_token):
    config = MagicMock()
    api_client = MagicMock()
    
    # 3 steps: 
    # 1. failing step
    # 2. on-success step (should be skipped)
    # 3. always step (should run)
    steps = [
        {
            "type": "run-command",
            "label": "Fail Command",
            "config": {"command": "false"},
            "condition": {"type": "always"}
        },
        {
            "type": "run-command",
            "label": "On Success Step",
            "config": {"command": "echo 1"},
            "condition": {"type": "on-success"}
        },
        {
            "type": "run-command",
            "label": "Always Step",
            "config": {"command": "echo 2"},
            "condition": {"type": "always"}
        }
    ]
    
    payload = {
        "pipeline_id": 1,
        "run_id": 42,
        "steps": steps,
        "branch": "main",
        "repo_path": "/test"
    }

    # Mock subprocess.run to make step 1 fail, and step 3 succeed
    def mock_run_cmd(args, **kwargs):
        if "false" in args or resolve_binary("false") in args:
            return MagicMock(returncode=1, stdout="", stderr="error")
        return MagicMock(returncode=0, stdout="success", stderr="")

    with patch("subprocess.run", side_effect=mock_run_cmd):
        res = handle_pipeline_run(payload, config, api_client)
        
    assert res["success"] is False
    assert res["status"] == "failed"
    
    # Check what was reported to update_pipeline_run
    # We expect several updates
    assert api_client.update_pipeline_run.call_count >= 2
    
    # Get last update call logs
    last_call_args = api_client.update_pipeline_run.call_args_list[-1]
    logs = last_call_args[0][2]
    
    assert logs[0]["status"] == "failed"
    assert logs[1]["status"] == "skipped"
    assert logs[2]["status"] == "succeeded"


@patch("actions.pipeline.load_agent_token", return_value="dummy_token")
def test_pipeline_step_conditions_on_failure(mock_load_token):
    config = MagicMock()
    api_client = MagicMock()
    
    # 3 steps: 
    # 1. successful step
    # 2. on-failure step (should be skipped)
    # 3. always step (should run)
    steps = [
        {
            "type": "run-command",
            "label": "Success Command",
            "config": {"command": "true"},
            "condition": {"type": "always"}
        },
        {
            "type": "run-command",
            "label": "On Failure Step",
            "config": {"command": "echo 1"},
            "condition": {"type": "on-failure"}
        },
        {
            "type": "run-command",
            "label": "Always Step",
            "config": {"command": "echo 2"},
            "condition": {"type": "always"}
        }
    ]
    
    payload = {
        "pipeline_id": 1,
        "run_id": 43,
        "steps": steps,
        "branch": "main"
    }

    with patch("subprocess.run", return_value=MagicMock(returncode=0, stdout="", stderr="")):
        res = handle_pipeline_run(payload, config, api_client)
        
    assert res["success"] is True
    assert res["status"] == "succeeded"
    
    last_call_args = api_client.update_pipeline_run.call_args_list[-1]
    logs = last_call_args[0][2]
    
    assert logs[0]["status"] == "succeeded"
    assert logs[1]["status"] == "skipped"
    assert logs[2]["status"] == "succeeded"
