import base64
import json
from unittest import mock

import pytest

from prowler.providers.okta.exceptions.exceptions import (
    OktaEnvironmentVariableError,
    OktaInsufficientPermissionsError,
    OktaInvalidCredentialsError,
    OktaInvalidOrgDomainError,
    OktaInvalidProviderIdError,
    OktaPrivateKeyFileError,
    OktaSetUpIdentityError,
)
from prowler.providers.okta.models import OktaIdentityInfo, OktaSession
from prowler.providers.okta.okta_provider import DEFAULT_SCOPES, OktaProvider
from tests.providers.okta.okta_fixtures import (
    OKTA_CLIENT_ID,
    OKTA_ORG_DOMAIN,
    OKTA_PRIVATE_KEY,
)


def _make_jwt(payload: dict) -> str:
    """Build an unsigned JWT carrying the given payload dict.

    The signature segment is irrelevant — `_decode_token_scopes` reads
    the payload without verification.
    """

    def _b64u(data: bytes) -> str:
        return base64.urlsafe_b64encode(data).rstrip(b"=").decode()

    header = _b64u(json.dumps({"alg": "none"}).encode())
    body = _b64u(json.dumps(payload).encode())
    return f"{header}.{body}.sig"


class Test_OktaProvider_decode_token_scopes:
    def test_returns_scopes_from_list_scp_claim(self):
        token = _make_jwt({"scp": ["okta.policies.read", "okta.brands.read"]})
        assert OktaProvider._decode_token_scopes(token) == [
            "okta.policies.read",
            "okta.brands.read",
        ]

    def test_returns_scopes_from_space_separated_scp_string(self):
        token = _make_jwt({"scp": "okta.policies.read okta.brands.read"})
        assert OktaProvider._decode_token_scopes(token) == [
            "okta.policies.read",
            "okta.brands.read",
        ]

    def test_returns_empty_list_when_token_is_none(self):
        assert OktaProvider._decode_token_scopes(None) == []

    def test_returns_empty_list_when_token_is_empty_string(self):
        assert OktaProvider._decode_token_scopes("") == []

    def test_returns_empty_list_when_scp_claim_missing(self):
        token = _make_jwt({"sub": "client-id"})
        assert OktaProvider._decode_token_scopes(token) == []

    def test_returns_empty_list_when_token_is_malformed(self):
        assert OktaProvider._decode_token_scopes("not.a.jwt-with-bad-base64!!") == []

    def test_returns_empty_list_when_payload_is_not_json(self):
        bad = base64.urlsafe_b64encode(b"not json").rstrip(b"=").decode()
        assert OktaProvider._decode_token_scopes(f"hdr.{bad}.sig") == []


@pytest.fixture
def _clear_okta_env(monkeypatch):
    for var in (
        "OKTA_ORG_DOMAIN",
        "OKTA_CLIENT_ID",
        "OKTA_PRIVATE_KEY",
        "OKTA_PRIVATE_KEY_FILE",
        "OKTA_SCOPES",
    ):
        monkeypatch.delenv(var, raising=False)


class Test_OktaProvider_validate_arguments:
    def test_missing_all_three_raises_combined(self, _clear_okta_env):
        with pytest.raises(OktaEnvironmentVariableError) as exc:
            OktaProvider.validate_arguments()
        msg = str(exc.value)
        assert "OKTA_ORG_DOMAIN" in msg
        assert "OKTA_CLIENT_ID" in msg
        assert "OKTA_PRIVATE_KEY" in msg

    def test_only_org_domain_missing(self, _clear_okta_env, tmp_path):
        key_file = tmp_path / "key.pem"
        key_file.write_text(OKTA_PRIVATE_KEY)
        with pytest.raises(OktaEnvironmentVariableError) as exc:
            OktaProvider.validate_arguments(
                okta_client_id=OKTA_CLIENT_ID,
                okta_private_key_file=str(key_file),
            )
        assert "OKTA_ORG_DOMAIN" in str(exc.value)

    def test_accepts_private_key_content_in_place_of_file(self, _clear_okta_env):
        OktaProvider.validate_arguments(
            okta_org_domain=OKTA_ORG_DOMAIN,
            okta_client_id=OKTA_CLIENT_ID,
            okta_private_key=OKTA_PRIVATE_KEY,
        )

    def test_all_present_via_args(self, _clear_okta_env, tmp_path):
        key_file = tmp_path / "key.pem"
        key_file.write_text(OKTA_PRIVATE_KEY)
        OktaProvider.validate_arguments(
            okta_org_domain=OKTA_ORG_DOMAIN,
            okta_client_id=OKTA_CLIENT_ID,
            okta_private_key_file=str(key_file),
        )

    def test_all_present_via_env(self, monkeypatch, tmp_path):
        key_file = tmp_path / "key.pem"
        key_file.write_text(OKTA_PRIVATE_KEY)
        monkeypatch.setenv("OKTA_ORG_DOMAIN", OKTA_ORG_DOMAIN)
        monkeypatch.setenv("OKTA_CLIENT_ID", OKTA_CLIENT_ID)
        monkeypatch.setenv("OKTA_PRIVATE_KEY_FILE", str(key_file))
        OktaProvider.validate_arguments()


class Test_OktaProvider_setup_session:
    def test_rejects_domain_with_scheme(self, _clear_okta_env, tmp_path):
        key_file = tmp_path / "key.pem"
        key_file.write_text(OKTA_PRIVATE_KEY)
        with pytest.raises(OktaInvalidOrgDomainError):
            OktaProvider.setup_session(
                org_domain="https://acme.okta.com",
                client_id=OKTA_CLIENT_ID,
                private_key_file=str(key_file),
            )

    def test_rejects_domain_with_trailing_slash(self, _clear_okta_env, tmp_path):
        key_file = tmp_path / "key.pem"
        key_file.write_text(OKTA_PRIVATE_KEY)
        with pytest.raises(OktaInvalidOrgDomainError):
            OktaProvider.setup_session(
                org_domain="acme.okta.com/",
                client_id=OKTA_CLIENT_ID,
                private_key_file=str(key_file),
            )

    def test_rejects_non_okta_tld(self, _clear_okta_env, tmp_path):
        key_file = tmp_path / "key.pem"
        key_file.write_text(OKTA_PRIVATE_KEY)
        with pytest.raises(OktaInvalidOrgDomainError):
            OktaProvider.setup_session(
                org_domain="login.example.com",
                client_id=OKTA_CLIENT_ID,
                private_key_file=str(key_file),
            )

    def test_accepts_all_okta_managed_tlds(self, _clear_okta_env, tmp_path):
        # Mirrors the domain whitelist used by the Okta SDK
        # (okta.config.config_validator) so that gov/mil tenants — exactly the
        # audience most likely to care about the DISA STIG check — are not
        # turned away at provider init.
        key_file = tmp_path / "key.pem"
        key_file.write_text(OKTA_PRIVATE_KEY)
        for domain in (
            "acme.oktapreview.com",
            "acme.okta-emea.com",
            "acme.okta-gov.com",
            "acme.okta.mil",
            "acme.okta-miltest.com",
            "acme.trex-govcloud.com",
        ):
            session = OktaProvider.setup_session(
                org_domain=domain,
                client_id=OKTA_CLIENT_ID,
                private_key_file=str(key_file),
            )
            assert session.org_domain == domain

    def test_unreadable_private_key_file_raises(self, _clear_okta_env):
        with pytest.raises(OktaPrivateKeyFileError):
            OktaProvider.setup_session(
                org_domain=OKTA_ORG_DOMAIN,
                client_id=OKTA_CLIENT_ID,
                private_key_file="/nonexistent/path.pem",
            )

    def test_happy_path_uses_default_scopes(self, _clear_okta_env, tmp_path):
        key_file = tmp_path / "key.pem"
        key_file.write_text(OKTA_PRIVATE_KEY)
        session = OktaProvider.setup_session(
            org_domain=OKTA_ORG_DOMAIN,
            client_id=OKTA_CLIENT_ID,
            private_key_file=str(key_file),
        )
        assert session.org_domain == OKTA_ORG_DOMAIN
        assert session.client_id == OKTA_CLIENT_ID
        assert session.private_key == OKTA_PRIVATE_KEY
        assert session.scopes == DEFAULT_SCOPES

    def test_custom_scopes_parsed_from_csv(self, _clear_okta_env, tmp_path):
        key_file = tmp_path / "key.pem"
        key_file.write_text(OKTA_PRIVATE_KEY)
        session = OktaProvider.setup_session(
            org_domain=OKTA_ORG_DOMAIN,
            client_id=OKTA_CLIENT_ID,
            private_key_file=str(key_file),
            scopes="okta.policies.read, okta.apps.read ,okta.users.read",
        )
        assert session.scopes == [
            "okta.policies.read",
            "okta.apps.read",
            "okta.users.read",
        ]

    def test_custom_scopes_accepts_list_input(self, _clear_okta_env, tmp_path):
        key_file = tmp_path / "key.pem"
        key_file.write_text(OKTA_PRIVATE_KEY)
        session = OktaProvider.setup_session(
            org_domain=OKTA_ORG_DOMAIN,
            client_id=OKTA_CLIENT_ID,
            private_key_file=str(key_file),
            scopes=["okta.policies.read", "okta.apps.read", "okta.users.read"],
        )
        assert session.scopes == [
            "okta.policies.read",
            "okta.apps.read",
            "okta.users.read",
        ]

    def test_custom_scopes_flattens_mixed_list_and_csv(self, _clear_okta_env, tmp_path):
        # Mirrors how argparse nargs="+" delivers values when a user
        # passes "--okta-scopes a,b c" — a list whose first element still
        # contains a comma.
        key_file = tmp_path / "key.pem"
        key_file.write_text(OKTA_PRIVATE_KEY)
        session = OktaProvider.setup_session(
            org_domain=OKTA_ORG_DOMAIN,
            client_id=OKTA_CLIENT_ID,
            private_key_file=str(key_file),
            scopes=["okta.policies.read,okta.apps.read", "okta.users.read"],
        )
        assert session.scopes == [
            "okta.policies.read",
            "okta.apps.read",
            "okta.users.read",
        ]

    def test_org_domain_normalized_lowercase_and_trimmed(
        self, _clear_okta_env, tmp_path
    ):
        # The provider lowercases and strips whitespace so that
        # "  ACME.okta.com  " is accepted as "acme.okta.com".
        key_file = tmp_path / "key.pem"
        key_file.write_text(OKTA_PRIVATE_KEY)
        session = OktaProvider.setup_session(
            org_domain="  ACME.okta.com  ",
            client_id=OKTA_CLIENT_ID,
            private_key_file=str(key_file),
        )
        assert session.org_domain == OKTA_ORG_DOMAIN

    def test_accepts_private_key_via_content_arg(self, _clear_okta_env):
        session = OktaProvider.setup_session(
            org_domain=OKTA_ORG_DOMAIN,
            client_id=OKTA_CLIENT_ID,
            private_key=OKTA_PRIVATE_KEY,
        )
        assert session.private_key == OKTA_PRIVATE_KEY

    def test_accepts_private_key_via_env_var(self, monkeypatch):
        monkeypatch.setenv("OKTA_PRIVATE_KEY", OKTA_PRIVATE_KEY)
        monkeypatch.delenv("OKTA_PRIVATE_KEY_FILE", raising=False)
        session = OktaProvider.setup_session(
            org_domain=OKTA_ORG_DOMAIN,
            client_id=OKTA_CLIENT_ID,
        )
        assert session.private_key == OKTA_PRIVATE_KEY

    def test_content_takes_precedence_over_file(self, _clear_okta_env, tmp_path):
        # File has stale content; explicit content arg should win.
        key_file = tmp_path / "stale.pem"
        key_file.write_text("STALE CONTENT FROM FILE")
        fresh_key = "-----BEGIN PRIVATE KEY-----\nFRESH\n-----END PRIVATE KEY-----"
        session = OktaProvider.setup_session(
            org_domain=OKTA_ORG_DOMAIN,
            client_id=OKTA_CLIENT_ID,
            private_key=fresh_key,
            private_key_file=str(key_file),
        )
        assert session.private_key == fresh_key


class Test_OktaProvider_setup_identity:
    def _session(self, tmp_path):
        key_file = tmp_path / "key.pem"
        key_file.write_text(OKTA_PRIVATE_KEY)
        return OktaProvider.setup_session(
            org_domain=OKTA_ORG_DOMAIN,
            client_id=OKTA_CLIENT_ID,
            private_key_file=str(key_file),
        )

    def test_synthesizes_identity_and_probes_successfully(
        self, _clear_okta_env, tmp_path
    ):
        session = self._session(tmp_path)

        async def fake_list_policies(*_a, **_k):
            return ([], mock.MagicMock(headers={}), None)

        with mock.patch(
            "prowler.providers.okta.okta_provider.OktaSDKClient"
        ) as mocked_client_cls:
            mocked = mock.MagicMock()
            mocked.list_policies = fake_list_policies
            mocked_client_cls.return_value = mocked
            identity = OktaProvider.setup_identity(session)

        assert identity.org_domain == OKTA_ORG_DOMAIN
        assert identity.client_id == OKTA_CLIENT_ID

    def test_populates_granted_scopes_from_access_token_scp_claim(
        self, _clear_okta_env, tmp_path
    ):
        session = self._session(tmp_path)

        async def fake_list_policies(*_a, **_k):
            return ([], mock.MagicMock(headers={}), None)

        with mock.patch(
            "prowler.providers.okta.okta_provider.OktaSDKClient"
        ) as mocked_client_cls:
            mocked = mock.MagicMock()
            mocked.list_policies = fake_list_policies
            mocked._request_executor._oauth._access_token = _make_jwt(
                {"scp": ["okta.policies.read", "okta.brands.read"]}
            )
            mocked_client_cls.return_value = mocked
            identity = OktaProvider.setup_identity(session)

        assert identity.granted_scopes == [
            "okta.policies.read",
            "okta.brands.read",
        ]

    def test_granted_scopes_empty_when_token_unavailable(
        self, _clear_okta_env, tmp_path
    ):
        session = self._session(tmp_path)

        async def fake_list_policies(*_a, **_k):
            return ([], mock.MagicMock(headers={}), None)

        with mock.patch(
            "prowler.providers.okta.okta_provider.OktaSDKClient"
        ) as mocked_client_cls:
            mocked = mock.MagicMock()
            mocked.list_policies = fake_list_policies
            mocked._request_executor._oauth._access_token = None
            mocked_client_cls.return_value = mocked
            identity = OktaProvider.setup_identity(session)

        assert identity.granted_scopes == []

    def test_raises_invalid_credentials_when_probe_returns_error(
        self, _clear_okta_env, tmp_path
    ):
        session = self._session(tmp_path)

        async def failing_list_policies(*_a, **_k):
            return ([], None, Exception("E0000011: Invalid token"))

        with mock.patch(
            "prowler.providers.okta.okta_provider.OktaSDKClient"
        ) as mocked_client_cls:
            mocked = mock.MagicMock()
            mocked.list_policies = failing_list_policies
            mocked_client_cls.return_value = mocked
            with pytest.raises(OktaInvalidCredentialsError):
                OktaProvider.setup_identity(session)

    def test_raises_insufficient_permissions_on_scope_error(
        self, _clear_okta_env, tmp_path
    ):
        session = self._session(tmp_path)

        async def failing_list_policies(*_a, **_k):
            return ([], None, Exception("invalid_scope: policies.read missing"))

        with mock.patch(
            "prowler.providers.okta.okta_provider.OktaSDKClient"
        ) as mocked_client_cls:
            mocked = mock.MagicMock()
            mocked.list_policies = failing_list_policies
            mocked_client_cls.return_value = mocked
            with pytest.raises(OktaInsufficientPermissionsError):
                OktaProvider.setup_identity(session)

    def test_raises_insufficient_permissions_on_forbidden(
        self, _clear_okta_env, tmp_path
    ):
        session = self._session(tmp_path)

        async def failing_list_policies(*_a, **_k):
            return ([], None, Exception("403 Forbidden"))

        with mock.patch(
            "prowler.providers.okta.okta_provider.OktaSDKClient"
        ) as mocked_client_cls:
            mocked = mock.MagicMock()
            mocked.list_policies = failing_list_policies
            mocked_client_cls.return_value = mocked
            with pytest.raises(OktaInsufficientPermissionsError):
                OktaProvider.setup_identity(session)

    def test_raises_insufficient_permissions_on_consent_required(
        self, _clear_okta_env, tmp_path
    ):
        # When zero requested scopes are consented on the service app, Okta
        # rejects the token request with HTTP 400 `consent_required` rather
        # than `invalid_scope` — must still be classified as a permission
        # gap so the user is pointed at the Okta API Scopes tab, not at
        # credential troubleshooting.
        session = self._session(tmp_path)

        async def failing_list_policies(*_a, **_k):
            return (
                [],
                None,
                Exception(
                    "Okta HTTP 400 consent_required You are not allowed any "
                    "of the requested scopes."
                ),
            )

        with mock.patch(
            "prowler.providers.okta.okta_provider.OktaSDKClient"
        ) as mocked_client_cls:
            mocked = mock.MagicMock()
            mocked.list_policies = failing_list_policies
            mocked_client_cls.return_value = mocked
            with pytest.raises(OktaInsufficientPermissionsError):
                OktaProvider.setup_identity(session)

    def test_wraps_unexpected_errors_in_setup_identity_error(
        self, _clear_okta_env, tmp_path
    ):
        session = self._session(tmp_path)

        async def boom(*_a, **_k):
            raise RuntimeError("network down")

        with mock.patch(
            "prowler.providers.okta.okta_provider.OktaSDKClient"
        ) as mocked_client_cls:
            mocked = mock.MagicMock()
            mocked.list_policies = boom
            mocked_client_cls.return_value = mocked
            with pytest.raises(OktaSetUpIdentityError):
                OktaProvider.setup_identity(session)


def _mock_setup_paths():
    """Patches that bypass the real SDK during provider construction."""
    session = OktaSession(
        org_domain=OKTA_ORG_DOMAIN,
        client_id=OKTA_CLIENT_ID,
        scopes=list(DEFAULT_SCOPES),
        private_key=OKTA_PRIVATE_KEY,
    )
    identity = OktaIdentityInfo(org_domain=OKTA_ORG_DOMAIN, client_id=OKTA_CLIENT_ID)
    return (
        mock.patch.object(OktaProvider, "validate_arguments"),
        mock.patch.object(OktaProvider, "setup_session", return_value=session),
        mock.patch.object(OktaProvider, "setup_identity", return_value=identity),
    )


class Test_OktaProvider_init:
    def test_init_end_to_end(self, _clear_okta_env, tmp_path):
        validate_p, session_p, identity_p = _mock_setup_paths()
        with validate_p, session_p, identity_p:
            provider = OktaProvider(
                okta_org_domain=OKTA_ORG_DOMAIN,
                okta_client_id=OKTA_CLIENT_ID,
                okta_private_key_file="/tmp/key.pem",
            )

        assert provider.type == "okta"
        assert provider.auth_method == "OAuth 2.0 (private-key JWT)"
        assert provider.identity.org_domain == OKTA_ORG_DOMAIN
        assert provider.identity.client_id == OKTA_CLIENT_ID
        assert provider.session.scopes == DEFAULT_SCOPES
        assert provider.audit_config is not None
        assert provider.mutelist is not None

    def test_default_max_retries_from_config_file(self, _clear_okta_env, tmp_path):
        validate_p, session_p, identity_p = _mock_setup_paths()
        with validate_p, session_p, identity_p:
            provider = OktaProvider(
                okta_org_domain=OKTA_ORG_DOMAIN,
                okta_client_id=OKTA_CLIENT_ID,
                okta_private_key_file="/tmp/key.pem",
            )

        # No CLI override: value comes from the bundled config.yaml default.
        assert provider.audit_config["okta_max_retries"] == 5

    def test_cli_retries_override_config_file(self, _clear_okta_env, tmp_path):
        validate_p, session_p, identity_p = _mock_setup_paths()
        with validate_p, session_p, identity_p:
            provider = OktaProvider(
                okta_org_domain=OKTA_ORG_DOMAIN,
                okta_client_id=OKTA_CLIENT_ID,
                okta_private_key_file="/tmp/key.pem",
                okta_retries_max_attempts=9,
            )

        assert provider.audit_config["okta_max_retries"] == 9

    def test_cli_retries_override_accepts_zero(self, _clear_okta_env, tmp_path):
        validate_p, session_p, identity_p = _mock_setup_paths()
        with validate_p, session_p, identity_p:
            provider = OktaProvider(
                okta_org_domain=OKTA_ORG_DOMAIN,
                okta_client_id=OKTA_CLIENT_ID,
                okta_private_key_file="/tmp/key.pem",
                okta_retries_max_attempts=0,
            )

        # 0 disables retries and must not be treated as "unset".
        assert provider.audit_config["okta_max_retries"] == 0

    def test_rate_limiter_built_from_config_file_default(
        self, _clear_okta_env, tmp_path
    ):
        validate_p, session_p, identity_p = _mock_setup_paths()
        with validate_p, session_p, identity_p:
            provider = OktaProvider(
                okta_org_domain=OKTA_ORG_DOMAIN,
                okta_client_id=OKTA_CLIENT_ID,
                okta_private_key_file="/tmp/key.pem",
            )

        # Bundled config.yaml enables throttling at 4 req/s.
        assert provider.rate_limiter is not None
        assert provider.audit_config["okta_requests_per_second"] == 4

    def test_cli_requests_per_second_override(self, _clear_okta_env, tmp_path):
        validate_p, session_p, identity_p = _mock_setup_paths()
        with validate_p, session_p, identity_p:
            provider = OktaProvider(
                okta_org_domain=OKTA_ORG_DOMAIN,
                okta_client_id=OKTA_CLIENT_ID,
                okta_private_key_file="/tmp/key.pem",
                okta_requests_per_second=10,
            )

        assert provider.audit_config["okta_requests_per_second"] == 10
        assert provider.rate_limiter is not None

    def test_requests_per_second_zero_disables_throttling(
        self, _clear_okta_env, tmp_path
    ):
        validate_p, session_p, identity_p = _mock_setup_paths()
        with validate_p, session_p, identity_p:
            provider = OktaProvider(
                okta_org_domain=OKTA_ORG_DOMAIN,
                okta_client_id=OKTA_CLIENT_ID,
                okta_private_key_file="/tmp/key.pem",
                okta_requests_per_second=0,
            )

        assert provider.rate_limiter is None


class Test_OktaProvider_test_connection:
    def test_success(self, _clear_okta_env, tmp_path):
        validate_p, session_p, identity_p = _mock_setup_paths()
        with validate_p, session_p, identity_p:
            connection = OktaProvider.test_connection(
                okta_org_domain=OKTA_ORG_DOMAIN,
                okta_client_id=OKTA_CLIENT_ID,
                okta_private_key_file="/tmp/key.pem",
            )
        assert connection.is_connected is True
        assert connection.error is None

    def test_returns_error_when_raise_disabled(self, _clear_okta_env):
        connection = OktaProvider.test_connection(raise_on_exception=False)
        assert connection.is_connected is False
        assert connection.error is not None

    def test_raises_when_raise_enabled(self, _clear_okta_env):
        with pytest.raises(OktaEnvironmentVariableError):
            OktaProvider.test_connection()

    def test_provider_id_match_succeeds(self, _clear_okta_env, tmp_path):
        validate_p, session_p, identity_p = _mock_setup_paths()
        with validate_p, session_p, identity_p:
            connection = OktaProvider.test_connection(
                okta_org_domain=OKTA_ORG_DOMAIN,
                okta_client_id=OKTA_CLIENT_ID,
                okta_private_key_file="/tmp/key.pem",
                provider_id=OKTA_ORG_DOMAIN,
            )
        assert connection.is_connected is True
        assert connection.error is None

    def test_provider_id_match_is_case_insensitive(self, _clear_okta_env, tmp_path):
        validate_p, session_p, identity_p = _mock_setup_paths()
        with validate_p, session_p, identity_p:
            connection = OktaProvider.test_connection(
                okta_org_domain=OKTA_ORG_DOMAIN,
                okta_client_id=OKTA_CLIENT_ID,
                okta_private_key_file="/tmp/key.pem",
                provider_id=OKTA_ORG_DOMAIN.upper(),
            )
        assert connection.is_connected is True

    def test_provider_id_mismatch_raises(self, _clear_okta_env, tmp_path):
        validate_p, session_p, identity_p = _mock_setup_paths()
        with validate_p, session_p, identity_p:
            with pytest.raises(OktaInvalidProviderIdError):
                OktaProvider.test_connection(
                    okta_org_domain=OKTA_ORG_DOMAIN,
                    okta_client_id=OKTA_CLIENT_ID,
                    okta_private_key_file="/tmp/key.pem",
                    provider_id="other.okta.com",
                )

    def test_provider_id_mismatch_returns_error_when_raise_disabled(
        self, _clear_okta_env, tmp_path
    ):
        validate_p, session_p, identity_p = _mock_setup_paths()
        with validate_p, session_p, identity_p:
            connection = OktaProvider.test_connection(
                okta_org_domain=OKTA_ORG_DOMAIN,
                okta_client_id=OKTA_CLIENT_ID,
                okta_private_key_file="/tmp/key.pem",
                provider_id="other.okta.com",
                raise_on_exception=False,
            )
        assert connection.is_connected is False
        assert isinstance(connection.error, OktaInvalidProviderIdError)


class Test_OktaProvider_print_credentials:
    def test_invokes_print_boxes_with_org_and_client(self, _clear_okta_env, tmp_path):
        validate_p, session_p, identity_p = _mock_setup_paths()
        with (
            validate_p,
            session_p,
            identity_p,
            mock.patch(
                "prowler.providers.okta.okta_provider.print_boxes"
            ) as mock_print,
        ):
            provider = OktaProvider(
                okta_org_domain=OKTA_ORG_DOMAIN,
                okta_client_id=OKTA_CLIENT_ID,
                okta_private_key_file="/tmp/key.pem",
            )
            provider.print_credentials()

        mock_print.assert_called_once()
        rendered = " ".join(mock_print.call_args.args[0])
        assert OKTA_ORG_DOMAIN in rendered
        assert OKTA_CLIENT_ID in rendered
        assert "OAuth 2.0" in rendered
