"""Unit tests for Client Credentials Flow authentication."""

import unittest
from unittest.mock import Mock, patch, MagicMock
import requests

from src.blackant.auth.blackant_auth import BlackAntAuth
from src.blackant.exceptions import BlackAntAuthenticationError
from src.blackant.client import BlackAntClient


class TestClientCredentialsAuth(unittest.TestCase):
    """Test cases for Client Credentials authentication flow."""

    def setUp(self):
        """Set up test fixtures."""
        self.client_id = "test-service-account"
        self.client_secret = "test-secret-xyz123"
        self.login_url = "https://test.blackant.app/api/auth/login"

    def tearDown(self):
        """Clean up after tests."""
        pass

    def test_init_with_client_credentials(self):
        """Test BlackAntAuth initialization with client credentials."""
        auth = BlackAntAuth(
            client_id=self.client_id,
            client_secret=self.client_secret,
            login_url=self.login_url
        )

        self.assertEqual(auth.client_id, self.client_id)
        self.assertEqual(auth.client_secret, self.client_secret)
        self.assertEqual(auth.login_url, self.login_url)
        self.assertEqual(auth._auth_mode, "client_credentials")
        self.assertFalse(auth._authenticated)

    def test_init_with_password_flow(self):
        """Test BlackAntAuth initialization with password flow."""
        auth = BlackAntAuth(
            user="testuser",
            password="testpass",
            login_url=self.login_url
        )

        self.assertEqual(auth.user, "testuser")
        self.assertEqual(auth.password, "testpass")
        self.assertEqual(auth._auth_mode, "password")

    def test_init_without_credentials_raises_error(self):
        """Test that initialization without credentials raises ValueError."""
        with self.assertRaises(ValueError) as context:
            BlackAntAuth(login_url=self.login_url)

        self.assertIn("Either (user+password) or (client_id+client_secret)", str(context.exception))

    def test_init_with_both_credentials_raises_error(self):
        """Test that providing both auth methods raises ValueError."""
        with self.assertRaises(ValueError) as context:
            BlackAntAuth(
                user="testuser",
                password="testpass",
                client_id=self.client_id,
                client_secret=self.client_secret,
                login_url=self.login_url
            )

        self.assertIn("not both", str(context.exception))

    @patch('src.blackant.auth.blackant_auth.requests.post')
    def test_authenticate_with_client_credentials_success(self, mock_post):
        """Test successful client credentials authentication."""
        # Mock successful response
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "access_token": "test_access_token_xyz",
            "token_type": "Bearer",
            "expires_in": 3600
        }
        mock_response.raise_for_status = Mock()
        mock_post.return_value = mock_response

        auth = BlackAntAuth(
            client_id=self.client_id,
            client_secret=self.client_secret,
            login_url=self.login_url
        )

        token = auth.authenticate_with_client_credentials()

        # Verify token returned and stored
        self.assertEqual(token, "test_access_token_xyz")
        self.assertEqual(auth.token_store.user_token, "test_access_token_xyz")
        self.assertTrue(auth._authenticated)

        # Verify request was made with correct parameters
        mock_post.assert_called_once()
        call_args = mock_post.call_args
        self.assertEqual(call_args[0][0], self.login_url)

        # Check request data
        request_data = call_args[1]['data']
        self.assertEqual(request_data['grant_type'], 'client_credentials')
        self.assertEqual(request_data['client_id'], self.client_id)
        self.assertEqual(request_data['client_secret'], self.client_secret)

    @patch('src.blackant.auth.blackant_auth.requests.post')
    def test_authenticate_with_client_credentials_no_token_in_response(self, mock_post):
        """Test client credentials auth fails when no access_token in response."""
        # Mock response without access_token
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"error": "invalid_client"}
        mock_response.raise_for_status = Mock()
        mock_post.return_value = mock_response

        auth = BlackAntAuth(
            client_id=self.client_id,
            client_secret=self.client_secret,
            login_url=self.login_url
        )

        with self.assertRaises(BlackAntAuthenticationError) as context:
            auth.authenticate_with_client_credentials()

        self.assertIn("No access_token", str(context.exception))
        self.assertFalse(auth._authenticated)

    @patch('src.blackant.auth.blackant_auth.requests.post')
    def test_authenticate_with_client_credentials_network_error(self, mock_post):
        """Test client credentials auth handles network errors."""
        # Mock network error
        mock_post.side_effect = requests.exceptions.ConnectionError("Network error")

        auth = BlackAntAuth(
            client_id=self.client_id,
            client_secret=self.client_secret,
            login_url=self.login_url
        )

        with self.assertRaises(BlackAntAuthenticationError) as context:
            auth.authenticate_with_client_credentials()

        self.assertIn("Client credentials authentication failed", str(context.exception))
        self.assertFalse(auth._authenticated)

    @patch('src.blackant.auth.blackant_auth.requests.post')
    def test_authenticate_with_client_credentials_http_error(self, mock_post):
        """Test client credentials auth handles HTTP errors (401, 403, etc)."""
        # Mock HTTP 401 error
        mock_response = Mock()
        mock_response.status_code = 401
        mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("401 Unauthorized")
        mock_post.return_value = mock_response

        auth = BlackAntAuth(
            client_id=self.client_id,
            client_secret=self.client_secret,
            login_url=self.login_url
        )

        with self.assertRaises(BlackAntAuthenticationError) as context:
            auth.authenticate_with_client_credentials()

        self.assertIn("Client credentials authentication failed", str(context.exception))

    @patch('src.blackant.auth.blackant_auth.requests.post')
    def test_authenticate_auto_selects_client_credentials(self, mock_post):
        """Test that authenticate() automatically selects client credentials flow."""
        # Mock successful response
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"access_token": "test_token"}
        mock_response.raise_for_status = Mock()
        mock_post.return_value = mock_response

        auth = BlackAntAuth(
            client_id=self.client_id,
            client_secret=self.client_secret,
            login_url=self.login_url
        )

        # Call authenticate() without specifying flow
        token = auth.authenticate()

        # Should use client credentials flow
        self.assertEqual(token, "test_token")
        call_args = mock_post.call_args[1]['data']
        self.assertEqual(call_args['grant_type'], 'client_credentials')


class TestBlackAntClientWithClientCredentials(unittest.TestCase):
    """Test cases for BlackAntClient with client credentials."""

    def setUp(self):
        """Set up test fixtures."""
        self.client_id = "test-service"
        self.client_secret = "secret123"

    @patch('src.blackant.client.ServiceRegistry')
    @patch('src.blackant.client.BlackAntDockerClient')
    @patch('src.blackant.client.HTTPClient')
    def test_client_init_with_client_credentials(self, mock_http, mock_docker, mock_service):
        """Test BlackAntClient initialization with client credentials."""
        client = BlackAntClient(
            client_id=self.client_id,
            client_secret=self.client_secret
        )

        self.assertIsNotNone(client.auth)
        self.assertEqual(client.auth.client_id, self.client_id)
        self.assertEqual(client.auth.client_secret, self.client_secret)
        self.assertEqual(client.auth._auth_mode, "client_credentials")

    @patch('src.blackant.client.ServiceRegistry')
    @patch('src.blackant.client.BlackAntDockerClient')
    @patch('src.blackant.client.HTTPClient')
    def test_client_init_with_password(self, mock_http, mock_docker, mock_service):
        """Test BlackAntClient initialization with password."""
        client = BlackAntClient(
            user="testuser",
            password="testpass"
        )

        self.assertEqual(client.auth.user, "testuser")
        self.assertEqual(client.auth._auth_mode, "password")

    def test_client_init_without_credentials_raises_error(self):
        """Test that BlackAntClient without credentials raises error."""
        with self.assertRaises(ValueError) as context:
            BlackAntClient()

        self.assertIn("Provide either", str(context.exception))

    @patch('src.blackant.client.ServiceRegistry')
    @patch('src.blackant.client.BlackAntDockerClient')
    @patch('src.blackant.client.HTTPClient')
    def test_client_repr_with_client_credentials(self, mock_http, mock_docker, mock_service):
        """Test __repr__ with client credentials."""
        client = BlackAntClient(
            client_id=self.client_id,
            client_secret=self.client_secret
        )

        repr_str = repr(client)
        self.assertIn("client_id=test-service", repr_str)
        self.assertIn("authenticated=False", repr_str)

    @patch('src.blackant.client.ServiceRegistry')
    @patch('src.blackant.client.BlackAntDockerClient')
    @patch('src.blackant.client.HTTPClient')
    @patch('src.blackant.auth.blackant_auth.requests.post')
    def test_client_authenticate_with_client_credentials(self, mock_post, mock_http, mock_docker, mock_service):
        """Test client authenticate() with client credentials."""
        # Mock successful auth response
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"access_token": "test_token_xyz"}
        mock_response.raise_for_status = Mock()
        mock_post.return_value = mock_response

        client = BlackAntClient(
            client_id=self.client_id,
            client_secret=self.client_secret
        )

        result = client.authenticate()

        self.assertTrue(result)
        self.assertTrue(client.is_authenticated())


if __name__ == "__main__":
    unittest.main()
