import sys
from pathlib import Path

# Add src to python path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))

from actions.cron import handle_cron_create, handle_cron_delete
import actions.cron
import pytest
from unittest.mock import MagicMock, patch


def test_cron_create_success(tmp_path):
    # Mock read_crontab to return an existing crontab
    existing_crontab = "* * * * * original_command\n"

    with patch("actions.cron.read_crontab", return_value=existing_crontab) as mock_read, \
         patch("actions.cron.write_crontab") as mock_write:

        payload = {
            "schedule": "*/5 * * * *",
            "command": "echo 1",
            "comment": "wolfpanel-managed:cmd_123"
        }
        res = handle_cron_create(payload, backup_dir=tmp_path)

        assert res["success"] is True
        assert res["cron_id"] == "cmd_123"

        # Verify crontab read/write was called
        mock_read.assert_called_once()

        # Verify write_crontab payload
        expected_new_crontab = "* * * * * original_command\n*/5 * * * * echo 1 # wolfpanel:cmd_123\n"
        mock_write.assert_called_once_with(expected_new_crontab)

        # Verify backup was taken
        backups = list(tmp_path.glob("crontab_*.bak"))
        assert len(backups) == 1
        assert backups[0].read_text(encoding="utf-8") == existing_crontab


def test_cron_delete_success(tmp_path):
    existing_crontab = (
        "* * * * * original_command\n"
        "*/5 * * * * echo 1 # wolfpanel:cmd_123\n"
        "1 2 3 4 5 other_command\n"
    )

    with patch("actions.cron.read_crontab", return_value=existing_crontab) as mock_read, \
         patch("actions.cron.write_crontab") as mock_write:

        payload = {"command_id": "cmd_123"}
        res = handle_cron_delete(payload, backup_dir=tmp_path)

        assert res["success"] is True
        assert res["cron_id"] == "cmd_123"

        expected_new_crontab = (
            "* * * * * original_command\n"
            "1 2 3 4 5 other_command\n"
        )
        mock_write.assert_called_once_with(expected_new_crontab)

        # Verify backup taken
        backups = list(tmp_path.glob("crontab_*.bak"))
        assert len(backups) == 1
        assert backups[0].read_text(encoding="utf-8") == existing_crontab


def test_cron_delete_separate_comment_line(tmp_path):
    existing_crontab = (
        "* * * * * original_command\n"
        "# wolfpanel:cmd_123\n"
        "*/5 * * * * echo 1\n"
        "1 2 3 4 5 other_command\n"
    )

    with patch("actions.cron.read_crontab", return_value=existing_crontab) as mock_read, \
         patch("actions.cron.write_crontab") as mock_write:

        payload = {"command_id": "cmd_123"}
        res = handle_cron_delete(payload, backup_dir=tmp_path)

        assert res["success"] is True

        expected_new_crontab = (
            "* * * * * original_command\n"
            "1 2 3 4 5 other_command\n"
        )
        mock_write.assert_called_once_with(expected_new_crontab)


def test_cron_delete_not_found(tmp_path):
    existing_crontab = "* * * * * original_command\n"

    with patch("actions.cron.read_crontab", return_value=existing_crontab) as mock_read, \
         patch("actions.cron.write_crontab") as mock_write:

        payload = {"command_id": "nonexistent"}
        res = handle_cron_delete(payload, backup_dir=tmp_path)

        assert res["success"] is False
        assert "not found" in res["error"]
        mock_write.assert_not_called()


def test_cron_create_validation_newline(tmp_path):
    payload = {
        "schedule": "* * * * *\n",
        "command": "echo 1",
        "comment": "wolfpanel-managed:123"
    }
    res = handle_cron_create(payload, backup_dir=tmp_path)
    assert res["success"] is False
    assert "Newline" in res["error"]

    payload_cmd = {
        "schedule": "* * * * *",
        "command": "echo 1\nrm -rf /",
        "comment": "wolfpanel-managed:123"
    }
    res_cmd = handle_cron_create(payload_cmd, backup_dir=tmp_path)
    assert res_cmd["success"] is False
    assert "Newline" in res_cmd["error"]


def test_cron_create_validation_hash(tmp_path):
    payload = {
        "schedule": "* * * * *",
        "command": "echo 1 # try to break comment",
        "comment": "wolfpanel-managed:123"
    }
    res = handle_cron_create(payload, backup_dir=tmp_path)
    assert res["success"] is False
    assert "Hash character" in res["error"]


# Command Runner Tests

from command_runner import process_commands


def test_command_runner_dispatches_successfully(tmp_path):
    # Mock Config
    config = MagicMock()
    config.status = "online"
    config.server_id = "1"

    # Mock ApiClient
    api_client = MagicMock()
    api_client.get_pending_commands.return_value = [
        {"id": 1, "command_type": "cron_job.create", "payload": {"schedule": "* * * * *", "command": "echo 1", "comment": "wolfpanel-managed:1"}},
        {"id": 2, "command_type": "invalid.type", "payload": {}}
    ]

    mock_create = MagicMock(return_value={"success": True, "message": "ok"})
    mock_handlers = {
        "cron_job.create": mock_create,
        "cron_job.delete": MagicMock()
    }

    # Mock load_agent_token
    with patch("command_runner.load_agent_token", return_value="token"), \
         patch("command_runner.HANDLERS", mock_handlers):

        process_commands(config, api_client)

        # Verification
        mock_create.assert_called_once_with({"schedule": "* * * * *", "command": "echo 1", "comment": "wolfpanel-managed:1"})

        # api_client.post_command_result should be called twice
        # First command should succeed
        api_client.post_command_result.assert_any_call(
            1,
            {"status": "succeeded", "result": {"success": True, "message": "ok"}},
            "token"
        )
        # Second command (invalid type) should fail
        api_client.post_command_result.assert_any_call(
            2,
            {"status": "failed", "result": {"error": "Unsupported command type: invalid.type"}},
            "token"
        )


if __name__ == "__main__":
    import traceback
    funcs = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
    failures = 0
    for func in funcs:
        try:
            # Provide a tmp_path for the tests that need it.
            if "tmp_path" in func.__code__.co_varnames:
                func(Path("/tmp"))
            else:
                func()
            print(f"PASS {func.__name__}")
        except Exception:  # noqa: BLE001
            failures += 1
            print(f"FAIL {func.__name__}")
            traceback.print_exc()
    sys.exit(1 if failures else 0)
