import pytest
from unittest.mock import mock_open, patch
import json
import os
from generate_web_config_yaml import (
    _generate_config_data,
    generate_configs,
    _read_user_passwords,
    _process_tls_config
)
import tempfile

def test_generate_configs_valid_input():
    # Mock the file operations and bcrypt
    mock_json_data = {
        "prometheusWebConfigGenerator": {
            "ServerCertPath": "/path/to/server.crt",
            "ServerKeyPath": "/path/to/server.key",
            "ClientCaCertPath": "/path/to/client-ca.crt",
            "UserPasswordFilePath": "user_passwords.txt"
        },
        "alertmanagerWebConfigGenerator": {
            "ServerCertPath": "/path/to/am-server.crt",
            "ServerKeyPath": "/path/to/am-server.key",
            "UserPasswordFilePath": "am_user_passwords.txt"
        }
    }
    mock_user_pass_content = "user1:password1\nuser2:password2\n"
    mock_am_user_pass_content = "am_user1:password1\nam_user2:password2\n"

    # Create a mock that returns different content based on the file being opened
    mock_files = {
        "mock_config.json": json.dumps(mock_json_data),
        "user_passwords.txt": mock_user_pass_content,
        "am_user_passwords.txt": mock_am_user_pass_content
    }

    def mock_open_func(filename, mode="r"):
        content = mock_files.get(filename, "")
        return mock_open(read_data=content)()

    with patch("builtins.open", side_effect=mock_open_func) as m_open, \
            patch("bcrypt.hashpw") as m_bcrypt_hashpw, \
            patch("yaml.dump") as m_yaml_dump, \
            patch("os.makedirs") as m_makedirs:

        m_bcrypt_hashpw.return_value = b"hashed_password"

        result = generate_configs("mock_config.json", "mock_output_dir")

        assert result is True
        assert m_bcrypt_hashpw.called
        assert m_yaml_dump.call_count == 2  # Called for both prometheus and alertmanager configs
        assert m_makedirs.called_with("mock_output_dir", exist_ok=True)


def test__generate_config_data_valid_input():
    mock_json_data = {
        "prometheusWebConfigGenerator": {
            "ServerCertPath": "/path/to/server.crt",
            "ServerKeyPath": "/path/to/server.key",
            "ClientCaCertPath": "/path/to/client-ca.crt",
            "UserPasswordFilePath": "user_passwords.txt"
        }
    }
    mock_user_pass_content = "user1:password1\nuser2:password2\n"

    with patch("builtins.open", mock_open(read_data=mock_user_pass_content)) as m_userpass, \
            patch("bcrypt.hashpw") as m_bcrypt_hashpw:

        m_bcrypt_hashpw.return_value = b"hashed_password"

        result = _generate_config_data(mock_json_data, "prometheus")
        assert result is not None
        assert "tls_server_config" in result
        assert "basic_auth_users" in result
        assert m_bcrypt_hashpw.called


def test__generate_config_data_partial_tls_config():
    mock_json_data = {
        "prometheusWebConfigGenerator": {
            "ServerCertPath": "/path/to/server.crt",
            "ServerKeyPath": "/path/to/server.key"
        }
    }

    result = _generate_config_data(mock_json_data, "prometheus")
    assert result is not None
    assert "tls_server_config" in result
    assert "cert_file" in result["tls_server_config"]
    assert "key_file" in result["tls_server_config"]
    assert "client_ca_file" not in result["tls_server_config"]
    assert "basic_auth_users" not in result


def test__generate_config_data_no_auth_users():
    mock_json_data = {
        "prometheusWebConfigGenerator": {
            "ServerCertPath": "/path/to/server.crt",
            "ServerKeyPath": "/path/to/server.key"
        }
    }

    result = _generate_config_data(mock_json_data, "prometheus")
    assert result is not None
    assert "tls_server_config" in result
    assert "basic_auth_users" not in result


def test_generate_configs_file_not_found():
    with patch("builtins.open", side_effect=FileNotFoundError):
        result = generate_configs("nonexistent_config.json", "mock_output_dir")
        assert result is False


def test_generate_configs_invalid_json():
    with patch("builtins.open", side_effect=json.JSONDecodeError("Invalid JSON", "", 0)):
        result = generate_configs("invalid_config.json", "mock_output_dir")
        assert result is False


def test__generate_config_data_missing_cert_or_key():
    # Test missing cert file
    mock_json_data = {
        "prometheusWebConfigGenerator": {
            "ServerKeyPath": "/path/to/server.key",
            "UserPasswordFilePath": "user_passwords.txt"
        }
    }
    mock_user_pass_content = "user1:password1\n"
    
    with patch("builtins.open", mock_open(read_data=mock_user_pass_content)) as m_userpass, \
            patch("bcrypt.hashpw") as m_bcrypt_hashpw:
        m_bcrypt_hashpw.return_value = b"hashed_password"
        result = _generate_config_data(mock_json_data, "prometheus")
        assert result is not None
        assert "tls_server_config" not in result
        assert "basic_auth_users" in result

    # Test missing key file
    mock_json_data = {
        "prometheusWebConfigGenerator": {
            "ServerCertPath": "/path/to/server.crt",
            "UserPasswordFilePath": "user_passwords.txt"
        }
    }
    with patch("builtins.open", mock_open(read_data=mock_user_pass_content)) as m_userpass, \
            patch("bcrypt.hashpw") as m_bcrypt_hashpw:
        m_bcrypt_hashpw.return_value = b"hashed_password"
        result = _generate_config_data(mock_json_data, "prometheus")
        assert result is not None
        assert "tls_server_config" not in result
        assert "basic_auth_users" in result

    # Test both missing (should be valid)
    mock_json_data = {
        "prometheusWebConfigGenerator": {
            "ClientCaCertPath": "/path/to/client-ca.crt",
            "UserPasswordFilePath": "user_passwords.txt"
        }
    }
    with patch("builtins.open", mock_open(read_data=mock_user_pass_content)) as m_userpass, \
            patch("bcrypt.hashpw") as m_bcrypt_hashpw:
        m_bcrypt_hashpw.return_value = b"hashed_password"
        result = _generate_config_data(mock_json_data, "prometheus")
        assert result is not None
        assert "tls_server_config" in result
        assert "cert_file" not in result["tls_server_config"]
        assert "key_file" not in result["tls_server_config"]
        assert "client_ca_file" in result["tls_server_config"]
        assert "basic_auth_users" in result


def test__generate_config_data_valid_cert_key_pair():
    mock_json_data = {
        "prometheusWebConfigGenerator": {
            "ServerCertPath": "/path/to/server.crt",
            "ServerKeyPath": "/path/to/server.key"
        }
    }
    result = _generate_config_data(mock_json_data, "prometheus")
    assert result is not None
    assert "tls_server_config" in result
    assert "cert_file" in result["tls_server_config"]
    assert "key_file" in result["tls_server_config"]
    assert result["tls_server_config"]["cert_file"] == "/path/to/server.crt"
    assert result["tls_server_config"]["key_file"] == "/path/to/server.key"

def test__read_user_passwords():
    # Test valid password file
    mock_content = "user1:password1\nuser2:password2\n"
    with patch("builtins.open", mock_open(read_data=mock_content)) as m_open, \
            patch("bcrypt.hashpw") as m_bcrypt_hashpw:
        m_bcrypt_hashpw.return_value = b"hashed_password"
        result = _read_user_passwords("passwords.txt")
        assert result == {"user1": "hashed_password", "user2": "hashed_password"}

    # Test empty password file
    with patch("builtins.open", mock_open(read_data="")) as m_open:
        result = _read_user_passwords("empty.txt")
        assert result == {}

    # Test invalid format
    with patch("builtins.open", mock_open(read_data="invalid_format\n")) as m_open:
        result = _read_user_passwords("invalid.txt")
        assert result == {}

    # Test file not found
    with patch("builtins.open", side_effect=FileNotFoundError):
        result = _read_user_passwords("nonexistent.txt")
        assert result is None

    # Test file with empty lines and comments
    mock_content = "\n# Comment\nuser1:password1\n\nuser2:password2\n"
    with patch("builtins.open", mock_open(read_data=mock_content)) as m_open, \
            patch("bcrypt.hashpw") as m_bcrypt_hashpw:
        m_bcrypt_hashpw.return_value = b"hashed_password"
        result = _read_user_passwords("passwords.txt")
        assert result == {"user1": "hashed_password", "user2": "hashed_password"}

    # Test file with duplicate usernames (should use last occurrence)
    mock_content = "user1:password1\nuser1:password2\n"
    with patch("builtins.open", mock_open(read_data=mock_content)) as m_open, \
            patch("bcrypt.hashpw") as m_bcrypt_hashpw:
        m_bcrypt_hashpw.return_value = b"hashed_password"
        result = _read_user_passwords("passwords.txt")
        assert result == {"user1": "hashed_password"}

def test__process_tls_config():
    # Test complete TLS config
    web_config = {
        "ServerCertPath": "/path/to/server.crt",
        "ServerKeyPath": "/path/to/server.key",
        "ClientCaCertPath": "/path/to/ca.crt",
        "ClientAuthType": "RequireAndVerifyClientCert"
    }
    result = _process_tls_config(web_config)
    assert result == {
        "cert_file": "/path/to/server.crt",
        "key_file": "/path/to/server.key",
        "client_ca_file": "/path/to/ca.crt",
        "client_auth_type": "RequireAndVerifyClientCert"
    }

    # Test incomplete TLS config (missing cert)
    web_config = {
        "ServerKeyPath": "/path/to/server.key"
    }
    result = _process_tls_config(web_config)
    assert result is None

    # Test incomplete TLS config (missing key)
    web_config = {
        "ServerCertPath": "/path/to/server.crt"
    }
    result = _process_tls_config(web_config)
    assert result is None

    # Test minimal valid TLS config
    web_config = {
        "ServerCertPath": "/path/to/server.crt",
        "ServerKeyPath": "/path/to/server.key"
    }
    result = _process_tls_config(web_config)
    assert result == {
        "cert_file": "/path/to/server.crt",
        "key_file": "/path/to/server.key",
    }

    # Test empty config
    web_config = {}
    result = _process_tls_config(web_config)
    assert result is None

    # Test config with only client CA
    web_config = {
        "ClientCaCertPath": "/path/to/ca.crt"
    }
    result = _process_tls_config(web_config)
    assert result == {
        "client_ca_file": "/path/to/ca.crt",
        "client_auth_type": "RequireAndVerifyClientCert",
    }

    # Test config with only client auth type
    web_config = {
        "ClientAuthType": "NoClientCert"
    }
    result = _process_tls_config(web_config)
    assert result is None

    # Test config with empty string values
    web_config = {
        "ServerCertPath": "",
        "ServerKeyPath": "",
        "ClientCaCertPath": "",
        "ClientAuthType": "RequireAndVerifyClientCert"
    }
    result = _process_tls_config(web_config)
    assert result is None

    # Test config with mixed empty and non-empty values
    web_config = {
        "ServerCertPath": "/path/to/server.crt",
        "ServerKeyPath": "",
        "ClientCaCertPath": "/path/to/ca.crt"
    }
    result = _process_tls_config(web_config)
    assert result == {
        "client_ca_file": "/path/to/ca.crt",
        "client_auth_type": "RequireAndVerifyClientCert",
    }

def test__generate_config_data():
    # Test complete config for Prometheus
    mock_json_data = {
        "prometheusWebConfigGenerator": {
            "ServerCertPath": "/path/to/server.crt",
            "ServerKeyPath": "/path/to/server.key",
            "UserPasswordFilePath": "passwords.txt"
        }
    }
    mock_user_pass_content = "user1:password1\n"
    
    with patch("builtins.open", mock_open(read_data=mock_user_pass_content)) as m_open, \
            patch("bcrypt.hashpw") as m_bcrypt_hashpw:
        m_bcrypt_hashpw.return_value = b"hashed_password"
        result = _generate_config_data(mock_json_data, "prometheus")
        assert result is not None
        assert "tls_server_config" in result
        assert "basic_auth_users" in result
        assert result["basic_auth_users"]["user1"] == "hashed_password"

    # Test complete config for Alertmanager
    mock_json_data = {
        "alertmanagerWebConfigGenerator": {
            "ServerCertPath": "/path/to/am-server.crt",
            "ServerKeyPath": "/path/to/am-server.key",
            "UserPasswordFilePath": "am-passwords.txt"
        }
    }
    mock_am_user_pass_content = "am_user1:password1\n"
    
    with patch("builtins.open", mock_open(read_data=mock_am_user_pass_content)) as m_open, \
            patch("bcrypt.hashpw") as m_bcrypt_hashpw:
        m_bcrypt_hashpw.return_value = b"hashed_password"
        result = _generate_config_data(mock_json_data, "alertmanager")
        assert result is not None
        assert "tls_server_config" in result
        assert "basic_auth_users" in result
        assert result["basic_auth_users"]["am_user1"] == "hashed_password"

    # Test config with only TLS for Prometheus
    mock_json_data = {
        "prometheusWebConfigGenerator": {
            "ServerCertPath": "/path/to/server.crt",
            "ServerKeyPath": "/path/to/server.key"
        }
    }
    result = _generate_config_data(mock_json_data, "prometheus")
    assert result is not None
    assert "tls_server_config" in result
    assert "basic_auth_users" not in result

    # Test config with only TLS for Alertmanager
    mock_json_data = {
        "alertmanagerWebConfigGenerator": {
            "ServerCertPath": "/path/to/am-server.crt",
            "ServerKeyPath": "/path/to/am-server.key"
        }
    }
    result = _generate_config_data(mock_json_data, "alertmanager")
    assert result is not None
    assert "tls_server_config" in result
    assert "basic_auth_users" not in result

    # Test config with only basic auth for Prometheus
    mock_json_data = {
        "prometheusWebConfigGenerator": {
            "UserPasswordFilePath": "passwords.txt"
        }
    }
    with patch("builtins.open", mock_open(read_data=mock_user_pass_content)) as m_open, \
            patch("bcrypt.hashpw") as m_bcrypt_hashpw:
        m_bcrypt_hashpw.return_value = b"hashed_password"
        result = _generate_config_data(mock_json_data, "prometheus")
        assert result is not None
        assert "tls_server_config" not in result
        assert "basic_auth_users" in result

    # Test config with only basic auth for Alertmanager
    mock_json_data = {
        "alertmanagerWebConfigGenerator": {
            "UserPasswordFilePath": "am-passwords.txt"
        }
    }
    with patch("builtins.open", mock_open(read_data=mock_am_user_pass_content)) as m_open, \
            patch("bcrypt.hashpw") as m_bcrypt_hashpw:
        m_bcrypt_hashpw.return_value = b"hashed_password"
        result = _generate_config_data(mock_json_data, "alertmanager")
        assert result is not None
        assert "tls_server_config" not in result
        assert "basic_auth_users" in result

    # Test empty config
    mock_json_data = {
        "prometheusWebConfigGenerator": {}
    }
    result = _generate_config_data(mock_json_data, "prometheus")
    assert result is None

    # Test missing config section
    mock_json_data = {}
    result = _generate_config_data(mock_json_data, "prometheus")
    assert result is None

    # Test invalid config type
    mock_json_data = {
        "prometheusWebConfigGenerator": {
            "ServerCertPath": "/path/to/server.crt",
            "ServerKeyPath": "/path/to/server.key"
        }
    }
    result = _generate_config_data(mock_json_data, "invalid")
    assert result is None

    # Test error in password file reading for Prometheus
    mock_json_data = {
        "prometheusWebConfigGenerator": {
            "UserPasswordFilePath": "nonexistent.txt"
        }
    }
    with patch("builtins.open", side_effect=FileNotFoundError):
        result = _generate_config_data(mock_json_data, "prometheus")
        assert result is None

    # Test error in password file reading for Alertmanager
    mock_json_data = {
        "alertmanagerWebConfigGenerator": {
            "UserPasswordFilePath": "nonexistent.txt"
        }
    }
    with patch("builtins.open", side_effect=FileNotFoundError):
        result = _generate_config_data(mock_json_data, "alertmanager")
        assert result is None

def test_generate_configs():
    # Test successful generation of both configs
    mock_json_data = {
        "prometheusWebConfigGenerator": {
            "ServerCertPath": "/path/to/server.crt",
            "ServerKeyPath": "/path/to/server.key",
            "UserPasswordFilePath": "passwords.txt"
        },
        "alertmanagerWebConfigGenerator": {
            "ServerCertPath": "/path/to/am-server.crt",
            "ServerKeyPath": "/path/to/am-server.key",
            "UserPasswordFilePath": "am-passwords.txt"
        }
    }
    mock_user_pass_content = "user1:password1\n"
    mock_am_user_pass_content = "am_user1:password1\n"

    mock_files = {
        "mock_config.json": json.dumps(mock_json_data),
        "passwords.txt": mock_user_pass_content,
        "am-passwords.txt": mock_am_user_pass_content
    }

    def mock_open_func(filename, mode="r"):
        content = mock_files.get(filename, "")
        return mock_open(read_data=content)()

    with patch("builtins.open", side_effect=mock_open_func) as m_open, \
            patch("bcrypt.hashpw") as m_bcrypt_hashpw, \
            patch("yaml.dump") as m_yaml_dump, \
            patch("os.makedirs") as m_makedirs:

        m_bcrypt_hashpw.return_value = b"hashed_password"

        result = generate_configs("mock_config.json", "mock_output_dir")

        assert result is True
        assert m_bcrypt_hashpw.called
        assert m_yaml_dump.call_count == 2
        assert m_makedirs.called_with("mock_output_dir", exist_ok=True)

    # Test generation with only Prometheus config
    mock_json_data = {
        "prometheusWebConfigGenerator": {
            "ServerCertPath": "/path/to/server.crt",
            "ServerKeyPath": "/path/to/server.key"
        }
    }
    with patch("builtins.open", mock_open(read_data=json.dumps(mock_json_data))) as m_json, \
            patch("yaml.dump") as m_yaml_dump, \
            patch("os.makedirs"):
        result = generate_configs("mock_config.json", "mock_output_dir")
        assert result is True
        assert m_yaml_dump.call_count == 1

    # Test generation with only Alertmanager config
    mock_json_data = {
        "alertmanagerWebConfigGenerator": {
            "ServerCertPath": "/path/to/am-server.crt",
            "ServerKeyPath": "/path/to/am-server.key"
        }
    }
    with patch("builtins.open", mock_open(read_data=json.dumps(mock_json_data))) as m_json, \
            patch("yaml.dump") as m_yaml_dump, \
            patch("os.makedirs"):
        result = generate_configs("mock_config.json", "mock_output_dir")
        assert result is True
        assert m_yaml_dump.call_count == 1

    # Test generation with empty config
    mock_json_data = {}
    with patch("builtins.open", mock_open(read_data=json.dumps(mock_json_data))) as m_json, \
            patch("yaml.dump") as m_yaml_dump, \
            patch("os.makedirs"):
        result = generate_configs("mock_config.json", "mock_output_dir")
        assert result is True
        assert m_yaml_dump.call_count == 0

def test_generate_configs_io_error():
    # Test IOError during config file reading
    mock_json_data = {
        "prometheusWebConfigGenerator": {
            "ServerCertPath": "/path/to/server.crt",
            "ServerKeyPath": "/path/to/am-server.key"
        }
    }
    with patch("builtins.open", mock_open(read_data=json.dumps(mock_json_data))) as m_json, \
            patch("yaml.dump", side_effect=IOError), \
            patch("os.makedirs"):
        result = generate_configs("mock_config.json", "mock_output_dir")
        assert result is False

    # Test IOError during password file reading
    mock_json_data = {
        "prometheusWebConfigGenerator": {
            "UserPasswordFilePath": "passwords.txt"
        }
    }
    with patch("builtins.open", side_effect=FileNotFoundError), \
            patch("os.makedirs"):
        result = generate_configs("mock_config.json", "mock_output_dir")
        assert result is False

    # Test IOError during directory creation
    with patch("builtins.open", mock_open(read_data=json.dumps({}))), \
            patch("os.makedirs", side_effect=IOError):
        result = generate_configs("mock_config.json", "mock_output_dir")
        assert result is False

def test_empty_config_creates_blank_files():
    # Create a temporary directory for output
    with tempfile.TemporaryDirectory() as output_dir:
        # Create an empty config file
        with tempfile.NamedTemporaryFile(mode='w', suffix='.json') as config_file:
            config_file.write('{}')
            config_file.flush()
            
            # Generate configs
            result = generate_configs(config_file.name, output_dir)
            
            # Verify the function succeeded
            assert result is True
            
            # Check that both files exist and are empty
            prom_file = os.path.join(output_dir, "web-config-prom.yaml")
            am_file = os.path.join(output_dir, "web-config-am.yaml")
            
            assert os.path.exists(prom_file)
            assert os.path.exists(am_file)
            
            # Verify files are completely blank
            assert os.path.getsize(prom_file) == 0
            assert os.path.getsize(am_file) == 0

def test_invalid_json_returns_false():
    with tempfile.TemporaryDirectory() as output_dir:
        with tempfile.NamedTemporaryFile(mode='w', suffix='.json') as config_file:
            config_file.write('invalid json')
            config_file.flush()
            
            result = generate_configs(config_file.name, output_dir)
            assert result is False

def test_nonexistent_config_file_returns_false():
    with tempfile.TemporaryDirectory() as output_dir:
        result = generate_configs("nonexistent_file.json", output_dir)
        assert result is False