import pytest
import os
import json
import base64
import yaml
import sys
import tempfile
import shutil
from unittest.mock import mock_open, patch, MagicMock
from generate_prometheus_config_yaml import (
    merge_yaml,
    split_credentials,
    validate_file_path,
    validate_directory_path,
    validate_input_data,
    ConfigError
)

def test_merge_yaml_basic():
    base = {"key1": "value1", "key2": "value2"}
    overlay = {"key2": "new_value2", "key3": "value3"}
    result = merge_yaml(base, overlay)
    assert result["key1"] == "value1"
    assert result["key2"] == "new_value2"
    assert result["key3"] == "value3"

def test_merge_yaml_nested():
    base = {"parent": {"child1": "value1", "child2": "value2"}}
    overlay = {"parent": {"child2": "new_value2", "child3": "value3"}}
    result = merge_yaml(base, overlay)
    assert result["parent"]["child1"] == "value1"
    assert result["parent"]["child2"] == "new_value2"
    assert result["parent"]["child3"] == "value3"

def test_merge_yaml_invalid_input():
    with pytest.raises(ConfigError):
        merge_yaml("not_a_dict", {"key": "value"})
    with pytest.raises(ConfigError):
        merge_yaml({"key": "value"}, "not_a_dict")

def test_split_credentials_mock():
    # Create a dictionary to store file contents
    file_contents = {}
    current_filename = None

    def mock_open_file(filename, mode='r', *args, **kwargs):
        nonlocal current_filename
        current_filename = filename
        return mock_open_instance.return_value

    def mock_write(content):
        # Store the content in our dictionary using the current filename
        file_contents[current_filename] = content

    # Set up the mock
    mock_open_instance = mock_open()
    mock_open_instance.side_effect = mock_open_file
    mock_open_instance.return_value.__enter__.return_value.write.side_effect = mock_write
    mock_open_instance.return_value.__enter__.return_value.read.return_value = 'username="testuser",password="testpass"'

    with patch("builtins.open", mock_open_instance):
        with patch("os.path.join", side_effect=lambda dir, file: f"{dir}/{file}"):
            with patch("os.chmod"):  # Mock chmod to avoid permission issues
                split_credentials("dummy_path", "dummy_dir")
                
                # Verify that both username and password files were written with correct content
                assert "dummy_dir/alertmanager-client-cred-username" in file_contents
                assert "dummy_dir/alertmanager-client-cred-password" in file_contents
                assert file_contents["dummy_dir/alertmanager-client-cred-username"] == "testuser"
                assert file_contents["dummy_dir/alertmanager-client-cred-password"] == "testpass"

def test_split_credentials_with_quotes():
    # Create a temporary directory
    test_dir = tempfile.mkdtemp()
    try:
        # Test with quoted values
        credentials_content = 'username="test-user" password="test-pass"'
        credentials_path = os.path.join(test_dir, 'credentials.txt')
        output_dir = os.path.join(test_dir, 'output')
        
        with open(credentials_path, 'w') as f:
            f.write(credentials_content)
            
        split_credentials(credentials_path, output_dir)
        
        # Verify username file
        username_path = os.path.join(output_dir, 'alertmanager-client-cred-username')
        assert os.path.exists(username_path)
        with open(username_path, 'r') as f:
            username = f.read()
        assert username == 'test-user'
        
        # Verify password file
        password_path = os.path.join(output_dir, 'alertmanager-client-cred-password')
        assert os.path.exists(password_path)
        with open(password_path, 'r') as f:
            password = f.read()
        assert password == 'test-pass'
    finally:
        shutil.rmtree(test_dir)

def test_split_credentials_without_quotes():
    # Create a temporary directory
    test_dir = tempfile.mkdtemp()
    try:
        # Test with unquoted values
        credentials_content = 'username=test-user password=test-pass'
        credentials_path = os.path.join(test_dir, 'credentials.txt')
        output_dir = os.path.join(test_dir, 'output')
        
        with open(credentials_path, 'w') as f:
            f.write(credentials_content)
            
        split_credentials(credentials_path, output_dir)
        
        # Verify username file
        username_path = os.path.join(output_dir, 'alertmanager-client-cred-username')
        assert os.path.exists(username_path)
        with open(username_path, 'r') as f:
            username = f.read()
        assert username == 'test-user'
        
        # Verify password file
        password_path = os.path.join(output_dir, 'alertmanager-client-cred-password')
        assert os.path.exists(password_path)
        with open(password_path, 'r') as f:
            password = f.read()
        assert password == 'test-pass'
    finally:
        shutil.rmtree(test_dir)

def test_split_credentials_invalid_format():
    with patch("builtins.open", mock_open(read_data="invalid_format")):
        with pytest.raises(ConfigError):
            split_credentials("dummy_path", "dummy_dir")

def test_split_credentials_empty_values():
    with patch("builtins.open", mock_open(read_data='username="",password=""')):
        with pytest.raises(ConfigError):
            split_credentials("dummy_path", "dummy_dir")

def test_split_credentials_directory_creation():
    # Create a temporary directory
    test_dir = tempfile.mkdtemp()
    try:
        # Test that directory is created if it doesn't exist
        credentials_content = 'username=test-user password=test-pass'
        credentials_path = os.path.join(test_dir, 'credentials.txt')
        output_dir = os.path.join(test_dir, 'non_existent_dir')
        
        # Verify directory doesn't exist initially
        assert not os.path.exists(output_dir)
        
        with open(credentials_path, 'w') as f:
            f.write(credentials_content)
            
        split_credentials(credentials_path, output_dir)
        
        # Verify directory was created
        assert os.path.exists(output_dir)
        
        # Verify files were created in the new directory
        username_path = os.path.join(output_dir, 'alertmanager-client-cred-username')
        password_path = os.path.join(output_dir, 'alertmanager-client-cred-password')
        assert os.path.exists(username_path)
        assert os.path.exists(password_path)
    finally:
        shutil.rmtree(test_dir)

def test_validate_file_path():
    with patch("os.path.exists", return_value=True):
        with patch("os.access", return_value=True):
            validate_file_path("dummy_path", "test")
    with patch("os.path.exists", return_value=False):
        with pytest.raises(ConfigError):
            validate_file_path("dummy_path", "test")
    with patch("os.path.exists", return_value=True):
        with patch("os.access", return_value=False):
            with pytest.raises(ConfigError):
                validate_file_path("dummy_path", "test")

def test_validate_directory_path():
    with patch("os.path.exists", return_value=True):
        with patch("os.access", return_value=True):
            validate_directory_path("dummy_path", "test")
    with patch("os.path.exists", return_value=False):
        with patch("os.makedirs"):
            with patch("os.access", return_value=True):
                validate_directory_path("dummy_path", "test")
    with patch("os.path.exists", return_value=True):
        with patch("os.access", return_value=False):
            with pytest.raises(ConfigError):
                validate_directory_path("dummy_path", "test")

def test_validate_input_data():
    valid_data = {
        "Overlay": "base64string",
        "ClientCredentialsPath": "/path/to/creds",
        "InitialConfigPath": "/path/to/config",
        "FinalClientCredentialsDir": "/path/to/output",
        "FinalConfigPath": "/path/to/final"
    }
    validate_input_data(valid_data)

    with pytest.raises(ConfigError):
        validate_input_data({})

    with pytest.raises(ConfigError):
        validate_input_data({
            "Overlay": "",
            "ClientCredentialsPath": "/path/to/creds",
            "InitialConfigPath": "/path/to/config",
            "FinalClientCredentialsDir": "/path/to/output",
            "FinalConfigPath": "/path/to/final"
        })

def test_main_success():
    input_data = {
        "Overlay": base64.b64encode(yaml.dump({"key": "value"}).encode()).decode(),
        "ClientCredentialsPath": "/path/to/creds",
        "InitialConfigPath": "/path/to/config",
        "FinalClientCredentialsDir": "/path/to/output",
        "FinalConfigPath": "/path/to/final"
    }

    mock_file = MagicMock()
    mock_file.read.side_effect = [
        json.dumps(input_data),  # First read for input JSON
        'username="testuser",password="testpass"'  # Second read for credentials
    ]
    mock_open_instance = mock_open()
    mock_open_instance.return_value.__enter__.return_value = mock_file

    with patch("builtins.open", mock_open_instance):
        with patch("os.path.exists", return_value=True):
            with patch("os.access", return_value=True):
                with patch("os.makedirs"):
                    with patch("shutil.copy2"):
                        with patch("yaml.safe_load", return_value={"key": "value"}):
                            with patch("yaml.dump"):
                                with patch("os.chmod"):  # Mock chmod to avoid permission issues
                                    with patch.object(sys, 'argv', ['script.py', 'input.json']):
                                        from generate_prometheus_config_yaml import main
                                        main()

def test_main_invalid_json():
    with patch.object(sys, 'argv', ['script.py', 'input.json']):
        with patch("builtins.open", mock_open(read_data="invalid json")):
            with pytest.raises(SystemExit) as exc_info:
                from generate_prometheus_config_yaml import main
                main()
            assert exc_info.value.code == 1

def test_main_invalid_base64():
    input_data = {
        "Overlay": "invalid_base64",  # Invalid base64 string
        "ClientCredentialsPath": "/path/to/creds",
        "InitialConfigPath": "/path/to/config",
        "FinalClientCredentialsDir": "/path/to/output",
        "FinalConfigPath": "/path/to/final"
    }

    with patch.object(sys, 'argv', ['script.py', 'input.json']):
        with patch("builtins.open", mock_open(read_data=json.dumps(input_data))):
            with pytest.raises(SystemExit) as exc_info:
                from generate_prometheus_config_yaml import main
                main()
            assert exc_info.value.code == 1

def test_main_invalid_yaml():
    input_data = {
        "Overlay": base64.b64encode(b"invalid: yaml: content").decode(),
        "ClientCredentialsPath": "/path/to/creds",
        "InitialConfigPath": "/path/to/config",
        "FinalClientCredentialsDir": "/path/to/output",
        "FinalConfigPath": "/path/to/final"
    }

    with patch.object(sys, 'argv', ['script.py', 'input.json']):
        with patch("builtins.open", mock_open(read_data=json.dumps(input_data))):
            with pytest.raises(SystemExit) as exc_info:
                from generate_prometheus_config_yaml import main
                main()
            assert exc_info.value.code == 1 