"""Integration tests for F5 iControl REST API tools.

Verifies correct URL construction, token-based auth, SSL, timeout, payload,
and token cleanup by inspecting how requests.post (login), requests.request
(data), and requests.delete (token cleanup) are called.
"""

import requests


class TestF5ListUrlConstruction:
    """Verify f5_list builds correct URLs."""

    def test_url_with_object_name(self, mock_f5_credentials, mock_f5_requests):
        """URL includes object type and name."""
        from mcp_network_jcd.tools.f5 import f5_list

        f5_list(host="10.0.0.1", object_type="pool", object_name="mypool")

        mock_f5_requests.assert_called_once()
        call_args = mock_f5_requests.call_args
        assert call_args[0][0] == "GET"
        assert call_args[0][1] == "https://10.0.0.1/mgmt/tm/ltm/pool/mypool"

    def test_url_without_object_name(self, mock_f5_credentials, mock_f5_requests):
        """URL lists all objects when name is empty."""
        from mcp_network_jcd.tools.f5 import f5_list

        f5_list(host="10.0.0.1", object_type="virtual")

        call_args = mock_f5_requests.call_args
        assert call_args[0][1] == "https://10.0.0.1/mgmt/tm/ltm/virtual"

    def test_url_irule_type(self, mock_f5_credentials, mock_f5_requests):
        """URL correctly uses irule type."""
        from mcp_network_jcd.tools.f5 import f5_list

        f5_list(host="10.0.0.1", object_type="irule", object_name="my_irule")

        call_args = mock_f5_requests.call_args
        assert call_args[0][1] == "https://10.0.0.1/mgmt/tm/ltm/irule/my_irule"


class TestF5SubCollectionUrlConstruction:
    """Verify f5_list builds correct URLs for sub-collections."""

    def test_url_pool_members(self, mock_f5_credentials, mock_f5_requests):
        """URL includes pool name and /members suffix."""
        from mcp_network_jcd.tools.f5 import f5_list

        f5_list(
            host="10.0.0.1",
            object_type="pool",
            object_name="mypool",
            sub_collection="members",
        )

        call_args = mock_f5_requests.call_args
        assert call_args[0][0] == "GET"
        assert call_args[0][1] == "https://10.0.0.1/mgmt/tm/ltm/pool/mypool/members"

    def test_url_virtual_policies(self, mock_f5_credentials, mock_f5_requests):
        """URL includes virtual name and /policies suffix."""
        from mcp_network_jcd.tools.f5 import f5_list

        f5_list(
            host="10.0.0.1",
            object_type="virtual",
            object_name="myvip",
            sub_collection="policies",
        )

        call_args = mock_f5_requests.call_args
        assert call_args[0][1] == "https://10.0.0.1/mgmt/tm/ltm/virtual/myvip/policies"

    def test_url_virtual_profiles(self, mock_f5_credentials, mock_f5_requests):
        """URL includes virtual name and /profiles suffix."""
        from mcp_network_jcd.tools.f5 import f5_list

        f5_list(
            host="10.0.0.1",
            object_type="virtual",
            object_name="myvip",
            sub_collection="profiles",
        )

        call_args = mock_f5_requests.call_args
        assert call_args[0][1] == "https://10.0.0.1/mgmt/tm/ltm/virtual/myvip/profiles"


class TestF5StatsUrlConstruction:
    """Verify f5_stats builds correct URLs."""

    def test_url_includes_stats_suffix(self, mock_f5_credentials, mock_f5_requests):
        """URL includes /stats at the end."""
        from mcp_network_jcd.tools.f5 import f5_stats

        f5_stats(host="10.0.0.1", object_type="virtual", object_name="myvip")

        call_args = mock_f5_requests.call_args
        assert call_args[0][0] == "GET"
        assert call_args[0][1] == "https://10.0.0.1/mgmt/tm/ltm/virtual/myvip/stats"


class TestF5TokenAuth:
    """Verify token-based authentication flow."""

    def test_login_called_with_credentials(self, mock_f5_credentials, mock_f5_requests, mocker):
        """Login POST is called with correct credentials."""
        from mcp_network_jcd.tools.f5 import f5_list

        mock_post = mocker.patch("mcp_network_jcd.tools.f5.requests.post")
        mock_login_resp = mocker.MagicMock()
        mock_login_resp.status_code = 200
        mock_login_resp.json.return_value = {"token": {"token": "TEST_TOKEN"}}
        mock_login_resp.raise_for_status.return_value = None
        mock_post.return_value = mock_login_resp

        f5_list(host="10.0.0.1", object_type="pool", object_name="test")

        mock_post.assert_called_once()
        call_kwargs = mock_post.call_args[1]
        assert call_kwargs["json"]["username"] == "admin"
        assert call_kwargs["json"]["password"] == "f5secret"

    def test_token_used_in_request_header(self, mock_f5_credentials, mock_f5_requests, mocker):
        """Data request uses X-F5-Auth-Token header from login."""
        from mcp_network_jcd.tools.f5 import f5_list

        mock_post = mocker.patch("mcp_network_jcd.tools.f5.requests.post")
        mock_login_resp = mocker.MagicMock()
        mock_login_resp.status_code = 200
        mock_login_resp.json.return_value = {"token": {"token": "MY_AUTH_TOKEN"}}
        mock_login_resp.raise_for_status.return_value = None
        mock_post.return_value = mock_login_resp

        f5_list(host="10.0.0.1", object_type="pool", object_name="test")

        call_kwargs = mock_f5_requests.call_args[1]
        assert call_kwargs["headers"]["X-F5-Auth-Token"] == "MY_AUTH_TOKEN"

    def test_login_url_correct(self, mock_f5_credentials, mock_f5_requests, mocker):
        """Login POST targets /mgmt/shared/authn/login."""
        from mcp_network_jcd.tools.f5 import f5_list

        mock_post = mocker.patch("mcp_network_jcd.tools.f5.requests.post")
        mock_login_resp = mocker.MagicMock()
        mock_login_resp.status_code = 200
        mock_login_resp.json.return_value = {"token": {"token": "TOK"}}
        mock_login_resp.raise_for_status.return_value = None
        mock_post.return_value = mock_login_resp

        f5_list(host="10.0.0.1", object_type="pool")

        assert mock_post.call_args[0][0] == "https://10.0.0.1/mgmt/shared/authn/login"


class TestF5SslVerification:
    """Verify SSL verification is disabled."""

    def test_verify_is_false_on_request(self, mock_f5_credentials, mock_f5_requests):
        """Data request has verify=False for self-signed certs."""
        from mcp_network_jcd.tools.f5 import f5_list

        f5_list(host="10.0.0.1", object_type="pool", object_name="test")

        call_kwargs = mock_f5_requests.call_args[1]
        assert call_kwargs["verify"] is False


class TestF5ProxyDisabled:
    """Verify proxy is disabled for direct access."""

    def test_proxies_empty_on_request(self, mock_f5_credentials, mock_f5_requests):
        """Data request has empty proxies dict to bypass proxy."""
        from mcp_network_jcd.tools.f5 import f5_list

        f5_list(host="10.0.0.1", object_type="pool", object_name="test")

        call_kwargs = mock_f5_requests.call_args[1]
        assert call_kwargs["proxies"] == {"http": "", "https": ""}


class TestF5Timeout:
    """Verify timeout is passed correctly."""

    def test_custom_timeout(self, mock_f5_credentials, mock_f5_requests):
        """Custom timeout value is forwarded to requests."""
        from mcp_network_jcd.tools.f5 import f5_list

        f5_list(host="10.0.0.1", object_type="pool", object_name="test", timeout=60)

        call_kwargs = mock_f5_requests.call_args[1]
        assert call_kwargs["timeout"] == 60

    def test_default_timeout(self, mock_f5_credentials, mock_f5_requests):
        """Default timeout is 20 seconds."""
        from mcp_network_jcd.tools.f5 import f5_stats

        f5_stats(host="10.0.0.1", object_type="pool", object_name="test")

        call_kwargs = mock_f5_requests.call_args[1]
        assert call_kwargs["timeout"] == 20


# ---------------------------------------------------------------------------
# Token cleanup (DELETE after use)
# ---------------------------------------------------------------------------
class TestF5TokenCleanup:
    """Verify auth token is deleted after each request."""

    def test_token_deleted_after_successful_list(self, mock_f5_credentials, mock_f5_requests, mocker):
        """DELETE called after successful f5_list."""
        from mcp_network_jcd.tools.f5 import f5_list

        mock_delete = mocker.patch("mcp_network_jcd.tools.f5.requests.delete")

        result = f5_list(host="10.0.0.1", object_type="pool", object_name="mypool")

        assert result["success"] is True
        mock_delete.assert_called_once()

    def test_token_deleted_after_successful_stats(self, mock_f5_credentials, mock_f5_requests, mocker):
        """DELETE called after successful f5_stats."""
        from mcp_network_jcd.tools.f5 import f5_stats

        mock_delete = mocker.patch("mcp_network_jcd.tools.f5.requests.delete")

        result = f5_stats(host="10.0.0.1", object_type="virtual", object_name="myvip")

        assert result["success"] is True
        mock_delete.assert_called_once()

    def test_token_deleted_after_http_error(self, mock_f5_credentials, mock_f5_requests_404, mocker):
        """DELETE called even when data request returns 404."""
        from mcp_network_jcd.tools.f5 import f5_list

        mock_delete = mocker.patch("mcp_network_jcd.tools.f5.requests.delete")

        result = f5_list(host="10.0.0.1", object_type="pool", object_name="nonexistent")

        assert result["success"] is False
        mock_delete.assert_called_once()

    def test_token_deleted_after_request_timeout(self, mock_f5_credentials, mocker):
        """DELETE called even when data request times out."""
        from mcp_network_jcd.tools.f5 import f5_list

        # Login succeeds
        mock_login = mocker.MagicMock()
        mock_login.status_code = 200
        mock_login.json.return_value = {"token": {"token": "TOK123"}}
        mock_login.raise_for_status.return_value = None
        mocker.patch("mcp_network_jcd.tools.f5.requests.post", return_value=mock_login)

        # Data request times out
        mocker.patch(
            "mcp_network_jcd.tools.f5.requests.request",
            side_effect=requests.exceptions.Timeout("timed out"),
        )

        mock_delete = mocker.patch("mcp_network_jcd.tools.f5.requests.delete")

        result = f5_list(host="10.0.0.1", object_type="pool", object_name="test")

        assert result["success"] is False
        assert result["error"]["code"] == "CONNECTION_TIMEOUT"
        mock_delete.assert_called_once()

    def test_token_deleted_after_connection_error(self, mock_f5_credentials, mocker):
        """DELETE called even when data request gets connection error."""
        from mcp_network_jcd.tools.f5 import f5_list

        mock_login = mocker.MagicMock()
        mock_login.status_code = 200
        mock_login.json.return_value = {"token": {"token": "TOK123"}}
        mock_login.raise_for_status.return_value = None
        mocker.patch("mcp_network_jcd.tools.f5.requests.post", return_value=mock_login)

        mocker.patch(
            "mcp_network_jcd.tools.f5.requests.request",
            side_effect=requests.exceptions.ConnectionError("refused"),
        )

        mock_delete = mocker.patch("mcp_network_jcd.tools.f5.requests.delete")

        result = f5_list(host="10.0.0.1", object_type="pool", object_name="test")

        assert result["success"] is False
        assert result["error"]["code"] == "CONNECTION_REFUSED"
        mock_delete.assert_called_once()

    def test_no_token_delete_when_login_fails(self, mock_f5_credentials, mock_f5_requests_401, mocker):
        """DELETE NOT called when login returns 401 (no token obtained)."""
        from mcp_network_jcd.tools.f5 import f5_list

        mock_delete = mocker.patch("mcp_network_jcd.tools.f5.requests.delete")

        result = f5_list(host="10.0.0.1", object_type="pool", object_name="test")

        assert result["success"] is False
        assert result["error"]["code"] == "AUTH_FAILED"
        mock_delete.assert_not_called()

    def test_no_token_delete_when_login_times_out(self, mock_f5_credentials, mocker):
        """DELETE NOT called when login times out (no token obtained)."""
        from mcp_network_jcd.tools.f5 import f5_list

        mocker.patch(
            "mcp_network_jcd.tools.f5.requests.post",
            side_effect=requests.exceptions.Timeout("login timed out"),
        )

        mock_delete = mocker.patch("mcp_network_jcd.tools.f5.requests.delete")

        result = f5_list(host="10.0.0.1", object_type="pool", object_name="test")

        assert result["success"] is False
        assert result["error"]["code"] == "CONNECTION_TIMEOUT"
        mock_delete.assert_not_called()


class TestF5TokenCleanupBestEffort:
    """Verify token delete failures don't mask original results."""

    def test_delete_timeout_does_not_mask_success(self, mock_f5_credentials, mock_f5_requests, mocker):
        """Delete timeout doesn't affect successful result."""
        from mcp_network_jcd.tools.f5 import f5_list

        mocker.patch(
            "mcp_network_jcd.tools.f5.requests.delete",
            side_effect=requests.exceptions.Timeout("delete timed out"),
        )

        result = f5_list(host="10.0.0.1", object_type="pool", object_name="mypool")

        assert result["success"] is True

    def test_delete_connection_error_does_not_mask_success(
        self, mock_f5_credentials, mock_f5_requests, mocker
    ):
        """Delete connection error doesn't affect successful result."""
        from mcp_network_jcd.tools.f5 import f5_list

        mocker.patch(
            "mcp_network_jcd.tools.f5.requests.delete",
            side_effect=requests.exceptions.ConnectionError("delete refused"),
        )

        result = f5_list(host="10.0.0.1", object_type="pool", object_name="mypool")

        assert result["success"] is True

    def test_delete_generic_exception_does_not_mask_success(
        self, mock_f5_credentials, mock_f5_requests, mocker
    ):
        """Delete generic exception doesn't affect successful result."""
        from mcp_network_jcd.tools.f5 import f5_list

        mocker.patch(
            "mcp_network_jcd.tools.f5.requests.delete",
            side_effect=RuntimeError("unexpected"),
        )

        result = f5_list(host="10.0.0.1", object_type="pool", object_name="mypool")

        assert result["success"] is True

    def test_delete_failure_does_not_mask_data_error(
        self, mock_f5_credentials, mock_f5_requests_404, mocker
    ):
        """Delete failure doesn't mask original HTTP_ERROR from data request."""
        from mcp_network_jcd.tools.f5 import f5_list

        mocker.patch(
            "mcp_network_jcd.tools.f5.requests.delete",
            side_effect=requests.exceptions.Timeout("delete timed out"),
        )

        result = f5_list(host="10.0.0.1", object_type="pool", object_name="nonexistent")

        assert result["success"] is False
        assert result["error"]["code"] == "HTTP_ERROR"


class TestF5TokenDeleteRequest:
    """Verify the DELETE request is correctly formed."""

    def test_delete_url_contains_token(self, mock_f5_credentials, mock_f5_requests, mocker):
        """DELETE URL is https://{host}/mgmt/shared/authz/tokens/{token}."""
        from mcp_network_jcd.tools.f5 import f5_list

        mock_delete = mocker.patch("mcp_network_jcd.tools.f5.requests.delete")

        f5_list(host="10.0.0.1", object_type="pool", object_name="test")

        mock_delete.assert_called_once()
        url = mock_delete.call_args[0][0]
        assert url == "https://10.0.0.1/mgmt/shared/authz/tokens/FAKE_TOKEN_12345"

    def test_delete_includes_auth_header(self, mock_f5_credentials, mock_f5_requests, mocker):
        """DELETE request includes X-F5-Auth-Token header."""
        from mcp_network_jcd.tools.f5 import f5_list

        mock_delete = mocker.patch("mcp_network_jcd.tools.f5.requests.delete")

        f5_list(host="10.0.0.1", object_type="pool", object_name="test")

        call_kwargs = mock_delete.call_args[1]
        assert call_kwargs["headers"]["X-F5-Auth-Token"] == "FAKE_TOKEN_12345"

    def test_delete_uses_verify_false(self, mock_f5_credentials, mock_f5_requests, mocker):
        """DELETE request uses verify=False."""
        from mcp_network_jcd.tools.f5 import f5_list

        mock_delete = mocker.patch("mcp_network_jcd.tools.f5.requests.delete")

        f5_list(host="10.0.0.1", object_type="pool", object_name="test")

        call_kwargs = mock_delete.call_args[1]
        assert call_kwargs["verify"] is False

    def test_delete_uses_same_timeout(self, mock_f5_credentials, mock_f5_requests, mocker):
        """DELETE request uses the same timeout as the original request."""
        from mcp_network_jcd.tools.f5 import f5_list

        mock_delete = mocker.patch("mcp_network_jcd.tools.f5.requests.delete")

        f5_list(host="10.0.0.1", object_type="pool", object_name="test", timeout=45)

        call_kwargs = mock_delete.call_args[1]
        assert call_kwargs["timeout"] == 45
