"""
Tests for all provider implementations
"""

import pytest
import json
import os
import tempfile
from unittest.mock import patch, MagicMock
from gal.providers.envoy import EnvoyProvider
from gal.providers.kong import KongProvider
from gal.providers.apisix import APISIXProvider
from gal.providers.traefik import TraefikProvider
from gal.config import (
    Config,
    Service,
    Upstream,
    Route,
    GlobalConfig,
    Transformation,
    ComputedField,
    Validation,
    RateLimitConfig
)


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"""
        provider = EnvoyProvider()
        config = self._create_config_with_transformation("envoy")

        result = provider.generate(config)

        assert "envoy.filters.http.lua" in result
        assert "function envoy_on_request" in result
        assert "test_service transformations" 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]
        )
