from __future__ import annotations

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

import actions.nginx
from actions.nginx import (
    handle_nginx_config_read,
    handle_nginx_config_write,
    handle_nginx_service_reload,
)


@pytest.fixture(autouse=True)
def setup_temp_nginx_base(tmp_path):
    """Override NGINX_BASE_DIR with a temporary folder and create standard directories."""
    original_base = actions.nginx.NGINX_BASE_DIR
    
    # Set to temp path
    actions.nginx.NGINX_BASE_DIR = str(tmp_path)
    
    # Create allowed directories
    os.makedirs(os.path.join(str(tmp_path), "sites-available"), exist_ok=True)
    os.makedirs(os.path.join(str(tmp_path), "conf.d"), exist_ok=True)
    
    yield tmp_path
    
    # Restore original base dir
    actions.nginx.NGINX_BASE_DIR = original_base


def test_verify_safe_path_blocks_path_traversal(tmp_path):
    # Path traversal going outside NGINX_BASE_DIR
    traversal_path = os.path.join(str(tmp_path), "..", "outside.conf")
    
    res = handle_nginx_config_read({"config_path": traversal_path})
    assert res["success"] is False
    assert "outside of Nginx config directory" in res["error"]

    res_write = handle_nginx_config_write({"config_path": traversal_path, "content": "test"})
    assert res_write["success"] is False
    assert "outside of Nginx config directory" in res_write["error"]


def test_write_blocks_invalid_directories(tmp_path):
    # Valid base directory, but not in sites-available or conf.d
    invalid_dir_path = os.path.join(str(tmp_path), "forbidden.conf")
    
    res = handle_nginx_config_write({"config_path": invalid_dir_path, "content": "test"})
    assert res["success"] is False
    assert "Directory is not allowed" in res["error"]


def test_read_and_write_respect_512kb_limit(tmp_path):
    valid_path = os.path.join(str(tmp_path), "conf.d", "test.conf")
    huge_content = "a" * (512 * 1024 + 1)
    
    # Test write limit
    res_write = handle_nginx_config_write({"config_path": valid_path, "content": huge_content})
    assert res_write["success"] is False
    assert "exceeds maximum size of 512KB" in res_write["error"]
    
    # Write normal file manually
    with open(valid_path, "w", encoding="utf-8") as f:
        f.write("test")
        
    # Mock size check to return a huge size
    with patch("os.path.getsize", return_value=512 * 1024 + 10):
        res_read = handle_nginx_config_read({"config_path": valid_path})
        assert res_read["success"] is False
        assert "exceeds maximum size of 512KB" in res_read["error"]


@patch("subprocess.run")
def test_write_config_success(mock_run, tmp_path):
    # Mock nginx -t to succeed
    mock_run.return_value = MagicMock(returncode=0, stdout="syntax ok", stderr="")
    
    valid_path = os.path.join(str(tmp_path), "conf.d", "site.conf")
    content = "server { listen 80; }"
    
    res = handle_nginx_config_write({"config_path": valid_path, "content": content})
    
    assert res["success"] is True
    assert os.path.exists(valid_path)
    with open(valid_path, "r") as f:
        assert f.read() == content
        
    # Check nginx -t was run
    mock_run.assert_called_once_with(
        ["nginx", "-t", "-c", "/etc/nginx/nginx.conf"],
        capture_output=True,
        text=True,
        shell=False,
        check=False
    )


@patch("subprocess.run")
def test_write_config_test_fails_and_restores(mock_run, tmp_path):
    # Mock nginx -t to fail
    mock_run.return_value = MagicMock(returncode=1, stdout="", stderr="nginx: [emerg] invalid directive")
    
    valid_path = os.path.join(str(tmp_path), "conf.d", "site.conf")
    
    # Create original file
    original_content = "server { listen 80; # original }"
    with open(valid_path, "w", encoding="utf-8") as f:
        f.write(original_content)
        
    new_invalid_content = "server { invalid_directive }"
    res = handle_nginx_config_write({"config_path": valid_path, "content": new_invalid_content})
    
    assert res["success"] is False
    assert res["error"] == "nginx_test_failed"
    assert "invalid directive" in res["nginx_output"]
    
    # Assert the original file was restored
    with open(valid_path, "r") as f:
        assert f.read() == original_content
        
    # Verify backup exists
    files_in_dir = os.listdir(os.path.dirname(valid_path))
    backup_files = [f for f in files_in_dir if ".wolfpanel.bak." in f]
    assert len(backup_files) == 1
    
    # Verify tmp file is deleted
    assert not os.path.exists(valid_path + ".tmp")
    assert not os.path.exists(valid_path + ".orig_temp")


@patch("subprocess.run")
def test_service_reload_success(mock_run):
    mock_run.return_value = MagicMock(returncode=0, stdout="reloaded", stderr="")
    
    res = handle_nginx_service_reload({})
    assert res["success"] is True
    assert res["reloaded"] is True
    assert "reloaded" in res["output"]


@patch("subprocess.run")
def test_service_reload_failure(mock_run):
    # Mock reload failed (first call), then status report (second call)
    mock_reload = MagicMock(returncode=1, stdout="", stderr="reload error")
    mock_status = MagicMock(returncode=0, stdout="inactive", stderr="")
    
    mock_run.side_effect = [mock_reload, mock_status]
    
    res = handle_nginx_service_reload({})
    assert res["success"] is False
    assert res["reloaded"] is False
    assert "reload error" in res["output"]
    assert "inactive" in res["output"]
