"""
Functional tests for WebSocket transport with SSL/TLS enabled.

These tests verify that WebSocket transport works correctly with SSL certificates
and CERT_REQUIRED validation.
"""

import asyncio

import pytest

import salt.transport

pytestmark = [
    pytest.mark.core_test,
]


async def test_ws_pub_server_with_ssl(
    io_loop,
    minion_opts,
    master_opts,
    process_manager,
    ssl_master_config,
    ssl_minion_config,
):
    """
    Test WebSocket publish server with SSL/TLS enabled.

    This test verifies:
    1. WebSocket publish server can start with SSL configuration
    2. WebSocket publish client can connect over TLS
    3. Messages can be published and received over encrypted connection
    """
    import salt.config

    # Configure SSL for both master and minion
    master_opts["transport"] = "ws"
    master_opts["ssl"] = ssl_master_config.copy()
    # Convert string constants to integers
    salt.config._update_ssl_config(master_opts)

    minion_opts["transport"] = "ws"
    minion_opts["ssl"] = ssl_minion_config.copy()
    # Convert string constants to integers
    salt.config._update_ssl_config(minion_opts)

    # Create publish server with SSL
    pub_server = salt.transport.publish_server(master_opts)
    pub_server.pre_fork(process_manager)
    await asyncio.sleep(3)

    # Create publish client with SSL
    pub_client = salt.transport.publish_client(
        minion_opts, io_loop, master_opts["interface"], master_opts["publish_port"]
    )
    await pub_client.connect()

    # Yield to loop to allow client to connect
    event = asyncio.Event()
    messages = []

    async def handle_msg(msg):
        messages.append(msg)
        event.set()

    try:
        pub_client.on_recv(handle_msg)

        # Send a message
        msg = {b"foo": b"bar"}
        await pub_server.publish(msg)

        # Wait for message to be received
        await asyncio.wait_for(event.wait(), 5)

        # Verify message was received
        assert [msg] == messages
    finally:
        pub_server.close()
        pub_client.close()
        process_manager.terminate()

    # Yield to loop to allow cleanup
    await asyncio.sleep(0.3)


async def test_ws_request_client_with_ssl(
    io_loop,
    minion_opts,
    master_opts,
    ssl_master_config,
    ssl_minion_config,
):
    """
    Test WebSocket request/response with SSL/TLS enabled.

    This test verifies:
    1. WebSocket request server can start with SSL configuration
    2. WebSocket request client can connect over TLS
    3. Request/response messages work over encrypted connection
    """
    # Configure SSL for both master and minion
    master_opts["transport"] = "ws"
    master_opts["ssl"] = ssl_master_config

    minion_opts["transport"] = "ws"
    minion_opts["ssl"] = ssl_minion_config

    # Create request server with SSL
    req_server_channel = salt.channel.server.ReqServerChannel.factory(master_opts)

    # Mock handler for requests
    async def handle_request(payload, header=None):
        # Simple echo server
        return payload

    # Note: This is a simplified test - full request/response testing
    # would require starting the actual request server which is more complex

    # For now, just verify the channel can be created with SSL config
    assert req_server_channel is not None

    # Cleanup
    req_server_channel.close()


async def test_ws_ssl_connection_refused_without_client_cert(
    io_loop,
    minion_opts,
    master_opts,
    ssl_master_config,
):
    """
    Test that WebSocket connection is refused when client doesn't provide certificate.

    This test verifies:
    1. Server with CERT_REQUIRED rejects clients without certificates
    2. TLS handshake fails appropriately
    """
    # Configure SSL for master only (minion has no SSL)
    master_opts["transport"] = "ws"
    master_opts["ssl"] = ssl_master_config

    minion_opts["transport"] = "ws"
    # Remove any SSL config from previous tests - minion should NOT have SSL
    minion_opts.pop("ssl", None)
    # Note: minion_opts does NOT have ssl config - connection should fail

    # Create publish server with SSL
    pub_server = salt.transport.publish_server(master_opts)
    pub_server.pre_fork(process_manager := salt.utils.process.ProcessManager())
    await asyncio.sleep(3)

    try:
        # Try to create publish client WITHOUT SSL
        pub_client = salt.transport.publish_client(
            minion_opts, io_loop, master_opts["interface"], master_opts["publish_port"]
        )

        # Connection should fail or timeout
        try:
            await asyncio.wait_for(pub_client.connect(), timeout=5)
            # If we get here, connection succeeded when it shouldn't have
            pytest.fail(
                "Client connected without SSL certificate when it should have been rejected"
            )
        except (asyncio.TimeoutError, OSError, ConnectionError):
            # Expected - connection should fail with timeout or connection error
            pass
        finally:
            pub_client.close()
    finally:
        pub_server.close()
        process_manager.terminate()

    await asyncio.sleep(0.3)


def test_ws_ssl_config_structure(ssl_master_config, ssl_minion_config):
    """
    Test that SSL configuration is properly structured for WebSocket transport.
    """
    # Verify master config
    assert ssl_master_config["cert_reqs"] == "CERT_REQUIRED"
    assert "certfile" in ssl_master_config
    assert "keyfile" in ssl_master_config
    assert "ca_certs" in ssl_master_config

    # Verify minion config
    assert ssl_minion_config["cert_reqs"] == "CERT_REQUIRED"
    assert "certfile" in ssl_minion_config
    assert "keyfile" in ssl_minion_config
    assert "ca_certs" in ssl_minion_config
