"""Unit tests for CredentialMiddleware.

This module contains comprehensive tests for the credential extraction
middleware, including API key and MetaInfo JWT validation.
"""

import tempfile
from pathlib import Path

import pytest
from fastapi import FastAPI, Request
from starlette.testclient import TestClient

from hezor_common.security.jwt import encode_jwt_with_pem
from hezor_common.transfer.base_sdk.constants import (
    ANONYMOUS_HEADER_PRIVATE_KEY,
    ANONYMOUS_HEADER_PRIVATE_KEY_PASSWORD,
    ANONYMOUS_HEADER_PUBLIC_KEY,
)
from hezor_common.transfer.base_sdk.meta_info import MetaInfo
from hezor_common.transfer.middlewares import CredentialMiddleware


@pytest.fixture
def test_app():
    """Create a test FastAPI application."""
    app = FastAPI()

    @app.get("/test")
    async def test_endpoint(request: Request):
        """Test endpoint that returns request state."""
        return {
            "api_key": getattr(request.state, "api_key", None),
            "meta_info": getattr(request.state, "meta_info", None),
            "api_key_error": getattr(request.state, "api_key_error", None),
            "meta_info_error": getattr(request.state, "meta_info_error", None),
        }

    return app


@pytest.fixture
def valid_api_key():
    """Return a valid API key for testing."""
    return "test-api-key-12345"


@pytest.fixture
def valid_meta_info():
    """Create a valid MetaInfo object."""
    return MetaInfo(
        subject="测试主体",
        subject_code="test_001",
        caller_id="user_123",
        data_coverage="202401-202412",
        creation_slug="test_model",
        creation_name="测试模型",
    )


@pytest.fixture
def valid_meta_jwt(valid_meta_info):
    """Create a valid MetaInfo JWT token."""
    return encode_jwt_with_pem(
        ANONYMOUS_HEADER_PRIVATE_KEY.encode("utf-8"),
        valid_meta_info.model_dump(),
        password=ANONYMOUS_HEADER_PRIVATE_KEY_PASSWORD,
    )


class TestCredentialMiddlewareInit:
    """Test CredentialMiddleware initialization."""

    def test_init_with_defaults(self, test_app):
        """Test initialization with default parameters."""
        middleware = CredentialMiddleware(test_app)
        assert middleware.api_key is None
        assert middleware.public_key_pem == ANONYMOUS_HEADER_PUBLIC_KEY
        assert middleware.verify_jwt is True

    def test_init_with_api_key(self, test_app, valid_api_key):
        """Test initialization with API key."""
        middleware = CredentialMiddleware(test_app, api_key=valid_api_key)
        assert middleware.api_key == valid_api_key

    def test_init_with_public_key_pem(self, test_app):
        """Test initialization with public key PEM content."""
        custom_key = "custom-public-key"
        middleware = CredentialMiddleware(test_app, public_key_pem=custom_key)
        assert middleware.public_key_pem == custom_key

    def test_init_with_public_key_path(self, test_app):
        """Test initialization with public key file path."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".pem", delete=False) as f:
            f.write("test-public-key-content")
            key_path = f.name

        try:
            middleware = CredentialMiddleware(test_app, public_key_path=key_path)
            assert middleware.public_key_pem == b"test-public-key-content"
        finally:
            Path(key_path).unlink()

    def test_init_with_invalid_key_path(self, test_app):
        """Test initialization with non-existent key file."""
        with pytest.raises(FileNotFoundError, match="Public key file not found"):
            CredentialMiddleware(test_app, public_key_path="/nonexistent/key.pem")

    def test_init_with_verify_jwt_false(self, test_app):
        """Test initialization with JWT verification disabled."""
        middleware = CredentialMiddleware(test_app, verify_jwt=False)
        assert middleware.verify_jwt is False


class TestCredentialMiddlewareAPIKey:
    """Test API key extraction and validation."""

    def test_valid_api_key(self, test_app, valid_api_key):
        """Test extraction of valid API key."""
        test_app.add_middleware(CredentialMiddleware, api_key=valid_api_key)
        client = TestClient(test_app)

        response = client.get("/test", headers={"Authorization": f"Bearer {valid_api_key}"})
        assert response.status_code == 200
        data = response.json()
        assert data["api_key"] == valid_api_key
        assert data["api_key_error"] is None

    def test_invalid_api_key(self, test_app, valid_api_key):
        """Test rejection of invalid API key."""
        test_app.add_middleware(CredentialMiddleware, api_key=valid_api_key)
        client = TestClient(test_app)

        response = client.get("/test", headers={"Authorization": "Bearer wrong-key"})
        assert response.status_code == 200
        data = response.json()
        assert data["api_key"] is None
        assert data["api_key_error"] == "Invalid API key"

    def test_no_api_key_validation(self, test_app):
        """Test acceptance of any API key when validation is disabled."""
        test_app.add_middleware(CredentialMiddleware)  # No api_key parameter
        client = TestClient(test_app)

        response = client.get("/test", headers={"Authorization": "Bearer any-key"})
        assert response.status_code == 200
        data = response.json()
        assert data["api_key"] == "any-key"
        assert data["api_key_error"] is None

    def test_missing_bearer_prefix(self, test_app, valid_api_key):
        """Test rejection of Authorization header without Bearer prefix."""
        test_app.add_middleware(CredentialMiddleware, api_key=valid_api_key)
        client = TestClient(test_app)

        response = client.get("/test", headers={"Authorization": valid_api_key})
        assert response.status_code == 200
        data = response.json()
        assert data["api_key"] is None
        assert data["api_key_error"] == "Invalid Authorization header format"

    def test_missing_authorization_header(self, test_app, valid_api_key):
        """Test request without Authorization header."""
        test_app.add_middleware(CredentialMiddleware, api_key=valid_api_key)
        client = TestClient(test_app)

        response = client.get("/test")
        assert response.status_code == 200
        data = response.json()
        assert data["api_key"] is None
        assert data["api_key_error"] is None


class TestCredentialMiddlewareMetaInfo:
    """Test MetaInfo JWT extraction and validation."""

    def test_valid_meta_info(self, test_app, valid_meta_jwt):
        """Test extraction of valid MetaInfo JWT."""
        test_app.add_middleware(CredentialMiddleware)
        client = TestClient(test_app)

        response = client.get("/test", headers={"X-META-INFO": valid_meta_jwt})
        assert response.status_code == 200
        data = response.json()
        assert data["meta_info"] is not None
        assert data["meta_info"]["subject"] == "测试主体"
        assert data["meta_info"]["subject_code"] == "test_001"
        assert data["meta_info_error"] is None

    def test_invalid_meta_info_jwt(self, test_app):
        """Test rejection of invalid MetaInfo JWT."""
        test_app.add_middleware(CredentialMiddleware)
        client = TestClient(test_app)

        response = client.get("/test", headers={"X-META-INFO": "invalid-jwt-token"})
        assert response.status_code == 200
        data = response.json()
        assert data["meta_info"] is None
        assert "Invalid MetaInfo JWT" in data["meta_info_error"]

    def test_missing_meta_info_header(self, test_app):
        """Test request without X-META-INFO header."""
        test_app.add_middleware(CredentialMiddleware)
        client = TestClient(test_app)

        response = client.get("/test")
        assert response.status_code == 200
        data = response.json()
        assert data["meta_info"] is None
        assert data["meta_info_error"] is None

    def test_meta_info_with_custom_public_key(self, test_app, valid_meta_info):
        """Test MetaInfo extraction with custom public key."""
        # Generate JWT with anonymous private key
        jwt_token = encode_jwt_with_pem(
            ANONYMOUS_HEADER_PRIVATE_KEY.encode("utf-8"),
            valid_meta_info.model_dump(),
            password=ANONYMOUS_HEADER_PRIVATE_KEY_PASSWORD,
        )

        # Middleware with matching public key
        test_app.add_middleware(CredentialMiddleware, public_key_pem=ANONYMOUS_HEADER_PUBLIC_KEY)
        client = TestClient(test_app)

        response = client.get("/test", headers={"X-META-INFO": jwt_token})
        assert response.status_code == 200
        data = response.json()
        assert data["meta_info"] is not None
        assert data["meta_info_error"] is None

    def test_meta_info_without_verification(self, test_app, valid_meta_info):
        """Test MetaInfo extraction without JWT verification."""
        # Generate JWT with anonymous private key
        jwt_token = encode_jwt_with_pem(
            ANONYMOUS_HEADER_PRIVATE_KEY.encode("utf-8"),
            valid_meta_info.model_dump(),
            password=ANONYMOUS_HEADER_PRIVATE_KEY_PASSWORD,
        )

        test_app.add_middleware(CredentialMiddleware, verify_jwt=False)
        client = TestClient(test_app)

        response = client.get("/test", headers={"X-META-INFO": jwt_token})
        assert response.status_code == 200
        data = response.json()
        assert data["meta_info"] is not None
        assert data["meta_info_error"] is None


class TestCredentialMiddlewareBothCredentials:
    """Test extraction of both API key and MetaInfo together."""

    def test_both_valid_credentials(self, test_app, valid_api_key, valid_meta_jwt):
        """Test extraction of both valid API key and MetaInfo."""
        test_app.add_middleware(CredentialMiddleware, api_key=valid_api_key)
        client = TestClient(test_app)

        response = client.get(
            "/test",
            headers={
                "Authorization": f"Bearer {valid_api_key}",
                "X-META-INFO": valid_meta_jwt,
            },
        )
        assert response.status_code == 200
        data = response.json()
        assert data["api_key"] == valid_api_key
        assert data["meta_info"] is not None
        assert data["api_key_error"] is None
        assert data["meta_info_error"] is None

    def test_valid_api_key_invalid_meta_info(self, test_app, valid_api_key):
        """Test valid API key with invalid MetaInfo."""
        test_app.add_middleware(CredentialMiddleware, api_key=valid_api_key)
        client = TestClient(test_app)

        response = client.get(
            "/test",
            headers={
                "Authorization": f"Bearer {valid_api_key}",
                "X-META-INFO": "invalid-jwt",
            },
        )
        assert response.status_code == 200
        data = response.json()
        assert data["api_key"] == valid_api_key
        assert data["meta_info"] is None
        assert data["api_key_error"] is None
        assert "Invalid MetaInfo JWT" in data["meta_info_error"]

    def test_invalid_api_key_valid_meta_info(self, test_app, valid_api_key, valid_meta_jwt):
        """Test invalid API key with valid MetaInfo."""
        test_app.add_middleware(CredentialMiddleware, api_key=valid_api_key)
        client = TestClient(test_app)

        response = client.get(
            "/test",
            headers={
                "Authorization": "Bearer wrong-key",
                "X-META-INFO": valid_meta_jwt,
            },
        )
        assert response.status_code == 200
        data = response.json()
        assert data["api_key"] is None
        assert data["meta_info"] is not None
        assert data["api_key_error"] == "Invalid API key"
        assert data["meta_info_error"] is None


class TestCredentialMiddlewareEdgeCases:
    """Test edge cases and error handling."""

    def test_malformed_meta_info_payload(self, test_app):
        """Test MetaInfo JWT with malformed payload."""
        # Create JWT with missing required fields
        incomplete_payload = {"subject": "test"}
        jwt_token = encode_jwt_with_pem(
            ANONYMOUS_HEADER_PRIVATE_KEY.encode("utf-8"),
            incomplete_payload,
            password=ANONYMOUS_HEADER_PRIVATE_KEY_PASSWORD,
        )

        test_app.add_middleware(CredentialMiddleware)
        client = TestClient(test_app)

        response = client.get("/test", headers={"X-META-INFO": jwt_token})
        assert response.status_code == 200
        data = response.json()
        assert data["meta_info"] is None
        assert "Invalid MetaInfo JWT" in data["meta_info_error"]

    def test_empty_authorization_header(self, test_app):
        """Test empty Authorization header."""
        test_app.add_middleware(CredentialMiddleware)
        client = TestClient(test_app)

        response = client.get("/test", headers={"Authorization": ""})
        assert response.status_code == 200
        data = response.json()
        assert data["api_key"] is None

    def test_bearer_without_token(self, test_app):
        """Test Authorization header with Bearer prefix but no token."""
        test_app.add_middleware(CredentialMiddleware)
        client = TestClient(test_app)

        response = client.get("/test", headers={"Authorization": "Bearer "})
        assert response.status_code == 200
        data = response.json()
        assert data["api_key"] == ""  # Empty string after "Bearer "

    def test_public_key_as_bytes(self, test_app, valid_meta_jwt):
        """Test initialization with public key as bytes."""
        test_app.add_middleware(
            CredentialMiddleware,
            public_key_pem=ANONYMOUS_HEADER_PUBLIC_KEY.encode("utf-8"),
        )
        client = TestClient(test_app)

        response = client.get("/test", headers={"X-META-INFO": valid_meta_jwt})
        assert response.status_code == 200
        data = response.json()
        assert data["meta_info"] is not None
        assert data["meta_info_error"] is None
