"""
Unit tests for JWT with server_id parameter (US-006).

Tests cover:
- TokenData model includes server_id field
- create_access_token includes server_id in payload
- create_refresh_token includes server_id in payload
- create_token_response passes server_id to both methods
- refresh_access_token preserves server_id from refresh token
- verify_token correctly extracts server_id
- Middleware extracts server_id into request.state

US-006: JWT cu Server ID

Note: These tests mock dependencies where necessary to avoid importing
oracledb which requires Oracle Instant Client.
"""
import pytest
from unittest.mock import MagicMock, AsyncMock, patch
import sys
import os
from datetime import datetime, timedelta

# Add project paths
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../shared'))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../'))


class TestTokenDataModel:
    """Tests for TokenData model with server_id."""

    def test_token_data_has_server_id_field(self):
        """Test TokenData model has server_id field."""
        from auth.jwt_handler import TokenData

        # Check field exists in model
        assert 'server_id' in TokenData.model_fields

    def test_token_data_server_id_is_optional(self):
        """Test server_id field is optional with None default."""
        from auth.jwt_handler import TokenData

        field_info = TokenData.model_fields['server_id']
        # Check that default is None (optional field)
        assert field_info.default is None

    def test_token_data_parses_server_id_from_payload(self):
        """Test TokenData correctly parses server_id from JWT payload."""
        from auth.jwt_handler import TokenData

        now = datetime.utcnow()
        payload = {
            "username": "testuser",
            "user_id": 123,
            "companies": ["FIRMA1"],
            "permissions": ["read"],
            "server_id": "romfast",
            "exp": now + timedelta(hours=1),
            "iat": now,
            "type": "access"
        }

        token_data = TokenData(**payload)

        assert token_data.server_id == "romfast"
        assert token_data.username == "testuser"

    def test_token_data_parses_null_server_id(self):
        """Test TokenData handles null server_id (single-server mode)."""
        from auth.jwt_handler import TokenData

        now = datetime.utcnow()
        payload = {
            "username": "testuser",
            "companies": ["FIRMA1"],
            "permissions": ["read"],
            "server_id": None,
            "exp": now + timedelta(hours=1),
            "iat": now,
            "type": "access"
        }

        token_data = TokenData(**payload)

        assert token_data.server_id is None


class TestCreateAccessToken:
    """Tests for create_access_token with server_id."""

    def test_create_access_token_signature_has_server_id(self):
        """Test create_access_token accepts server_id parameter."""
        import inspect
        from auth.jwt_handler import JWTHandler

        sig = inspect.signature(JWTHandler.create_access_token)
        params = list(sig.parameters.keys())

        assert 'server_id' in params
        assert sig.parameters['server_id'].default is None

    def test_create_access_token_includes_server_id_in_payload(self):
        """Test server_id is included in JWT payload when provided."""
        from auth.jwt_handler import JWTHandler
        from jose import jwt

        handler = JWTHandler(secret_key="test-secret")

        token = handler.create_access_token(
            username="testuser",
            companies=["FIRMA1"],
            server_id="romfast"
        )

        # Decode without verification to check payload
        payload = jwt.decode(token, "test-secret", algorithms=["HS256"])

        assert payload['server_id'] == "romfast"
        assert payload['username'] == "testuser"
        assert payload['type'] == "access"

    def test_create_access_token_without_server_id(self):
        """Test token creation works without server_id (backward compatible)."""
        from auth.jwt_handler import JWTHandler
        from jose import jwt

        handler = JWTHandler(secret_key="test-secret")

        token = handler.create_access_token(
            username="testuser",
            companies=["FIRMA1"]
        )

        payload = jwt.decode(token, "test-secret", algorithms=["HS256"])

        assert payload['server_id'] is None
        assert payload['username'] == "testuser"


class TestCreateRefreshToken:
    """Tests for create_refresh_token with server_id."""

    def test_create_refresh_token_signature_has_server_id(self):
        """Test create_refresh_token accepts server_id parameter."""
        import inspect
        from auth.jwt_handler import JWTHandler

        sig = inspect.signature(JWTHandler.create_refresh_token)
        params = list(sig.parameters.keys())

        assert 'server_id' in params
        assert sig.parameters['server_id'].default is None

    def test_create_refresh_token_includes_server_id_in_payload(self):
        """Test server_id is included in refresh token payload."""
        from auth.jwt_handler import JWTHandler
        from jose import jwt

        handler = JWTHandler(secret_key="test-secret")

        token = handler.create_refresh_token(
            username="testuser",
            server_id="romfast"
        )

        payload = jwt.decode(token, "test-secret", algorithms=["HS256"])

        assert payload['server_id'] == "romfast"
        assert payload['type'] == "refresh"

    def test_create_refresh_token_without_server_id(self):
        """Test refresh token works without server_id."""
        from auth.jwt_handler import JWTHandler
        from jose import jwt

        handler = JWTHandler(secret_key="test-secret")

        token = handler.create_refresh_token(username="testuser")

        payload = jwt.decode(token, "test-secret", algorithms=["HS256"])

        assert payload['server_id'] is None


class TestCreateTokenResponse:
    """Tests for create_token_response with server_id."""

    def test_create_token_response_signature_has_server_id(self):
        """Test create_token_response accepts server_id parameter."""
        import inspect
        from auth.jwt_handler import JWTHandler

        sig = inspect.signature(JWTHandler.create_token_response)
        params = list(sig.parameters.keys())

        assert 'server_id' in params
        assert sig.parameters['server_id'].default is None

    def test_create_token_response_passes_server_id_to_both_tokens(self):
        """Test server_id is passed to both access and refresh tokens."""
        from auth.jwt_handler import JWTHandler
        from jose import jwt

        handler = JWTHandler(secret_key="test-secret")

        response = handler.create_token_response(
            username="testuser",
            companies=["FIRMA1"],
            server_id="romfast"
        )

        # Check access token
        access_payload = jwt.decode(
            response.access_token, "test-secret", algorithms=["HS256"]
        )
        assert access_payload['server_id'] == "romfast"

        # Check refresh token
        refresh_payload = jwt.decode(
            response.refresh_token, "test-secret", algorithms=["HS256"]
        )
        assert refresh_payload['server_id'] == "romfast"


class TestRefreshAccessToken:
    """Tests for refresh_access_token preserving server_id."""

    def test_refresh_access_token_preserves_server_id(self):
        """Test that server_id from refresh token is preserved in new access token."""
        from auth.jwt_handler import JWTHandler
        from jose import jwt

        handler = JWTHandler(secret_key="test-secret")

        # Create refresh token with server_id
        refresh_token = handler.create_refresh_token(
            username="testuser",
            server_id="romfast"
        )

        # Refresh to get new access token
        new_access_token = handler.refresh_access_token(
            refresh_token=refresh_token,
            companies=["FIRMA1", "FIRMA2"]
        )

        assert new_access_token is not None

        # Verify server_id is preserved
        payload = jwt.decode(new_access_token, "test-secret", algorithms=["HS256"])

        assert payload['server_id'] == "romfast"
        assert payload['companies'] == ["FIRMA1", "FIRMA2"]

    def test_refresh_access_token_preserves_null_server_id(self):
        """Test that null server_id is preserved in refreshed token."""
        from auth.jwt_handler import JWTHandler
        from jose import jwt

        handler = JWTHandler(secret_key="test-secret")

        # Create refresh token without server_id (single-server mode)
        refresh_token = handler.create_refresh_token(username="testuser")

        # Refresh
        new_access_token = handler.refresh_access_token(
            refresh_token=refresh_token,
            companies=["FIRMA1"]
        )

        payload = jwt.decode(new_access_token, "test-secret", algorithms=["HS256"])

        assert payload['server_id'] is None


class TestVerifyToken:
    """Tests for verify_token with server_id extraction."""

    def test_verify_token_extracts_server_id(self):
        """Test verify_token correctly extracts server_id from payload."""
        from auth.jwt_handler import JWTHandler

        handler = JWTHandler(secret_key="test-secret")

        # Create token with server_id
        token = handler.create_access_token(
            username="testuser",
            companies=["FIRMA1"],
            server_id="romfast"
        )

        # Verify and extract
        token_data = handler.verify_token(token)

        assert token_data is not None
        assert token_data.server_id == "romfast"
        assert token_data.username == "testuser"

    def test_verify_token_handles_null_server_id(self):
        """Test verify_token handles null server_id correctly."""
        from auth.jwt_handler import JWTHandler

        handler = JWTHandler(secret_key="test-secret")

        token = handler.create_access_token(
            username="testuser",
            companies=["FIRMA1"]
        )

        token_data = handler.verify_token(token)

        assert token_data is not None
        assert token_data.server_id is None


class TestMiddlewareServerIdExtraction:
    """Tests for middleware extracting server_id into request.state."""

    def test_middleware_create_current_user_preserves_token_data(self):
        """Test that middleware sets token_data which includes server_id."""
        # The middleware sets request.state.token_data which contains server_id
        # Read the source file directly to avoid oracledb dependency
        middleware_path = os.path.join(
            os.path.dirname(__file__),
            '../../shared/auth/middleware.py'
        )
        with open(middleware_path, 'r') as f:
            source = f.read()

        # Verify the middleware sets token_data on request.state
        assert 'request.state.token_data = token_data' in source

    def test_middleware_extracts_server_id_from_token_data(self):
        """Test that middleware extracts server_id into request.state.server_id."""
        # Read the source file directly to avoid oracledb dependency
        middleware_path = os.path.join(
            os.path.dirname(__file__),
            '../../shared/auth/middleware.py'
        )
        with open(middleware_path, 'r') as f:
            source = f.read()

        # Verify the code contains server_id extraction
        assert 'request.state.server_id' in source


class TestAuthServiceJWTIntegration:
    """Tests for auth_service passing server_id to jwt_handler."""

    def test_authenticate_and_create_tokens_passes_server_id(self):
        """Test that auth_service passes server_id to jwt_handler."""
        # Read the source file directly to avoid oracledb dependency
        auth_service_path = os.path.join(
            os.path.dirname(__file__),
            '../../shared/auth/auth_service.py'
        )
        with open(auth_service_path, 'r') as f:
            source = f.read()

        # Verify server_id is passed to create_token_response
        assert 'server_id=server_id' in source


class TestBackwardCompatibility:
    """Tests ensuring backward compatibility when server_id is not provided."""

    def test_token_creation_without_server_id(self):
        """Test all token creation methods work without server_id."""
        from auth.jwt_handler import JWTHandler

        handler = JWTHandler(secret_key="test-secret")

        # Access token
        access = handler.create_access_token(username="user", companies=[])
        assert access is not None

        # Refresh token
        refresh = handler.create_refresh_token(username="user")
        assert refresh is not None

        # Token response
        response = handler.create_token_response(username="user", companies=[])
        assert response is not None
        assert response.access_token is not None
        assert response.refresh_token is not None

    def test_token_verification_without_server_id(self):
        """Test token verification works for tokens without server_id."""
        from auth.jwt_handler import JWTHandler

        handler = JWTHandler(secret_key="test-secret")

        # Create token without server_id
        token = handler.create_access_token(username="user", companies=[])

        # Verify should work
        token_data = handler.verify_token(token)

        assert token_data is not None
        assert token_data.username == "user"
        assert token_data.server_id is None


class TestAcceptanceCriteria:
    """Tests validating all acceptance criteria for US-006."""

    def test_ac1_jwt_handler_includes_server_id_in_payload(self):
        """AC1: jwt_handler.py include server_id în payload la generare token."""
        from auth.jwt_handler import JWTHandler
        from jose import jwt

        handler = JWTHandler(secret_key="test-secret")

        token = handler.create_access_token(
            username="testuser",
            companies=["FIRMA1"],
            server_id="romfast"
        )

        payload = jwt.decode(token, "test-secret", algorithms=["HS256"])

        assert 'server_id' in payload
        assert payload['server_id'] == "romfast"

    def test_ac2_middleware_extracts_server_id_to_request_state(self):
        """AC2: Middleware extrage server_id din token și îl pune în request.state."""
        # Read the source file directly to avoid oracledb dependency
        middleware_path = os.path.join(
            os.path.dirname(__file__),
            '../../shared/auth/middleware.py'
        )
        with open(middleware_path, 'r') as f:
            source = f.read()

        # Verify server_id is set on request.state
        assert 'request.state.server_id' in source

    def test_ac3_all_oracle_queries_should_use_server_id(self):
        """AC3: Toate query-urile Oracle folosesc request.state.server_id pentru pool."""
        # This is an integration test - we verify the middleware sets server_id
        # which should then be used by routes/services

        from auth.jwt_handler import TokenData

        # Verify TokenData has server_id field
        assert 'server_id' in TokenData.model_fields

    def test_ac4_jwt_decode_validates_server_id_presence(self):
        """AC4: JWT decode validează prezența server_id."""
        from auth.jwt_handler import JWTHandler

        handler = JWTHandler(secret_key="test-secret")

        # Create and verify token with server_id
        token = handler.create_access_token(
            username="user",
            companies=[],
            server_id="romfast"
        )

        token_data = handler.verify_token(token)

        assert token_data is not None
        assert hasattr(token_data, 'server_id')
        assert token_data.server_id == "romfast"


class TestJWTPayloadStructure:
    """Tests verifying JWT payload structure includes server_id."""

    def test_access_token_payload_structure(self):
        """Test access token has complete payload structure with server_id."""
        from auth.jwt_handler import JWTHandler
        from jose import jwt

        handler = JWTHandler(secret_key="test-secret")

        token = handler.create_access_token(
            username="testuser",
            companies=["FIRMA1", "FIRMA2"],
            user_id=123,
            permissions=["read", "write"],
            server_id="romfast"
        )

        payload = jwt.decode(token, "test-secret", algorithms=["HS256"])

        # Verify all expected fields
        expected_fields = [
            'username', 'user_id', 'companies', 'permissions',
            'server_id', 'exp', 'iat', 'type'
        ]

        for field in expected_fields:
            assert field in payload, f"Missing field: {field}"

        assert payload['server_id'] == "romfast"
        assert payload['type'] == "access"

    def test_refresh_token_payload_structure(self):
        """Test refresh token has correct payload structure with server_id."""
        from auth.jwt_handler import JWTHandler
        from jose import jwt

        handler = JWTHandler(secret_key="test-secret")

        token = handler.create_refresh_token(
            username="testuser",
            user_id=123,
            server_id="romfast"
        )

        payload = jwt.decode(token, "test-secret", algorithms=["HS256"])

        # Verify expected fields for refresh token
        expected_fields = ['username', 'user_id', 'server_id', 'exp', 'iat', 'type']

        for field in expected_fields:
            assert field in payload, f"Missing field: {field}"

        assert payload['server_id'] == "romfast"
        assert payload['type'] == "refresh"
