"""
Tests for all provider implementations
"""

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

import pytest

from gal.config import (
    ComputedField,
    Config,
    GlobalConfig,
    RateLimitConfig,
    Route,
    Service,
    Transformation,
    Upstream,
    Validation,
)
from gal.providers.apisix import APISIXProvider
from gal.providers.envoy import EnvoyProvider
from gal.providers.kong import KongProvider
from gal.providers.traefik import TraefikProvider


class TestEnvoyProvider:
    """Test Envoy provider"""

    def test_name(self):
        """Test provider name"""
        provider = EnvoyProvider()
        assert provider.name() == "envoy"

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

    def test_validate_port_zero(self):
        """Test validation fails with port 0"""
        provider = EnvoyProvider()
        global_config = GlobalConfig(port=0)
        upstream = Upstream(host="test.local", port=8080)
        route = Route(path_prefix="/api")
        service = Service(
            name="test", type="rest", protocol="http", upstream=upstream, routes=[route]
        )
        config = Config(
            version="1.0", provider="envoy", global_config=global_config, services=[service]
        )

        with pytest.raises(ValueError, match="Port must be specified"):
            provider.validate(config)

    def test_generate_basic_config(self):
        """Test generating basic Envoy configuration"""
        provider = EnvoyProvider()
        config = self._create_basic_config("envoy")

        result = provider.generate(config)

        assert "# Envoy Configuration Generated by GAL" in result
        assert "static_resources:" in result
        assert "listeners:" in result
        assert "clusters:" in result
        assert "admin:" in result
        assert "test_service_cluster" in result

    def test_generate_grpc_service(self):
        """Test generating config for gRPC service"""
        provider = EnvoyProvider()
        global_config = GlobalConfig()
        upstream = Upstream(host="grpc.local", port=9090)
        route = Route(path_prefix="/myapp.Service")
        service = Service(
            name="grpc_service", type="grpc", protocol="http2", upstream=upstream, routes=[route]
        )
        config = Config(
            version="1.0", provider="envoy", global_config=global_config, services=[service]
        )

        result = provider.generate(config)

        assert "grpc: {}" in result
        assert "http2_protocol_options: {}" in result
        assert "grpc_service_cluster" in result

    def test_generate_with_transformations(self):
        """Test generating config with transformations (legacy feature)"""
        provider = EnvoyProvider()
        config = self._create_config_with_transformation("envoy")

        result = provider.generate(config)

        # Legacy transformation feature no longer generates Lua filters
        # Body transformation feature (body_transformation) generates Lua filters instead
        assert "test_service_cluster" in result
        assert "/api" in result

    def test_generate_multiple_services(self):
        """Test generating config with multiple services"""
        provider = EnvoyProvider()
        global_config = GlobalConfig()

        service1 = Service(
            name="service1",
            type="rest",
            protocol="http",
            upstream=Upstream(host="svc1.local", port=8080),
            routes=[Route(path_prefix="/api/v1")],
        )
        service2 = Service(
            name="service2",
            type="grpc",
            protocol="http2",
            upstream=Upstream(host="svc2.local", port=9090),
            routes=[Route(path_prefix="/myapp.Service")],
        )

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

        result = provider.generate(config)

        assert "service1_cluster" in result
        assert "service2_cluster" in result
        assert result.count("Services: 2") == 1

    def _create_basic_config(self, provider_name):
        """Helper to create basic config"""
        global_config = GlobalConfig()
        upstream = Upstream(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=provider_name, global_config=global_config, services=[service]
        )

    def _create_config_with_transformation(self, provider_name):
        """Helper to create config with transformation"""
        global_config = GlobalConfig()
        upstream = Upstream(host="test.local", port=8080)
        route = Route(path_prefix="/api")
        transformation = Transformation(
            enabled=True,
            defaults={"status": "active"},
            computed_fields=[ComputedField(field="id", generator="uuid", prefix="test_")],
        )
        service = Service(
            name="test_service",
            type="rest",
            protocol="http",
            upstream=upstream,
            routes=[route],
            transformation=transformation,
        )
        return Config(
            version="1.0", provider=provider_name, global_config=global_config, services=[service]
        )


class TestKongProvider:
    """Test Kong provider"""

    def test_name(self):
        """Test provider name"""
        provider = KongProvider()
        assert provider.name() == "kong"

    def test_validate(self):
        """Test validation always succeeds"""
        provider = KongProvider()
        config = self._create_basic_config()
        assert provider.validate(config) is True

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

        result = provider.generate(config)

        assert "# Kong Declarative Configuration Generated by GAL" in result
        assert "_format_version: '3.0'" in result
        assert "services:" in result
        assert "name: test_service" in result
        assert "protocol: http" in result
        assert "host: test.local" in result
        assert "port: 8080" in result

    def test_generate_grpc_service(self):
        """Test generating config for gRPC service"""
        provider = KongProvider()
        global_config = GlobalConfig()
        upstream = Upstream(host="grpc.local", port=9090)
        route = Route(path_prefix="/myapp.Service")
        service = Service(
            name="grpc_service", type="grpc", protocol="http2", upstream=upstream, routes=[route]
        )
        config = Config(
            version="1.0", provider="kong", global_config=global_config, services=[service]
        )

        result = provider.generate(config)

        assert "protocol: grpc" in result
        assert "name: grpc_service" in result

    def test_generate_with_methods(self):
        """Test generating config with HTTP methods"""
        provider = KongProvider()
        global_config = GlobalConfig()
        upstream = Upstream(host="test.local", port=8080)
        route = Route(path_prefix="/api/test", methods=["GET", "POST"])
        service = Service(
            name="test_service", type="rest", protocol="http", upstream=upstream, routes=[route]
        )
        config = Config(
            version="1.0", provider="kong", global_config=global_config, services=[service]
        )

        result = provider.generate(config)

        assert "methods:" in result
        assert "- GET" in result
        assert "- POST" in result

    def test_generate_with_transformation(self):
        """Test generating config with transformation plugin"""
        provider = KongProvider()
        global_config = GlobalConfig()
        upstream = Upstream(host="test.local", port=8080)
        route = Route(path_prefix="/api")
        transformation = Transformation(enabled=True, defaults={"status": "active", "role": "user"})
        service = Service(
            name="test_service",
            type="rest",
            protocol="http",
            upstream=upstream,
            routes=[route],
            transformation=transformation,
        )
        config = Config(
            version="1.0", provider="kong", global_config=global_config, services=[service]
        )

        result = provider.generate(config)

        assert "plugins:" in result
        assert "request-transformer" in result
        assert "x-default-status: 'active'" in result
        assert "x-default-role: 'user'" in result

    def _create_basic_config(self):
        """Helper to create basic config"""
        global_config = GlobalConfig()
        upstream = Upstream(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="kong", global_config=global_config, services=[service]
        )


class TestAPISIXProvider:
    """Test APISIX provider"""

    def test_name(self):
        """Test provider name"""
        provider = APISIXProvider()
        assert provider.name() == "apisix"

    def test_validate(self):
        """Test validation always succeeds"""
        provider = APISIXProvider()
        config = self._create_basic_config()
        assert provider.validate(config) is True

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

        result = provider.generate(config)
        config_json = json.loads(result)

        assert "routes" in config_json
        assert "upstreams" in config_json
        assert "services" in config_json
        assert len(config_json["services"]) == 1
        assert config_json["services"][0]["id"] == "test_service"

    def test_generate_upstream(self):
        """Test upstream generation"""
        provider = APISIXProvider()
        config = self._create_basic_config()

        result = provider.generate(config)
        config_json = json.loads(result)

        assert len(config_json["upstreams"]) == 1
        upstream = config_json["upstreams"][0]
        assert upstream["id"] == "test_service_upstream"
        assert upstream["type"] == "roundrobin"
        assert "test.local:8080" in upstream["nodes"]

    def test_generate_route(self):
        """Test route generation"""
        provider = APISIXProvider()
        config = self._create_basic_config()

        result = provider.generate(config)
        config_json = json.loads(result)

        assert len(config_json["routes"]) == 1
        route = config_json["routes"][0]
        assert route["uri"] == "/api/*"
        assert route["name"] == "test_service_route"
        assert route["service_id"] == "test_service"

    def test_generate_with_methods(self):
        """Test generating route with HTTP methods"""
        provider = APISIXProvider()
        global_config = GlobalConfig()
        upstream = Upstream(host="test.local", port=8080)
        route = Route(path_prefix="/api/test", methods=["GET", "POST"])
        service = Service(
            name="test_service", type="rest", protocol="http", upstream=upstream, routes=[route]
        )
        config = Config(
            version="1.0", provider="apisix", global_config=global_config, services=[service]
        )

        result = provider.generate(config)
        config_json = json.loads(result)

        assert "methods" in config_json["routes"][0]
        assert config_json["routes"][0]["methods"] == ["GET", "POST"]

    def test_generate_with_transformation(self):
        """Test generating config with Lua transformation"""
        provider = APISIXProvider()
        global_config = GlobalConfig()
        upstream = Upstream(host="test.local", port=8080)
        route = Route(path_prefix="/api")
        transformation = Transformation(
            enabled=True,
            defaults={"status": "active"},
            computed_fields=[
                ComputedField(field="id", generator="uuid", prefix="test_"),
                ComputedField(field="created_at", generator="timestamp"),
            ],
        )
        service = Service(
            name="test_service",
            type="rest",
            protocol="http",
            upstream=upstream,
            routes=[route],
            transformation=transformation,
        )
        config = Config(
            version="1.0", provider="apisix", global_config=global_config, services=[service]
        )

        result = provider.generate(config)
        config_json = json.loads(result)

        assert "plugins" in config_json["services"][0]
        assert "serverless-pre-function" in config_json["services"][0]["plugins"]

    def test_lua_transformation_generation(self):
        """Test Lua transformation script generation"""
        provider = APISIXProvider()
        upstream = Upstream(host="test.local", port=8080)
        route = Route(path_prefix="/api")
        transformation = Transformation(
            enabled=True,
            defaults={"status": "active", "count": 0},
            computed_fields=[
                ComputedField(field="id", generator="uuid", prefix="usr_"),
                ComputedField(field="timestamp", generator="timestamp"),
            ],
        )
        service = Service(
            name="test_service",
            type="rest",
            protocol="http",
            upstream=upstream,
            routes=[route],
            transformation=transformation,
        )

        lua_code = provider._generate_lua_transformation(service)

        assert "return function(conf, ctx)" in lua_code
        assert "require('apisix.core')" in lua_code
        assert "json_body.status = json_body.status or 'active'" in lua_code
        assert "json_body.count = json_body.count or 0" in lua_code
        assert "json_body.id" in lua_code
        assert "usr_" in lua_code
        assert "json_body.timestamp" in lua_code
        assert "os.time()" in lua_code

    def _create_basic_config(self):
        """Helper to create basic config"""
        global_config = GlobalConfig()
        upstream = Upstream(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="apisix", global_config=global_config, services=[service]
        )


class TestTraefikProvider:
    """Test Traefik provider"""

    def test_name(self):
        """Test provider name"""
        provider = TraefikProvider()
        assert provider.name() == "traefik"

    def test_validate(self):
        """Test validation always succeeds"""
        provider = TraefikProvider()
        config = self._create_basic_config()
        assert provider.validate(config) is True

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

        result = provider.generate(config)

        assert "# Traefik Configuration Generated by GAL" in result
        assert "http:" in result
        assert "routers:" in result
        assert "services:" in result
        assert "test_service_router_0:" in result
        assert "test_service_service:" in result

    def test_generate_router(self):
        """Test router generation"""
        provider = TraefikProvider()
        config = self._create_basic_config()

        result = provider.generate(config)

        assert "test_service_router_0:" in result
        assert "rule: 'PathPrefix(`/api`)" in result
        assert "service: test_service_service" in result

    def test_generate_service_loadbalancer(self):
        """Test service loadbalancer generation"""
        provider = TraefikProvider()
        config = self._create_basic_config()

        result = provider.generate(config)

        assert "test_service_service:" in result
        assert "loadBalancer:" in result
        assert "servers:" in result
        assert "url: 'http://test.local:8080'" in result

    def test_generate_multiple_routes(self):
        """Test generating multiple routes for a service"""
        provider = TraefikProvider()
        global_config = GlobalConfig()
        upstream = Upstream(host="test.local", port=8080)
        service = Service(
            name="test_service",
            type="rest",
            protocol="http",
            upstream=upstream,
            routes=[Route(path_prefix="/api/v1"), Route(path_prefix="/api/v2")],
        )
        config = Config(
            version="1.0", provider="traefik", global_config=global_config, services=[service]
        )

        result = provider.generate(config)

        assert "test_service_router_0:" in result
        assert "test_service_router_1:" in result
        assert "PathPrefix(`/api/v1`)" in result
        assert "PathPrefix(`/api/v2`)" in result

    def test_generate_with_transformation(self):
        """Test generating config with transformation middleware"""
        provider = TraefikProvider()
        global_config = GlobalConfig()
        upstream = Upstream(host="test.local", port=8080)
        route = Route(path_prefix="/api")
        transformation = Transformation(enabled=True, defaults={"status": "active", "role": "user"})
        service = Service(
            name="test_service",
            type="rest",
            protocol="http",
            upstream=upstream,
            routes=[route],
            transformation=transformation,
        )
        config = Config(
            version="1.0", provider="traefik", global_config=global_config, services=[service]
        )

        result = provider.generate(config)

        assert "middlewares:" in result
        assert "- test_service_transform" in result
        assert "test_service_transform:" in result
        assert "status: 'active'" in result
        assert "role: 'user'" in result

    def test_generate_without_transformation(self):
        """Test that middlewares section is omitted without transformations"""
        provider = TraefikProvider()
        config = self._create_basic_config()

        result = provider.generate(config)

        # Count how many times "middlewares:" appears
        # It should not appear in the output for services without transformations
        lines = result.split("\n")
        middleware_lines = [l for l in lines if l.strip() == "middlewares:"]

        # Should not have middlewares section at the end
        assert result.count("  middlewares:") == 0

    def _create_basic_config(self):
        """Helper to create basic config"""
        global_config = GlobalConfig()
        upstream = Upstream(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="traefik", global_config=global_config, services=[service]
        )


class TestProviderDeployment:
    """Test deployment methods for all providers"""

    def test_envoy_deploy_file_based(self):
        """Test Envoy file-based deployment"""
        provider = EnvoyProvider()
        config = self._create_basic_config("envoy")

        with tempfile.TemporaryDirectory() as tmpdir:
            output_file = os.path.join(tmpdir, "envoy.yaml")
            result = provider.deploy(config, output_file=output_file)

            assert result is True
            assert os.path.exists(output_file)

            # Verify content
            with open(output_file, "r") as f:
                content = f.read()
                assert "static_resources:" in content
                assert "test_service_cluster" in content

    @patch("gal.providers.envoy.requests.get")
    def test_envoy_deploy_with_admin_api(self, mock_get):
        """Test Envoy deployment with Admin API check"""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_get.return_value = mock_response

        provider = EnvoyProvider()
        config = self._create_basic_config("envoy")

        with tempfile.TemporaryDirectory() as tmpdir:
            output_file = os.path.join(tmpdir, "envoy.yaml")
            result = provider.deploy(
                config, output_file=output_file, admin_url="http://localhost:9901"
            )

            assert result is True
            mock_get.assert_called_once_with("http://localhost:9901/ready", timeout=5)

    def test_kong_deploy_file_based(self):
        """Test Kong file-based deployment"""
        provider = KongProvider()
        config = self._create_basic_config("kong")

        with tempfile.TemporaryDirectory() as tmpdir:
            output_file = os.path.join(tmpdir, "kong.yaml")
            result = provider.deploy(config, output_file=output_file)

            assert result is True
            assert os.path.exists(output_file)

            # Verify content
            with open(output_file, "r") as f:
                content = f.read()
                assert "_format_version: '3.0'" in content
                assert "services:" in content

    @patch("gal.providers.kong.requests.post")
    @patch("gal.providers.kong.requests.get")
    def test_kong_deploy_with_admin_api(self, mock_get, mock_post):
        """Test Kong deployment via Admin API"""
        mock_get.return_value = MagicMock(status_code=200)
        mock_post.return_value = MagicMock(status_code=201)

        provider = KongProvider()
        config = self._create_basic_config("kong")

        with tempfile.TemporaryDirectory() as tmpdir:
            output_file = os.path.join(tmpdir, "kong.yaml")
            result = provider.deploy(
                config, output_file=output_file, admin_url="http://localhost:8001"
            )

            assert result is True
            mock_get.assert_called_once()
            mock_post.assert_called_once()

    def test_apisix_deploy_file_based(self):
        """Test APISIX file-based deployment"""
        provider = APISIXProvider()
        config = self._create_basic_config("apisix")

        with tempfile.TemporaryDirectory() as tmpdir:
            output_file = os.path.join(tmpdir, "apisix.json")
            result = provider.deploy(config, output_file=output_file)

            assert result is True
            assert os.path.exists(output_file)

            # Verify content
            with open(output_file, "r") as f:
                content = json.load(f)
                assert "routes" in content
                assert "services" in content
                assert "upstreams" in content

    @patch("gal.providers.apisix.requests.put")
    def test_apisix_deploy_with_admin_api(self, mock_put):
        """Test APISIX deployment via Admin API"""
        mock_response = MagicMock()
        mock_response.status_code = 201
        mock_put.return_value = mock_response

        provider = APISIXProvider()
        config = self._create_basic_config("apisix")

        with tempfile.TemporaryDirectory() as tmpdir:
            output_file = os.path.join(tmpdir, "apisix.json")
            result = provider.deploy(
                config,
                output_file=output_file,
                admin_url="http://localhost:9180",
                api_key="test-key",
            )

            assert result is True
            # Should have called PUT for upstream, service, and route
            assert mock_put.call_count == 3

    def test_traefik_deploy_file_based(self):
        """Test Traefik file-based deployment"""
        provider = TraefikProvider()
        config = self._create_basic_config("traefik")

        with tempfile.TemporaryDirectory() as tmpdir:
            output_file = os.path.join(tmpdir, "traefik.yaml")
            result = provider.deploy(config, output_file=output_file)

            assert result is True
            assert os.path.exists(output_file)

            # Verify content
            with open(output_file, "r") as f:
                content = f.read()
                assert "http:" in content
                assert "routers:" in content
                assert "services:" in content

    @patch("gal.providers.traefik.requests.get")
    def test_traefik_deploy_with_api_check(self, mock_get):
        """Test Traefik deployment with API verification"""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_get.return_value = mock_response

        provider = TraefikProvider()
        config = self._create_basic_config("traefik")

        with tempfile.TemporaryDirectory() as tmpdir:
            output_file = os.path.join(tmpdir, "traefik.yaml")
            result = provider.deploy(
                config, output_file=output_file, api_url="http://localhost:8080"
            )

            assert result is True
            mock_get.assert_called_once()

    def test_deploy_creates_directory(self):
        """Test that deploy creates output directory if it doesn't exist"""
        provider = EnvoyProvider()
        config = self._create_basic_config("envoy")

        with tempfile.TemporaryDirectory() as tmpdir:
            output_file = os.path.join(tmpdir, "subdir", "envoy.yaml")
            result = provider.deploy(config, output_file=output_file)

            assert result is True
            assert os.path.exists(output_file)
            assert os.path.exists(os.path.dirname(output_file))

    def _create_basic_config(self, provider_name):
        """Helper to create basic config"""
        global_config = GlobalConfig()
        upstream = Upstream(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=provider_name, global_config=global_config, services=[service]
        )
