"""
Tests for Nginx provider implementation

This module tests the Nginx provider's ability to generate correct nginx.conf
configurations for various scenarios including load balancing, rate limiting,
authentication, headers, CORS, and passive health checks.
"""

import pytest

from gal.config import (
    ActiveHealthCheck,
    ApiKeyConfig,
    AuthenticationConfig,
    BasicAuthConfig,
    CircuitBreakerConfig,
    Config,
    CORSPolicy,
    GlobalConfig,
    HeaderManipulation,
    HealthCheckConfig,
    JwtConfig,
    LoadBalancerConfig,
    PassiveHealthCheck,
    RateLimitConfig,
    Route,
    Service,
    Upstream,
    UpstreamTarget,
)
from gal.providers.nginx import NginxProvider


class TestNginxProvider:
    """Test Nginx provider"""

    def test_name(self):
        """Test provider name"""
        provider = NginxProvider()
        assert provider.name() == "nginx"

    def test_validate_success(self):
        """Test successful validation"""
        provider = NginxProvider()
        config = self._create_basic_config()
        assert provider.validate(config) is True

    def test_validate_warns_active_health_checks(self, caplog):
        """Test validation warns about unsupported active health checks"""
        provider = NginxProvider()

        upstream = Upstream(
            targets=[
                UpstreamTarget(host="api-1.local", port=8080),
                UpstreamTarget(host="api-2.local", port=8080),
            ],
            health_check=HealthCheckConfig(
                active=ActiveHealthCheck(enabled=True, http_path="/health")
            ),
        )

        route = Route(path_prefix="/api")
        service = Service(
            name="test_service", type="rest", protocol="http", upstream=upstream, routes=[route]
        )

        config = Config(
            version="1.0", provider="nginx", global_config=GlobalConfig(), services=[service]
        )

        provider.validate(config)
        assert "Active health checks are not supported" in caplog.text
        assert "Nginx Plus only" in caplog.text

    def test_validate_warns_jwt_auth(self, caplog):
        """Test validation warns about JWT authentication"""
        provider = NginxProvider()

        route = Route(
            path_prefix="/api",
            authentication=AuthenticationConfig(
                enabled=True,
                type="jwt",
                jwt=JwtConfig(
                    issuer="https://auth.example.com",
                    audience="api",
                    jwks_uri="https://auth.example.com/.well-known/jwks.json",
                ),
            ),
        )

        service = Service(
            name="test_service",
            type="rest",
            protocol="http",
            upstream=Upstream(targets=[UpstreamTarget(host="api.local", port=8080)]),
            routes=[route],
        )

        config = Config(
            version="1.0", provider="nginx", global_config=GlobalConfig(), services=[service]
        )

        provider.validate(config)
        assert "JWT authentication requires OpenResty/Lua" in caplog.text

    def test_validate_warns_circuit_breaker(self, caplog):
        """Test validation warns about circuit breaker"""
        provider = NginxProvider()

        route = Route(
            path_prefix="/api",
            circuit_breaker=CircuitBreakerConfig(enabled=True, max_failures=5, timeout="30s"),
        )

        service = Service(
            name="test_service",
            type="rest",
            protocol="http",
            upstream=Upstream(targets=[UpstreamTarget(host="api.local", port=8080)]),
            routes=[route],
        )

        config = Config(
            version="1.0", provider="nginx", global_config=GlobalConfig(), services=[service]
        )

        provider.validate(config)
        assert "Circuit breaker support is limited" in caplog.text
        assert "Requires Lua" in caplog.text

    def test_generate_basic_config(self):
        """Test generating basic Nginx configuration"""
        provider = NginxProvider()
        config = self._create_basic_config()

        result = provider.generate(config)

        assert "# Nginx Configuration Generated by GAL" in result
        assert "events {" in result
        assert "http {" in result
        assert "server {" in result
        assert "location /api {" in result
        # Nginx creates upstream even for single target
        assert "upstream upstream_test_service {" in result
        assert "proxy_pass http://upstream_test_service;" in result

    def test_generate_with_upstream(self):
        """Test generating config with upstream block for load balancing"""
        provider = NginxProvider()

        upstream = Upstream(
            targets=[
                UpstreamTarget(host="api-1.internal", port=8080, weight=2),
                UpstreamTarget(host="api-2.internal", port=8080, weight=1),
            ]
        )

        route = Route(path_prefix="/api")
        service = Service(
            name="test_service", type="rest", protocol="http", upstream=upstream, routes=[route]
        )

        config = Config(
            version="1.0", provider="nginx", global_config=GlobalConfig(port=80), services=[service]
        )

        result = provider.generate(config)

        assert "upstream upstream_test_service {" in result
        assert "server api-1.internal:8080 weight=2" in result
        # weight=1 is default and not explicitly shown
        assert "server api-2.internal:8080" in result
        assert "proxy_pass http://upstream_test_service;" in result

    def test_generate_load_balancing_round_robin(self):
        """Test round-robin load balancing (default)"""
        provider = NginxProvider()
        config = self._create_lb_config("round_robin")

        result = provider.generate(config)

        assert "upstream upstream_lb_service {" in result
        # round_robin is default, no explicit directive
        assert "server api-1.internal:8080" in result
        assert "server api-2.internal:8080" in result

    def test_generate_load_balancing_least_conn(self):
        """Test least connections load balancing"""
        provider = NginxProvider()
        config = self._create_lb_config("least_conn")

        result = provider.generate(config)

        assert "upstream upstream_lb_service {" in result
        assert "least_conn;" in result
        assert "server api-1.internal:8080" in result

    def test_generate_load_balancing_ip_hash(self):
        """Test IP hash load balancing (sticky sessions)"""
        provider = NginxProvider()
        config = self._create_lb_config("ip_hash")

        result = provider.generate(config)

        assert "upstream upstream_lb_service {" in result
        assert "ip_hash;" in result
        assert "server api-1.internal:8080" in result

    def test_generate_load_balancing_weighted(self):
        """Test weighted load balancing"""
        provider = NginxProvider()

        upstream = Upstream(
            targets=[
                UpstreamTarget(host="api-1.internal", port=8080, weight=3),
                UpstreamTarget(host="api-2.internal", port=8080, weight=1),
            ],
            load_balancer=LoadBalancerConfig(algorithm="weighted"),
        )

        route = Route(path_prefix="/api")
        service = Service(
            name="weighted_service", type="rest", protocol="http", upstream=upstream, routes=[route]
        )

        config = Config(
            version="1.0", provider="nginx", global_config=GlobalConfig(), services=[service]
        )

        result = provider.generate(config)

        assert "server api-1.internal:8080 weight=3" in result
        # weight=1 is default and not explicitly shown
        assert "server api-2.internal:8080" in result

    def test_generate_passive_health_checks(self):
        """Test passive health check configuration"""
        provider = NginxProvider()

        upstream = Upstream(
            targets=[
                UpstreamTarget(host="api-1.internal", port=8080),
                UpstreamTarget(host="api-2.internal", port=8080),
            ],
            health_check=HealthCheckConfig(
                passive=PassiveHealthCheck(
                    enabled=True, max_failures=3, unhealthy_status_codes=[500, 502, 503]
                )
            ),
        )

        route = Route(path_prefix="/api")
        service = Service(
            name="health_service", type="rest", protocol="http", upstream=upstream, routes=[route]
        )

        config = Config(
            version="1.0", provider="nginx", global_config=GlobalConfig(), services=[service]
        )

        result = provider.generate(config)

        assert "max_fails=3" in result
        assert "fail_timeout=30s" in result

    def test_generate_rate_limiting(self):
        """Test rate limiting configuration"""
        provider = NginxProvider()

        route = Route(
            path_prefix="/api",
            rate_limit=RateLimitConfig(
                enabled=True,
                requests_per_second=100,
                burst=200,
                key_type="ip_address",
                response_status=429,
            ),
        )

        service = Service(
            name="rate_service",
            type="rest",
            protocol="http",
            upstream=Upstream(targets=[UpstreamTarget(host="api.local", port=8080)]),
            routes=[route],
        )

        config = Config(
            version="1.0", provider="nginx", global_config=GlobalConfig(), services=[service]
        )

        result = provider.generate(config)

        assert "# Rate Limiting Zones" in result
        assert "limit_req_zone" in result
        assert "rate=100r/s;" in result
        assert "limit_req zone=rate_service_route_0_ratelimit burst=200 nodelay;" in result
        assert "limit_req_status 429;" in result

    def test_generate_rate_limiting_by_header(self):
        """Test rate limiting by custom header"""
        provider = NginxProvider()

        route = Route(
            path_prefix="/api",
            rate_limit=RateLimitConfig(
                enabled=True,
                requests_per_second=50,
                burst=100,
                key_type="header",
                key_header="X-API-Key",
            ),
        )

        service = Service(
            name="header_rl_service",
            type="rest",
            protocol="http",
            upstream=Upstream(targets=[UpstreamTarget(host="api.local", port=8080)]),
            routes=[route],
        )

        config = Config(
            version="1.0", provider="nginx", global_config=GlobalConfig(), services=[service]
        )

        result = provider.generate(config)

        assert "limit_req_zone $http_x_api_key" in result
        assert "rate=50r/s;" in result

    def test_generate_basic_authentication(self):
        """Test basic authentication configuration"""
        provider = NginxProvider()

        route = Route(
            path_prefix="/api",
            authentication=AuthenticationConfig(
                enabled=True,
                type="basic",
                basic_auth=BasicAuthConfig(users={"admin": "password"}, realm="Protected API"),
            ),
        )

        service = Service(
            name="auth_service",
            type="rest",
            protocol="http",
            upstream=Upstream(targets=[UpstreamTarget(host="api.local", port=8080)]),
            routes=[route],
        )

        config = Config(
            version="1.0", provider="nginx", global_config=GlobalConfig(), services=[service]
        )

        result = provider.generate(config)

        assert "# Basic Authentication" in result
        assert 'auth_basic "Protected API";' in result
        assert "auth_basic_user_file /etc/nginx/.htpasswd;" in result

    def test_generate_api_key_authentication(self):
        """Test API key authentication (note: requires Lua)"""
        provider = NginxProvider()

        route = Route(
            path_prefix="/api",
            authentication=AuthenticationConfig(
                enabled=True,
                type="api_key",
                api_key=ApiKeyConfig(
                    keys=["key_123abc"], key_name="X-API-Key", in_location="header"
                ),
            ),
        )

        service = Service(
            name="apikey_service",
            type="rest",
            protocol="http",
            upstream=Upstream(targets=[UpstreamTarget(host="api.local", port=8080)]),
            routes=[route],
        )

        config = Config(
            version="1.0", provider="nginx", global_config=GlobalConfig(), services=[service]
        )

        result = provider.generate(config)

        assert "# API Key authentication not natively supported" in result
        assert "# Requires Lua or external authentication" in result

    def test_generate_jwt_authentication(self):
        """Test JWT authentication (note: requires OpenResty)"""
        provider = NginxProvider()

        route = Route(
            path_prefix="/api",
            authentication=AuthenticationConfig(
                enabled=True,
                type="jwt",
                jwt=JwtConfig(
                    issuer="https://auth.example.com",
                    audience="api",
                    jwks_uri="https://auth.example.com/.well-known/jwks.json",
                ),
            ),
        )

        service = Service(
            name="jwt_service",
            type="rest",
            protocol="http",
            upstream=Upstream(targets=[UpstreamTarget(host="api.local", port=8080)]),
            routes=[route],
        )

        config = Config(
            version="1.0", provider="nginx", global_config=GlobalConfig(), services=[service]
        )

        result = provider.generate(config)

        assert "# JWT authentication requires OpenResty/Lua" in result

    def test_generate_request_headers(self):
        """Test request header manipulation"""
        provider = NginxProvider()

        route = Route(
            path_prefix="/api",
            headers=HeaderManipulation(
                request_add={"X-Request-ID": "{{uuid}}", "X-Gateway": "GAL"},
                request_set={"User-Agent": "GAL-Gateway/1.0"},
                request_remove=["X-Internal-Token"],
            ),
        )

        service = Service(
            name="headers_service",
            type="rest",
            protocol="http",
            upstream=Upstream(targets=[UpstreamTarget(host="api.local", port=8080)]),
            routes=[route],
        )

        config = Config(
            version="1.0", provider="nginx", global_config=GlobalConfig(), services=[service]
        )

        result = provider.generate(config)

        assert "# Request Headers" in result
        assert "proxy_set_header X-Request-ID $request_id;" in result
        assert "proxy_set_header X-Gateway 'GAL';" in result
        assert "proxy_set_header User-Agent 'GAL-Gateway/1.0';" in result
        assert "proxy_set_header X-Internal-Token '';" in result

    def test_generate_response_headers(self):
        """Test response header manipulation"""
        provider = NginxProvider()

        route = Route(
            path_prefix="/api",
            headers=HeaderManipulation(
                response_add={"X-Frame-Options": "DENY", "X-Content-Type-Options": "nosniff"},
                response_set={"Server": "GAL-Gateway"},
                response_remove=["X-Powered-By"],
            ),
        )

        service = Service(
            name="resp_headers_service",
            type="rest",
            protocol="http",
            upstream=Upstream(targets=[UpstreamTarget(host="api.local", port=8080)]),
            routes=[route],
        )

        config = Config(
            version="1.0", provider="nginx", global_config=GlobalConfig(), services=[service]
        )

        result = provider.generate(config)

        assert "# Response Headers" in result
        assert "add_header X-Frame-Options 'DENY' always;" in result
        assert "add_header X-Content-Type-Options 'nosniff' always;" in result
        assert "add_header Server 'GAL-Gateway' always;" in result
        assert "# Note: Response header removal requires ngx_headers_more module" in result

    def test_generate_cors(self):
        """Test CORS configuration"""
        provider = NginxProvider()

        route = Route(
            path_prefix="/api",
            cors=CORSPolicy(
                enabled=True,
                allowed_origins=["https://app.example.com"],
                allowed_methods=["GET", "POST", "PUT", "DELETE"],
                allowed_headers=["Content-Type", "Authorization"],
                expose_headers=["X-Request-ID"],
                allow_credentials=True,
                max_age=86400,
            ),
        )

        service = Service(
            name="cors_service",
            type="rest",
            protocol="http",
            upstream=Upstream(targets=[UpstreamTarget(host="api.local", port=8080)]),
            routes=[route],
        )

        config = Config(
            version="1.0", provider="nginx", global_config=GlobalConfig(), services=[service]
        )

        result = provider.generate(config)

        assert "# CORS Configuration" in result
        assert (
            "add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;" in result
        )
        assert (
            "add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE' always;" in result
        )
        assert (
            "add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;"
            in result
        )
        assert "add_header 'Access-Control-Expose-Headers' 'X-Request-ID' always;" in result
        assert "add_header 'Access-Control-Allow-Credentials' 'true' always;" in result
        assert "add_header 'Access-Control-Max-Age' '86400' always;" in result
        assert "# Handle preflight requests" in result
        assert "if ($request_method = 'OPTIONS')" in result
        assert "return 204;" in result

    def test_generate_multiple_services(self):
        """Test generating config with multiple services"""
        provider = NginxProvider()

        service1 = Service(
            name="service1",
            type="rest",
            protocol="http",
            upstream=Upstream(targets=[UpstreamTarget(host="svc1.local", port=8080)]),
            routes=[Route(path_prefix="/api/v1")],
        )

        service2 = Service(
            name="service2",
            type="rest",
            protocol="http",
            upstream=Upstream(targets=[UpstreamTarget(host="svc2.local", port=9090)]),
            routes=[Route(path_prefix="/api/v2")],
        )

        config = Config(
            version="1.0",
            provider="nginx",
            global_config=GlobalConfig(),
            services=[service1, service2],
        )

        result = provider.generate(config)

        assert "# Server for service1" in result
        assert "# Server for service2" in result
        assert "location /api/v1" in result
        assert "location /api/v2" in result

    def test_generate_multiple_routes_per_service(self):
        """Test generating config with multiple routes per service"""
        provider = NginxProvider()

        service = Service(
            name="multi_route",
            type="rest",
            protocol="http",
            upstream=Upstream(targets=[UpstreamTarget(host="api.local", port=8080)]),
            routes=[
                Route(path_prefix="/api/v1"),
                Route(path_prefix="/api/v2"),
                Route(path_prefix="/api/v3"),
            ],
        )

        config = Config(
            version="1.0", provider="nginx", global_config=GlobalConfig(), services=[service]
        )

        result = provider.generate(config)

        assert "location /api/v1" in result
        assert "location /api/v2" in result
        assert "location /api/v3" in result

    def test_generate_custom_port(self):
        """Test generating config with custom listen port"""
        provider = NginxProvider()

        service = Service(
            name="custom_port_service",
            type="rest",
            protocol="http",
            upstream=Upstream(targets=[UpstreamTarget(host="api.local", port=8080)]),
            routes=[Route(path_prefix="/api")],
        )

        config = Config(
            version="1.0",
            provider="nginx",
            global_config=GlobalConfig(port=8888),
            services=[service],
        )

        result = provider.generate(config)

        assert "listen 8888;" in result

    def test_generate_with_all_features_combined(self):
        """Test generating config with all features combined"""
        provider = NginxProvider()

        upstream = Upstream(
            targets=[
                UpstreamTarget(host="api-1.internal", port=8080, weight=2),
                UpstreamTarget(host="api-2.internal", port=8080, weight=1),
            ],
            health_check=HealthCheckConfig(
                passive=PassiveHealthCheck(enabled=True, max_failures=3)
            ),
            load_balancer=LoadBalancerConfig(algorithm="least_conn"),
        )

        route = Route(
            path_prefix="/api",
            rate_limit=RateLimitConfig(enabled=True, requests_per_second=100, burst=200),
            authentication=AuthenticationConfig(
                enabled=True, type="basic", basic_auth=BasicAuthConfig(users={"admin": "password"})
            ),
            headers=HeaderManipulation(
                request_add={"X-Request-ID": "{{uuid}}"}, response_add={"X-Frame-Options": "DENY"}
            ),
            cors=CORSPolicy(
                enabled=True,
                allowed_origins=["https://app.example.com"],
                allowed_methods=["GET", "POST"],
            ),
        )

        service = Service(
            name="full_featured", type="rest", protocol="http", upstream=upstream, routes=[route]
        )

        config = Config(
            version="1.0", provider="nginx", global_config=GlobalConfig(port=80), services=[service]
        )

        result = provider.generate(config)

        # Verify all features present
        assert "limit_req_zone" in result
        assert "upstream upstream_full_featured {" in result
        assert "least_conn;" in result
        assert "server api-1.internal:8080 weight=2 max_fails=3" in result
        assert "limit_req zone=" in result
        assert 'auth_basic "' in result
        assert "proxy_set_header X-Request-ID $request_id;" in result
        assert "add_header X-Frame-Options 'DENY' always;" in result
        assert "add_header 'Access-Control-Allow-Origin'" in result

    def test_template_variable_conversion(self):
        """Test conversion of GAL template variables to Nginx variables"""
        provider = NginxProvider()

        # Test {{uuid}} -> $request_id
        assert provider._convert_template_value("{{uuid}}") == "$request_id"

        # Test {{now}} -> $time_iso8601
        assert provider._convert_template_value("{{now}}") == "$time_iso8601"
        assert provider._convert_template_value("{{timestamp}}") == "$time_iso8601"

        # Test static values
        assert provider._convert_template_value("static-value") == "'static-value'"

    # Helper methods

    def _create_basic_config(self):
        """Helper to create basic config"""
        global_config = GlobalConfig()
        upstream = Upstream(targets=[UpstreamTarget(host="test.local", port=8080)])
        route = Route(path_prefix="/api")
        service = Service(
            name="test_service", type="rest", protocol="http", upstream=upstream, routes=[route]
        )
        return Config(
            version="1.0", provider="nginx", global_config=global_config, services=[service]
        )

    def _create_lb_config(self, algorithm: str):
        """Helper to create load balancing config"""
        upstream = Upstream(
            targets=[
                UpstreamTarget(host="api-1.internal", port=8080),
                UpstreamTarget(host="api-2.internal", port=8080),
            ],
            load_balancer=LoadBalancerConfig(algorithm=algorithm),
        )

        route = Route(path_prefix="/api")
        service = Service(
            name="lb_service", type="rest", protocol="http", upstream=upstream, routes=[route]
        )

        return Config(
            version="1.0", provider="nginx", global_config=GlobalConfig(), services=[service]
        )
