import os
from unittest import mock
from unittest.mock import MagicMock, patch

import pytest

from prowler.providers.common.models import Connection
from prowler.providers.vercel.exceptions.exceptions import (
    VercelAuthenticationError,
    VercelCredentialsError,
)
from prowler.providers.vercel.models import VercelIdentityInfo, VercelSession
from prowler.providers.vercel.vercel_provider import VercelProvider
from tests.providers.vercel.vercel_fixtures import (
    API_TOKEN,
    TEAM_ID,
    TEAM_NAME,
    TEAM_SLUG,
    USER_EMAIL,
    USER_ID,
    USERNAME,
)


class TestVercelProviderSetupSession:
    def test_setup_session_with_env_var(self):
        with mock.patch.dict(os.environ, {"VERCEL_TOKEN": API_TOKEN}, clear=False):
            session = VercelProvider.setup_session()

        assert isinstance(session, VercelSession)
        assert session.token == API_TOKEN
        assert session.http_session is not None

    def test_setup_session_with_api_token_param(self):
        with mock.patch.dict(os.environ, {}, clear=True):
            session = VercelProvider.setup_session(api_token=API_TOKEN)

        assert isinstance(session, VercelSession)
        assert session.token == API_TOKEN
        assert session.http_session is not None

    def test_setup_session_with_team_id_param(self):
        with mock.patch.dict(os.environ, {}, clear=True):
            session = VercelProvider.setup_session(api_token=API_TOKEN, team_id=TEAM_ID)

        assert session.token == API_TOKEN
        assert session.team_id == TEAM_ID

    def test_setup_session_no_credentials_raises(self):
        with mock.patch.dict(os.environ, {}, clear=True):
            os.environ.pop("VERCEL_TOKEN", None)
            with pytest.raises(VercelCredentialsError):
                VercelProvider.setup_session()

    def test_setup_session_team_from_env(self):
        with mock.patch.dict(
            os.environ, {"VERCEL_TOKEN": API_TOKEN, "VERCEL_TEAM": TEAM_ID}
        ):
            session = VercelProvider.setup_session()

        assert session.team_id == TEAM_ID


class TestVercelProviderSetupIdentity:
    def test_setup_identity_with_team(self):
        mock_session = VercelSession(
            token=API_TOKEN, team_id=TEAM_ID, http_session=MagicMock()
        )

        # Mock user response
        user_response = MagicMock()
        user_response.status_code = 200
        user_response.json.return_value = {
            "user": {
                "id": USER_ID,
                "username": USERNAME,
                "email": USER_EMAIL,
            }
        }
        user_response.raise_for_status = MagicMock()

        # Mock team response
        team_response = MagicMock()
        team_response.status_code = 200
        team_response.json.return_value = {
            "id": TEAM_ID,
            "name": TEAM_NAME,
            "slug": TEAM_SLUG,
        }
        team_response.raise_for_status = MagicMock()

        def mock_get(url, **kwargs):
            if "/v2/user" in url:
                return user_response
            if f"/v2/teams/{TEAM_ID}" in url:
                return team_response
            return MagicMock()

        mock_session.http_session.get = mock_get

        identity = VercelProvider.setup_identity(mock_session)

        assert isinstance(identity, VercelIdentityInfo)
        assert identity.user_id == USER_ID
        assert identity.username == USERNAME
        assert identity.email == USER_EMAIL
        assert identity.team is not None
        assert identity.team.id == TEAM_ID
        assert identity.team.name == TEAM_NAME
        assert identity.team.slug == TEAM_SLUG

    def test_setup_identity_personal_account(self):
        mock_session = VercelSession(
            token=API_TOKEN, team_id=None, http_session=MagicMock()
        )

        user_response = MagicMock()
        user_response.status_code = 200
        user_response.json.return_value = {
            "user": {
                "id": USER_ID,
                "username": USERNAME,
                "email": USER_EMAIL,
            }
        }
        user_response.raise_for_status = MagicMock()

        mock_session.http_session.get = MagicMock(return_value=user_response)

        identity = VercelProvider.setup_identity(mock_session)

        assert identity.team is None
        assert identity.user_id == USER_ID


class TestVercelProviderValidateCredentials:
    def test_valid_credentials(self):
        mock_session = VercelSession(
            token=API_TOKEN, team_id=None, http_session=MagicMock()
        )

        response = MagicMock()
        response.status_code = 200
        response.raise_for_status = MagicMock()
        mock_session.http_session.get = MagicMock(return_value=response)

        # Should not raise
        VercelProvider.validate_credentials(mock_session)

    def test_invalid_token_raises(self):
        mock_session = VercelSession(
            token="invalid", team_id=None, http_session=MagicMock()
        )

        response = MagicMock()
        response.status_code = 401
        mock_session.http_session.get = MagicMock(return_value=response)

        with pytest.raises(VercelAuthenticationError):
            VercelProvider.validate_credentials(mock_session)

    def test_forbidden_raises(self):
        mock_session = VercelSession(
            token=API_TOKEN, team_id=None, http_session=MagicMock()
        )

        response = MagicMock()
        response.status_code = 403
        mock_session.http_session.get = MagicMock(return_value=response)

        with pytest.raises(VercelAuthenticationError):
            VercelProvider.validate_credentials(mock_session)


class TestVercelProviderTestConnection:
    @patch.object(VercelProvider, "validate_credentials")
    @patch.object(VercelProvider, "setup_session")
    def test_successful_connection(self, mock_setup_session, mock_validate):
        mock_setup_session.return_value = VercelSession(
            token=API_TOKEN, team_id=None, http_session=MagicMock()
        )
        mock_validate.return_value = None

        result = VercelProvider.test_connection(raise_on_exception=False)

        assert isinstance(result, Connection)
        assert result.is_connected is True

    @patch.object(VercelProvider, "validate_credentials")
    @patch.object(VercelProvider, "setup_session")
    def test_successful_connection_with_params(self, mock_setup_session, mock_validate):
        mock_setup_session.return_value = VercelSession(
            token=API_TOKEN, team_id=TEAM_ID, http_session=MagicMock()
        )
        mock_validate.return_value = None

        result = VercelProvider.test_connection(
            api_token=API_TOKEN, team_id=TEAM_ID, raise_on_exception=False
        )

        assert isinstance(result, Connection)
        assert result.is_connected is True
        mock_setup_session.assert_called_once_with(api_token=API_TOKEN, team_id=TEAM_ID)

    @patch.object(VercelProvider, "setup_session")
    def test_failed_connection_no_credentials(self, mock_setup_session):
        mock_setup_session.side_effect = VercelCredentialsError(
            message="No credentials"
        )

        result = VercelProvider.test_connection(raise_on_exception=False)

        assert isinstance(result, Connection)
        assert result.is_connected is False
        assert result.error is not None

    @patch.object(VercelProvider, "setup_session")
    def test_failed_connection_raises(self, mock_setup_session):
        mock_setup_session.side_effect = VercelCredentialsError(
            message="No credentials"
        )

        with pytest.raises(VercelCredentialsError):
            VercelProvider.test_connection(raise_on_exception=True)


class TestVercelSessionTokenSecurity:
    """The Vercel API token must never leak through serialization or repr."""

    def test_token_is_still_accessible_as_attribute(self):
        session = VercelSession(token=API_TOKEN, team_id=TEAM_ID)

        assert session.token == API_TOKEN

    def test_token_excluded_from_dict(self):
        session = VercelSession(token=API_TOKEN, team_id=TEAM_ID)

        serialized = session.dict()

        assert "token" not in serialized
        assert API_TOKEN not in str(serialized)
        assert serialized["team_id"] == TEAM_ID

    def test_token_excluded_from_model_dump(self):
        session = VercelSession(token=API_TOKEN, team_id=TEAM_ID)

        serialized = session.model_dump()

        assert "token" not in serialized
        assert API_TOKEN not in str(serialized)

    def test_token_excluded_from_json(self):
        session = VercelSession(token=API_TOKEN, team_id=TEAM_ID)

        serialized = session.json()

        assert "token" not in serialized
        assert API_TOKEN not in serialized

    def test_token_excluded_from_model_dump_json(self):
        session = VercelSession(token=API_TOKEN, team_id=TEAM_ID)

        serialized = session.model_dump_json()

        assert "token" not in serialized
        assert API_TOKEN not in serialized

    def test_token_excluded_from_repr_and_str(self):
        session = VercelSession(token=API_TOKEN, team_id=TEAM_ID)

        assert API_TOKEN not in repr(session)
        assert API_TOKEN not in str(session)
        assert "token" not in repr(session)
