"""
Envoy Proxy provider implementation.

Generates Envoy Proxy static configuration in YAML format with support
for HTTP/2, gRPC, and Lua-based request transformations.
"""

import logging
import os
from typing import Any, Dict, List, Optional

import requests
import yaml

from ..config import (
    ActiveHealthCheck,
    Config,
    GlobalConfig,
    HealthCheckConfig,
    LoadBalancerConfig,
    PassiveHealthCheck,
    Route,
    Service,
    Upstream,
    UpstreamTarget,
)
from ..provider import Provider

logger = logging.getLogger(__name__)


class EnvoyProvider(Provider):
    """Envoy Proxy gateway provider.

    Generates static configuration for Envoy Proxy, a high-performance
    C++ based edge and service proxy. Supports both REST and gRPC services
    with native HTTP/2 support.

    Output Format:
        YAML file with static_resources configuration including:
        - Listeners with HTTP connection manager
        - Routes with prefix matching
        - Clusters with health checking and load balancing
        - Optional Lua filters for request transformations
        - Admin interface configuration

    Transformations:
        Implemented using Envoy's Lua filter (envoy.filters.http.lua).
        Provides inline Lua code for:
        - Setting default field values
        - Generating computed fields (UUID, timestamp)
        - Request body manipulation

    gRPC Support:
        Automatic HTTP/2 protocol configuration for gRPC services.
        Uses http2_protocol_options on clusters.

    Example:
        >>> provider = EnvoyProvider()
        >>> provider.name()
        'envoy'
        >>> config = Config.from_yaml("gateway.yaml")
        >>> output = provider.generate(config)
        >>> "static_resources:" in output
        True

    See Also:
        https://www.envoyproxy.io/docs/envoy/latest/configuration/overview/overview
    """

    def name(self) -> str:
        """Return provider name.

        Returns:
            str: "envoy"
        """
        return "envoy"

    def validate(self, config: Config) -> bool:
        """Validate configuration for Envoy.

        Ensures that required configuration parameters are present
        and valid for Envoy deployment.

        Args:
            config: Configuration to validate

        Returns:
            True if validation passes

        Raises:
            ValueError: If port is 0 or invalid

        Example:
            >>> provider = EnvoyProvider()
            >>> config = Config(version="1.0", provider="envoy",
            ...                 global_config=GlobalConfig(port=8080),
            ...                 services=[])
            >>> provider.validate(config)
            True
        """
        logger.debug(f"Validating Envoy configuration: {len(config.services)} services")
        if config.global_config.port == 0:
            logger.error("Envoy validation failed: Port must be specified")
            raise ValueError("Port must be specified")
        logger.debug("Envoy validation successful")
        return True

    def generate(self, config: Config) -> str:
        """Generate Envoy static configuration in YAML format.

        Creates a complete Envoy static_resources configuration including
        listeners, routes, clusters, and optional Lua transformation filters.

        Configuration Structure:
            - static_resources:
                - listeners: HTTP connection manager with routes
                - clusters: Upstream service definitions
            - admin: Admin interface configuration

        Args:
            config: Configuration object containing services and settings

        Returns:
            Complete Envoy YAML configuration as string

        Example:
            >>> provider = EnvoyProvider()
            >>> config = Config.from_yaml("config.yaml")
            >>> yaml_output = provider.generate(config)
            >>> "listeners:" in yaml_output
            True
            >>> "clusters:" in yaml_output
            True
        """
        logger.info(f"Generating Envoy configuration for {len(config.services)} services")
        output = []
        output.append("# Envoy Configuration Generated by GAL")
        output.append(f"# Provider: {config.provider}")
        output.append(
            f"# Services: {len(config.services)} ({len(config.get_grpc_services())} gRPC, {len(config.get_rest_services())} REST)"
        )
        output.append("")

        # Static resources
        output.append("static_resources:")
        output.append("  listeners:")
        output.append("  - name: listener_0")
        output.append("    address:")
        output.append("      socket_address:")
        output.append(f"        address: {config.global_config.host}")
        output.append(f"        port_value: {config.global_config.port}")
        output.append("    filter_chains:")
        output.append("    - filters:")
        output.append("      - name: envoy.filters.network.http_connection_manager")
        output.append("        typed_config:")
        output.append(
            "          '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"
        )
        output.append("          stat_prefix: ingress_http")
        output.append("          codec_type: AUTO")
        output.append("          route_config:")
        output.append("            name: local_route")
        output.append("            virtual_hosts:")
        output.append("            - name: backend")
        output.append("              domains: ['*']")
        output.append("              routes:")

        # Routes
        for service in config.services:
            for route in service.routes:
                output.append(f"              - match:")
                output.append(f"                  prefix: '{route.path_prefix}'")
                if service.type == "grpc":
                    output.append("                  grpc: {}")
                output.append("                route:")
                output.append(f"                  cluster: {service.name}_cluster")

                # Add timeout configuration if specified
                if route.timeout:
                    timeout = route.timeout
                    # Envoy uses timeout on route level for overall request timeout
                    output.append(f"                  timeout: {timeout.read}")
                    # idle_timeout for keep-alive connections
                    output.append(f"                  idle_timeout: {timeout.idle}")

                # Add retry policy if configured
                if route.retry and route.retry.enabled:
                    retry = route.retry
                    output.append("                  retry_policy:")
                    output.append(f"                    num_retries: {retry.attempts}")

                    # Map retry_on conditions to Envoy format
                    retry_conditions = []
                    for condition in retry.retry_on:
                        if condition == "connect_timeout":
                            retry_conditions.append("connect-failure")
                        elif condition == "http_5xx":
                            retry_conditions.append("5xx")
                        elif condition == "http_502":
                            retry_conditions.append("retriable-status-codes")
                        elif condition == "http_503":
                            retry_conditions.append("retriable-status-codes")
                        elif condition == "http_504":
                            retry_conditions.append("retriable-status-codes")
                        elif condition == "retriable_4xx":
                            retry_conditions.append("retriable-4xx")
                        elif condition == "reset":
                            retry_conditions.append("reset")
                        elif condition == "refused":
                            retry_conditions.append("refused-stream")

                    if retry_conditions:
                        output.append(
                            f"                    retry_on: {','.join(set(retry_conditions))}"
                        )

                    # Add retriable status codes if specified
                    retriable_codes = []
                    for condition in retry.retry_on:
                        if condition == "http_502":
                            retriable_codes.append(502)
                        elif condition == "http_503":
                            retriable_codes.append(503)
                        elif condition == "http_504":
                            retriable_codes.append(504)

                    if retriable_codes:
                        output.append("                    retriable_status_codes:")
                        for code in sorted(set(retriable_codes)):
                            output.append(f"                    - {code}")

                    # Add per-try timeout (base_interval for first try)
                    output.append(f"                    per_try_timeout: {retry.base_interval}")

                # Add WebSocket support if configured
                if route.websocket and route.websocket.enabled:
                    ws = route.websocket
                    output.append("                  upgrade_configs:")
                    output.append("                  - upgrade_type: websocket")
                    # Set idle timeout for WebSocket connections
                    output.append(f"                  idle_timeout: {ws.idle_timeout}")
                    # Note: max_message_size and ping_interval are handled at cluster level

                # Add route-level header manipulation
                if route.headers:
                    headers = route.headers
                    # Request headers
                    if headers.request_add or headers.request_set:
                        output.append("                request_headers_to_add:")
                        # Add headers (append: true keeps existing)
                        for key, value in headers.request_add.items():
                            output.append("                - header:")
                            output.append(f"                    key: {key}")
                            output.append(f"                    value: '{value}'")
                            output.append("                  append: true")
                        # Set headers (append: false overwrites existing)
                        for key, value in headers.request_set.items():
                            output.append("                - header:")
                            output.append(f"                    key: {key}")
                            output.append(f"                    value: '{value}'")
                            output.append("                  append: false")
                    if headers.request_remove:
                        output.append("                request_headers_to_remove:")
                        for header_name in headers.request_remove:
                            output.append(f"                - {header_name}")

                    # Response headers
                    if headers.response_add or headers.response_set:
                        output.append("                response_headers_to_add:")
                        # Add headers
                        for key, value in headers.response_add.items():
                            output.append("                - header:")
                            output.append(f"                    key: {key}")
                            output.append(f"                    value: '{value}'")
                            output.append("                  append: true")
                        # Set headers
                        for key, value in headers.response_set.items():
                            output.append("                - header:")
                            output.append(f"                    key: {key}")
                            output.append(f"                    value: '{value}'")
                            output.append("                  append: false")
                    if headers.response_remove:
                        output.append("                response_headers_to_remove:")
                        for header_name in headers.response_remove:
                            output.append(f"                - {header_name}")

                # Add service-level header manipulation if configured
                elif (
                    service.transformation
                    and service.transformation.enabled
                    and service.transformation.headers
                ):
                    headers = service.transformation.headers
                    # Request headers
                    if headers.request_add or headers.request_set:
                        output.append("                request_headers_to_add:")
                        for key, value in headers.request_add.items():
                            output.append("                - header:")
                            output.append(f"                    key: {key}")
                            output.append(f"                    value: '{value}'")
                            output.append("                  append: true")
                        for key, value in headers.request_set.items():
                            output.append("                - header:")
                            output.append(f"                    key: {key}")
                            output.append(f"                    value: '{value}'")
                            output.append("                  append: false")
                    if headers.request_remove:
                        output.append("                request_headers_to_remove:")
                        for header_name in headers.request_remove:
                            output.append(f"                - {header_name}")

                    # Response headers
                    if headers.response_add or headers.response_set:
                        output.append("                response_headers_to_add:")
                        for key, value in headers.response_add.items():
                            output.append("                - header:")
                            output.append(f"                    key: {key}")
                            output.append(f"                    value: '{value}'")
                            output.append("                  append: true")
                        for key, value in headers.response_set.items():
                            output.append("                - header:")
                            output.append(f"                    key: {key}")
                            output.append(f"                    value: '{value}'")
                            output.append("                  append: false")
                    if headers.response_remove:
                        output.append("                response_headers_to_remove:")
                        for header_name in headers.response_remove:
                            output.append(f"                - {header_name}")

                # Add CORS policy if configured
                if route.cors and route.cors.enabled:
                    cors = route.cors
                    output.append("                cors:")
                    output.append("                  allow_origin_string_match:")
                    for origin in cors.allowed_origins:
                        if origin == "*":
                            output.append("                  - safe_regex:")
                            output.append("                      regex: '.*'")
                        else:
                            output.append("                  - exact: '{}'".format(origin))
                    output.append(
                        "                  allow_methods: '{}'".format(
                            ", ".join(cors.allowed_methods)
                        )
                    )
                    output.append(
                        "                  allow_headers: '{}'".format(
                            ", ".join(cors.allowed_headers)
                        )
                    )
                    if cors.expose_headers:
                        output.append(
                            "                  expose_headers: '{}'".format(
                                ", ".join(cors.expose_headers)
                            )
                        )
                    output.append(
                        "                  allow_credentials: {}".format(
                            str(cors.allow_credentials).lower()
                        )
                    )
                    output.append("                  max_age: '{}'".format(cors.max_age))

        # Add WebSocket upgrade support if any route has WebSocket enabled
        has_websocket = any(
            route.websocket and route.websocket.enabled
            for service in config.services
            for route in service.routes
        )
        if has_websocket:
            output.append("          upgrade_configs:")
            output.append("          - upgrade_type: websocket")

        # Access logging
        if config.global_config and config.global_config.logging:
            self._generate_envoy_logging(config.global_config.logging, output)

        # HTTP filters
        output.append("          http_filters:")

        # Add authentication filter if any route has authentication enabled
        has_authentication = any(
            route.authentication and route.authentication.enabled
            for service in config.services
            for route in service.routes
        )
        if has_authentication:
            # Collect authentication configs
            jwt_configs = []
            basic_auth_configs = []
            api_key_configs = []

            for service in config.services:
                for route in service.routes:
                    if route.authentication and route.authentication.enabled:
                        auth = route.authentication
                        if auth.type == "jwt" and auth.jwt:
                            jwt_configs.append(auth.jwt)
                        elif auth.type == "basic" and auth.basic_auth:
                            basic_auth_configs.append(auth.basic_auth)
                        elif auth.type == "api_key" and auth.api_key:
                            api_key_configs.append(auth.api_key)

            # Add JWT authentication filter if needed
            if jwt_configs:
                jwt_config = jwt_configs[0]  # Use first JWT config
                output.append("          - name: envoy.filters.http.jwt_authn")
                output.append("            typed_config:")
                output.append(
                    "              '@type': type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication"
                )
                output.append("              providers:")
                output.append("                jwt_provider:")
                if jwt_config.issuer:
                    output.append(f"                  issuer: '{jwt_config.issuer}'")
                if jwt_config.audience:
                    output.append("                  audiences:")
                    output.append(f"                  - '{jwt_config.audience}'")
                if jwt_config.jwks_uri:
                    output.append("                  remote_jwks:")
                    output.append("                    http_uri:")
                    output.append(f"                      uri: '{jwt_config.jwks_uri}'")
                    output.append("                      cluster: jwks_cluster")
                    output.append("                      timeout: 5s")
                    output.append("                    cache_duration:")
                    output.append("                      seconds: 300")
                output.append("                  from_headers:")
                output.append("                  - name: Authorization")
                output.append("                    value_prefix: 'Bearer '")
                output.append("              rules:")
                output.append("              - match:")
                output.append("                  prefix: /")
                output.append("                requires:")
                output.append("                  provider_name: jwt_provider")

            # Add basic auth or API key validation via Lua filter
            if basic_auth_configs or api_key_configs:
                output.append("          - name: envoy.filters.http.lua")
                output.append("            typed_config:")
                output.append(
                    "              '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
                )
                output.append("              inline_code: |")
                output.append("                function envoy_on_request(request_handle)")

                if basic_auth_configs:
                    basic_config = basic_auth_configs[0]
                    output.append("                  -- Basic Authentication")
                    output.append(
                        "                  local auth_header = request_handle:headers():get('authorization')"
                    )
                    output.append(
                        "                  if auth_header and string.match(auth_header, '^Basic ') then"
                    )
                    output.append("                    -- Validate Basic Auth credentials here")
                    output.append(
                        "                    -- In production, decode base64 and check against user database"
                    )
                    output.append("                  else")
                    output.append("                    request_handle:respond(")
                    output.append("                      {[':status'] = '401',")
                    output.append(
                        f"                       ['www-authenticate'] = 'Basic realm=\"{basic_config.realm}\"'}},"
                    )
                    output.append("                      'Unauthorized')")
                    output.append("                    return")
                    output.append("                  end")

                if api_key_configs:
                    api_key_config = api_key_configs[0]
                    key_name = api_key_config.key_name
                    output.append("                  -- API Key Authentication")
                    output.append(
                        f"                  local api_key = request_handle:headers():get('{key_name.lower()}')"
                    )
                    output.append("                  if not api_key then")
                    output.append("                    request_handle:respond(")
                    output.append("                      {[':status'] = '401'},")
                    output.append("                      'Unauthorized: Missing API Key')")
                    output.append("                    return")
                    output.append("                  end")
                    output.append(
                        "                  -- Validate API key against database/cache here"
                    )

                output.append("                end")

        # Add rate limiting filter if any route has rate limiting enabled
        has_rate_limits = any(
            route.rate_limit and route.rate_limit.enabled
            for service in config.services
            for route in service.routes
        )
        if has_rate_limits:
            # Find the first rate limit config to use as defaults
            first_rate_limit = None
            for service in config.services:
                for route in service.routes:
                    if route.rate_limit and route.rate_limit.enabled:
                        first_rate_limit = route.rate_limit
                        break
                if first_rate_limit:
                    break

            output.append("          - name: envoy.filters.http.local_ratelimit")
            output.append("            typed_config:")
            output.append(
                "              '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit"
            )
            output.append("              stat_prefix: http_local_rate_limiter")
            output.append("              token_bucket:")
            output.append(f"                max_tokens: {first_rate_limit.burst}")
            output.append(
                f"                tokens_per_fill: {first_rate_limit.requests_per_second}"
            )
            output.append("                fill_interval: 1s")
            output.append("              filter_enabled:")
            output.append("                runtime_key: local_rate_limit_enabled")
            output.append("                default_value:")
            output.append("                  numerator: 100")
            output.append("                  denominator: HUNDRED")
            output.append("              filter_enforced:")
            output.append("                runtime_key: local_rate_limit_enforced")
            output.append("                default_value:")
            output.append("                  numerator: 100")
            output.append("                  denominator: HUNDRED")
            output.append("              response_headers_to_add:")
            output.append("                - append: false")
            output.append("                  header:")
            output.append("                    key: x-local-rate-limit")
            output.append("                    value: 'true'")
            output.append(f"              status_code: {first_rate_limit.response_status}")

        # Add body transformation filter if any route has body transformation enabled
        has_body_transformations = any(
            route.body_transformation and route.body_transformation.enabled
            for service in config.services
            for route in service.routes
        )
        if has_body_transformations:
            output.append("          - name: envoy.filters.http.lua")
            output.append("            typed_config:")
            output.append(
                "              '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
            )
            output.append("              inline_code: |")
            output.append("                local cjson = require('cjson')")
            output.append("")
            output.append("                -- Helper function to generate UUID")
            output.append("                function generate_uuid()")
            output.append("                  local random = math.random")
            output.append(
                "                  local template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'"
            )
            output.append("                  return string.gsub(template, '[xy]', function (c)")
            output.append(
                "                    local v = (c == 'x') and random(0, 0xf) or random(8, 0xb)"
            )
            output.append("                    return string.format('%x', v)")
            output.append("                  end)")
            output.append("                end")
            output.append("")
            output.append("                -- Helper function to get current timestamp")
            output.append("                function get_timestamp()")
            output.append("                  return os.date('%Y-%m-%dT%H:%M:%SZ')")
            output.append("                end")
            output.append("")
            output.append("                -- Helper function to transform request body")
            output.append(
                "                function transform_request_body(body_json, add_fields, remove_fields, rename_fields)"
            )
            output.append("                  -- Add fields")
            output.append("                  if add_fields then")
            output.append("                    for key, value in pairs(add_fields) do")
            output.append("                      -- Replace template variables")
            output.append("                      if value == '{{uuid}}' then")
            output.append("                        body_json[key] = generate_uuid()")
            output.append(
                "                      elseif value == '{{now}}' or value == '{{timestamp}}' then"
            )
            output.append("                        body_json[key] = get_timestamp()")
            output.append("                      else")
            output.append("                        body_json[key] = value")
            output.append("                      end")
            output.append("                    end")
            output.append("                  end")
            output.append("")
            output.append("                  -- Remove fields")
            output.append("                  if remove_fields then")
            output.append("                    for _, field_name in ipairs(remove_fields) do")
            output.append("                      body_json[field_name] = nil")
            output.append("                    end")
            output.append("                  end")
            output.append("")
            output.append("                  -- Rename fields")
            output.append("                  if rename_fields then")
            output.append("                    for old_name, new_name in pairs(rename_fields) do")
            output.append("                      if body_json[old_name] ~= nil then")
            output.append("                        body_json[new_name] = body_json[old_name]")
            output.append("                        body_json[old_name] = nil")
            output.append("                      end")
            output.append("                    end")
            output.append("                  end")
            output.append("")
            output.append("                  return body_json")
            output.append("                end")
            output.append("")
            output.append("                -- Helper function to transform response body")
            output.append(
                "                function transform_response_body(body_json, filter_fields, add_fields)"
            )
            output.append("                  -- Filter (remove) sensitive fields")
            output.append("                  if filter_fields then")
            output.append("                    for _, field_name in ipairs(filter_fields) do")
            output.append("                      body_json[field_name] = nil")
            output.append("                    end")
            output.append("                  end")
            output.append("")
            output.append("                  -- Add metadata fields")
            output.append("                  if add_fields then")
            output.append("                    for key, value in pairs(add_fields) do")
            output.append("                      -- Replace template variables")
            output.append("                      if value == '{{uuid}}' then")
            output.append("                        body_json[key] = generate_uuid()")
            output.append(
                "                      elseif value == '{{now}}' or value == '{{timestamp}}' then"
            )
            output.append("                        body_json[key] = get_timestamp()")
            output.append("                      else")
            output.append("                        body_json[key] = value")
            output.append("                      end")
            output.append("                    end")
            output.append("                  end")
            output.append("")
            output.append("                  return body_json")
            output.append("                end")
            output.append("")
            output.append("                -- Request transformation")
            output.append("                function envoy_on_request(request_handle)")
            output.append("                  local path = request_handle:headers():get(':path')")
            output.append("")

            # Generate route-specific transformation logic
            for service in config.services:
                for route in service.routes:
                    if route.body_transformation and route.body_transformation.enabled:
                        bt = route.body_transformation
                        if bt.request:
                            output.append(
                                f"                  -- Body transformation for {service.name} {route.path_prefix}"
                            )
                            output.append(
                                f"                  if string.find(path, '{route.path_prefix}') then"
                            )
                            output.append("                    local body = request_handle:body()")
                            output.append("                    if body and body:length() > 0 then")
                            output.append(
                                "                      local success, body_json = pcall(cjson.decode, body:getBytes(0, body:length()))"
                            )
                            output.append("                      if success then")

                            # Generate add_fields table
                            if bt.request.add_fields:
                                output.append("                        local add_fields = {")
                                for key, value in bt.request.add_fields.items():
                                    if isinstance(value, str):
                                        output.append(
                                            f"                          {key} = '{value}',"
                                        )
                                    else:
                                        output.append(f"                          {key} = {value},")
                                output.append("                        }")
                            else:
                                output.append("                        local add_fields = nil")

                            # Generate remove_fields table
                            if bt.request.remove_fields:
                                output.append("                        local remove_fields = {")
                                for field in bt.request.remove_fields:
                                    output.append(f"                          '{field}',")
                                output.append("                        }")
                            else:
                                output.append("                        local remove_fields = nil")

                            # Generate rename_fields table
                            if bt.request.rename_fields:
                                output.append("                        local rename_fields = {")
                                for old_name, new_name in bt.request.rename_fields.items():
                                    output.append(
                                        f"                          {old_name} = '{new_name}',"
                                    )
                                output.append("                        }")
                            else:
                                output.append("                        local rename_fields = nil")

                            output.append("")
                            output.append(
                                "                        body_json = transform_request_body(body_json, add_fields, remove_fields, rename_fields)"
                            )
                            output.append(
                                "                        local new_body = cjson.encode(body_json)"
                            )
                            output.append(
                                "                        request_handle:body():setBytes(new_body)"
                            )
                            output.append("                      end")
                            output.append("                    end")
                            output.append("                  end")
                            output.append("")

            output.append("                end")
            output.append("")
            output.append("                -- Response transformation")
            output.append("                function envoy_on_response(response_handle)")
            output.append("                  local path = response_handle:headers():get(':path')")
            output.append("")

            # Generate response transformation logic
            for service in config.services:
                for route in service.routes:
                    if route.body_transformation and route.body_transformation.enabled:
                        bt = route.body_transformation
                        if bt.response:
                            output.append(
                                f"                  -- Response transformation for {service.name} {route.path_prefix}"
                            )
                            output.append(
                                f"                  if string.find(path, '{route.path_prefix}') then"
                            )
                            output.append("                    local body = response_handle:body()")
                            output.append("                    if body and body:length() > 0 then")
                            output.append(
                                "                      local success, body_json = pcall(cjson.decode, body:getBytes(0, body:length()))"
                            )
                            output.append("                      if success then")

                            # Generate filter_fields table
                            if bt.response.filter_fields:
                                output.append("                        local filter_fields = {")
                                for field in bt.response.filter_fields:
                                    output.append(f"                          '{field}',")
                                output.append("                        }")
                            else:
                                output.append("                        local filter_fields = nil")

                            # Generate add_fields table
                            if bt.response.add_fields:
                                output.append("                        local add_fields = {")
                                for key, value in bt.response.add_fields.items():
                                    if isinstance(value, str):
                                        output.append(
                                            f"                          {key} = '{value}',"
                                        )
                                    else:
                                        output.append(f"                          {key} = {value},")
                                output.append("                        }")
                            else:
                                output.append("                        local add_fields = nil")

                            output.append("")
                            output.append(
                                "                        body_json = transform_response_body(body_json, filter_fields, add_fields)"
                            )
                            output.append(
                                "                        local new_body = cjson.encode(body_json)"
                            )
                            output.append(
                                "                        response_handle:body():setBytes(new_body)"
                            )
                            output.append("                      end")
                            output.append("                    end")
                            output.append("                  end")
                            output.append("")

            output.append("                end")

        output.append("          - name: envoy.filters.http.router")
        output.append("            typed_config:")
        output.append(
            "              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
        )
        output.append("")

        # Clusters
        output.append("  clusters:")
        for service in config.services:
            self._generate_envoy_cluster(service, output)
            output.append("")

        # Add JWKS cluster if JWT authentication is configured
        if has_authentication:
            jwt_configs = []
            for svc in config.services:
                for route in svc.routes:
                    if (
                        route.authentication
                        and route.authentication.enabled
                        and route.authentication.type == "jwt"
                    ):
                        if route.authentication.jwt and route.authentication.jwt.jwks_uri:
                            jwt_configs.append(route.authentication.jwt)

            if jwt_configs:
                jwt_config = jwt_configs[0]
                # Extract host and port from JWKS URI
                import re

                jwks_match = re.match(r"https?://([^:/]+)(?::(\d+))?", jwt_config.jwks_uri)
                if jwks_match:
                    jwks_host = jwks_match.group(1)
                    jwks_port = jwks_match.group(2) or (
                        "443" if jwt_config.jwks_uri.startswith("https") else "80"
                    )

                    output.append("  - name: jwks_cluster")
                    output.append("    type: STRICT_DNS")
                    output.append("    lb_policy: ROUND_ROBIN")
                    output.append("    load_assignment:")
                    output.append("      cluster_name: jwks_cluster")
                    output.append("      endpoints:")
                    output.append("      - lb_endpoints:")
                    output.append("        - endpoint:")
                    output.append("            address:")
                    output.append("              socket_address:")
                    output.append(f"                address: {jwks_host}")
                    output.append(f"                port_value: {jwks_port}")
                    if jwt_config.jwks_uri.startswith("https"):
                        output.append("    transport_socket:")
                        output.append("      name: envoy.transport_sockets.tls")
                        output.append("      typed_config:")
                        output.append(
                            "        '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext"
                        )
                        output.append("        sni: " + jwks_host)
                    output.append("")

        # Admin
        output.append("admin:")
        output.append("  address:")
        output.append("    socket_address:")
        output.append(f"      address: {config.global_config.host}")
        output.append(f"      port_value: {config.global_config.admin_port}")

        # Metrics export (Prometheus/OpenTelemetry)
        if config.global_config and config.global_config.metrics:
            self._generate_envoy_metrics(config.global_config.metrics, output)

        result = "\n".join(output)
        logger.info(
            f"Envoy configuration generated: {len(result)} bytes, {len(config.services)} services"
        )
        return result

    def _generate_envoy_logging(self, logging_config, output: list):
        """Generate Envoy access logging configuration.

        Args:
            logging_config: LoggingConfig object
            output: Output list to append YAML lines to
        """
        output.append("          access_log:")
        output.append("          - name: envoy.access_loggers.file")
        output.append("            typed_config:")
        output.append(
            "              '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog"
        )
        output.append(f"              path: {logging_config.access_log_path}")

        # Log format (JSON or text)
        if logging_config.format == "json":
            output.append("              json_format:")
            output.append('                request_id: "%REQ(X-REQUEST-ID)%"')
            output.append('                method: "%REQ(:METHOD)%"')
            output.append('                path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"')
            output.append('                protocol: "%PROTOCOL%"')
            output.append('                response_code: "%RESPONSE_CODE%"')
            output.append('                bytes_received: "%BYTES_RECEIVED%"')
            output.append('                bytes_sent: "%BYTES_SENT%"')
            output.append('                duration: "%DURATION%"')
            output.append(
                '                upstream_service_time: "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%"'
            )
            output.append('                x_forwarded_for: "%REQ(X-FORWARDED-FOR)%"')

            # Include configured headers
            for header in logging_config.include_headers:
                safe_header = header.replace("-", "_").lower()
                output.append(f'                {safe_header}: "%REQ({header})%"')

            # Custom fields
            for key, value in logging_config.custom_fields.items():
                output.append(f'                {key}: "{value}"')
        else:
            # Text format
            output.append("              log_format:")
            output.append("                text_format_source:")
            output.append(
                '                  inline_string: "[%START_TIME%] %REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL% %RESPONSE_CODE% %BYTES_RECEIVED% %BYTES_SENT% %DURATION%\\n"'
            )

        # Sampling (if sample_rate < 1.0)
        if logging_config.sample_rate < 1.0:
            sample_percent = int(logging_config.sample_rate * 100)
            output.append("              filter:")
            output.append("                runtime_filter:")
            output.append("                  runtime_key: access_log_sampling")
            output.append("                  percent_sampled:")
            output.append(f"                    numerator: {sample_percent}")
            output.append("                    denominator: HUNDRED")

        output.append("")

    def _generate_envoy_metrics(self, metrics_config, output: list):
        """Generate Envoy metrics export configuration.

        Args:
            metrics_config: MetricsConfig object
            output: Output list to append YAML lines to
        """
        if metrics_config.exporter in ("prometheus", "both"):
            # Prometheus stats endpoint (via admin interface)
            output.append("")
            output.append("# Prometheus metrics available at admin interface")
            output.append(f"# http://<admin_host>:<admin_port>/stats/prometheus")
            logger.info(
                f"Prometheus metrics enabled on admin interface at port {metrics_config.prometheus_port}"
            )

        if metrics_config.exporter in ("opentelemetry", "both"):
            # OpenTelemetry exporter
            if metrics_config.opentelemetry_endpoint:
                output.append("")
                output.append("stats_sinks:")
                output.append("- name: envoy.stat_sinks.open_telemetry")
                output.append("  typed_config:")
                output.append(
                    "    '@type': type.googleapis.com/envoy.extensions.stat_sinks.open_telemetry.v3.SinkConfig"
                )
                output.append("    grpc_service:")
                output.append("      envoy_grpc:")
                output.append("        cluster_name: opentelemetry_collector")
                logger.info(
                    f"OpenTelemetry metrics export enabled to {metrics_config.opentelemetry_endpoint}"
                )

    def _generate_envoy_cluster(self, service, output: list):
        """Generate Envoy cluster configuration with health checks and load balancing.

        Creates Envoy cluster configuration supporting:
        - Multiple endpoints with load balancing
        - Active health checks (HTTP/TCP/gRPC)
        - Passive health checks (Outlier Detection)
        - Load balancing policies

        Args:
            service: Service object with upstream configuration
            output: Output list to append YAML lines to
        """
        output.append(f"  - name: {service.name}_cluster")
        output.append("    type: STRICT_DNS")

        # Configure load balancing policy
        lb_policy = "ROUND_ROBIN"  # Default
        if service.upstream.load_balancer:
            algorithm = service.upstream.load_balancer.algorithm
            # Envoy policies: ROUND_ROBIN, LEAST_REQUEST, RING_HASH, RANDOM, MAGLEV
            envoy_lb_map = {
                "round_robin": "ROUND_ROBIN",
                "least_conn": "LEAST_REQUEST",
                "ip_hash": "RING_HASH",
                "weighted": "ROUND_ROBIN",  # Weighted uses ROUND_ROBIN with endpoint weights
            }
            lb_policy = envoy_lb_map.get(algorithm, "ROUND_ROBIN")

        output.append(f"    lb_policy: {lb_policy}")

        # Configure timeout (default connect timeout for cluster)
        # Route-specific timeouts can override this
        default_timeout = "5s"
        for route in service.routes:
            if route.timeout:
                default_timeout = route.timeout.connect
                break
        output.append(f"    connect_timeout: {default_timeout}")

        # Configure ring hash for IP-based hashing
        if service.upstream.load_balancer and service.upstream.load_balancer.algorithm == "ip_hash":
            output.append("    ring_hash_lb_config:")
            output.append("      minimum_ring_size: 1024")

        # gRPC support
        if service.type == "grpc":
            output.append("    http2_protocol_options: {}")

        # Configure health checks
        if service.upstream.health_check:
            hc = service.upstream.health_check

            # Active health checks
            if hc.active and hc.active.enabled:
                active = hc.active
                output.append("    health_checks:")
                output.append("    - timeout: " + active.timeout)
                output.append("      interval: " + active.interval)
                output.append(f"      unhealthy_threshold: {active.unhealthy_threshold}")
                output.append(f"      healthy_threshold: {active.healthy_threshold}")
                output.append("      http_health_check:")
                output.append(f"        path: {active.http_path}")
                output.append("        expected_statuses:")
                for status_code in active.healthy_status_codes:
                    output.append(f"        - start: {status_code}")
                    output.append(f"          end: {status_code + 1}")

            # Passive health checks (Outlier Detection)
            if hc.passive and hc.passive.enabled:
                passive = hc.passive
                output.append("    outlier_detection:")
                output.append(f"      consecutive_5xx: {passive.max_failures}")
                output.append("      interval: 10s")
                output.append("      base_ejection_time: 30s")
                output.append("      max_ejection_percent: 50")
                output.append("      enforcing_consecutive_5xx: 100")
                output.append("      success_rate_minimum_hosts: 5")
                output.append("      success_rate_request_volume: 10")
                output.append("      enforcing_success_rate: 100")

        # Check if any route has circuit breaker enabled (for backward compatibility)
        circuit_breaker = None
        for route in service.routes:
            if route.circuit_breaker and route.circuit_breaker.enabled:
                circuit_breaker = route.circuit_breaker
                break

        if circuit_breaker and not (
            service.upstream.health_check and service.upstream.health_check.passive
        ):
            # Only add if not already added via passive health check
            output.append("    outlier_detection:")
            output.append(f"      consecutive_5xx: {circuit_breaker.max_failures}")
            output.append("      interval: 10s")
            timeout_value = circuit_breaker.timeout.rstrip("s")
            output.append(f"      base_ejection_time: {timeout_value}s")
            output.append("      max_ejection_percent: 50")
            output.append("      enforcing_consecutive_5xx: 100")
            output.append(f"      success_rate_minimum_hosts: {circuit_breaker.half_open_requests}")
            output.append("      success_rate_request_volume: 10")
            output.append("      enforcing_success_rate: 100")

        # Configure load assignment (endpoints)
        output.append("    load_assignment:")
        output.append(f"      cluster_name: {service.name}_cluster")
        output.append("      endpoints:")
        output.append("      - lb_endpoints:")

        if service.upstream.targets:
            # Multiple targets mode
            for target in service.upstream.targets:
                output.append("        - endpoint:")
                output.append("            address:")
                output.append("              socket_address:")
                output.append(f"                address: {target.host}")
                output.append(f"                port_value: {target.port}")
                if (
                    service.upstream.load_balancer
                    and service.upstream.load_balancer.algorithm == "weighted"
                ):
                    output.append("          load_balancing_weight:")
                    output.append(f"            value: {target.weight}")
        else:
            # Simple mode (single host/port)
            output.append("        - endpoint:")
            output.append("            address:")
            output.append("              socket_address:")
            output.append(f"                address: {service.upstream.host}")
            output.append(f"                port_value: {service.upstream.port}")

    def deploy(
        self, config: Config, output_file: Optional[str] = None, admin_url: Optional[str] = None
    ) -> bool:
        """Deploy Envoy configuration.

        Deploys configuration via file-based approach. Optionally triggers
        hot-reload via Envoy Admin API if admin_url is provided.

        Deployment Methods:
            1. File-based (default): Write config to file for Envoy to load
            2. Hot-reload (optional): Trigger config reload via Admin API

        Args:
            config: Configuration to deploy
            output_file: Path to write config file (default: envoy.yaml)
            admin_url: Envoy Admin API URL (default: http://localhost:9901)

        Returns:
            True if deployment successful

        Raises:
            IOError: If file write fails
            requests.RequestException: If Admin API call fails

        Example:
            >>> provider = EnvoyProvider()
            >>> config = Config.from_yaml("config.yaml")
            >>> # File-based deployment
            >>> provider.deploy(config, output_file="/etc/envoy/envoy.yaml")
            True
            >>> # With hot-reload
            >>> provider.deploy(config, admin_url="http://envoy:9901")
            True
        """
        logger.info(f"Deploying Envoy configuration to file: {output_file or 'envoy.yaml'}")
        # Generate configuration
        generated_config = self.generate(config)

        # Determine output file
        if output_file is None:
            output_file = "envoy.yaml"

        # Write configuration to file
        try:
            # Create directory if it doesn't exist
            os.makedirs(os.path.dirname(output_file) or ".", exist_ok=True)

            with open(output_file, "w") as f:
                f.write(generated_config)

            logger.info(f"Envoy configuration successfully written to {output_file}")
            print(f"✓ Envoy configuration written to {output_file}")
        except IOError as e:
            logger.error(f"Failed to write Envoy config file to {output_file}: {e}")
            print(f"✗ Failed to write config file: {e}")
            return False

        # Optionally trigger hot-reload via Admin API
        if admin_url:
            admin_url = admin_url.rstrip("/")
            logger.debug(f"Checking Envoy Admin API at {admin_url}")
            try:
                # Check if Envoy is reachable
                response = requests.get(f"{admin_url}/ready", timeout=5)

                if response.status_code == 200:
                    logger.info(f"Envoy Admin API is reachable at {admin_url}")
                    print(f"✓ Envoy Admin API is reachable at {admin_url}")
                    print("  Note: File-based config requires Envoy restart or --drain-strategy")
                    print(f"  To reload: docker restart <envoy-container>")
                else:
                    logger.warning(f"Envoy Admin API returned status {response.status_code}")
                    print(f"⚠ Envoy Admin API returned status {response.status_code}")

            except requests.RequestException as e:
                logger.warning(f"Could not reach Envoy Admin API at {admin_url}: {e}")
                print(f"⚠ Could not reach Envoy Admin API: {e}")
                print(f"  Config written to {output_file}, but manual reload required")

        logger.info("Envoy deployment completed successfully")
        return True

    def parse(self, provider_config: str) -> Config:
        """Parse Envoy YAML configuration to GAL format.

        Converts Envoy static_resources configuration into GAL Config model.
        Extracts clusters, listeners, routes, health checks, and load balancing.

        Args:
            provider_config: Envoy YAML configuration string

        Returns:
            GAL Config object

        Raises:
            ValueError: If parsing fails or configuration is invalid

        Example:
            >>> provider = EnvoyProvider()
            >>> envoy_yaml = '''
            ... static_resources:
            ...   clusters:
            ...   - name: api_cluster
            ...     connect_timeout: 5s
            ... '''
            >>> config = provider.parse(envoy_yaml)
        """
        logger.info("Parsing Envoy configuration to GAL format")

        try:
            envoy_config = yaml.safe_load(provider_config)
        except yaml.YAMLError as e:
            raise ValueError(f"Invalid Envoy YAML: {e}")

        if not isinstance(envoy_config, dict):
            raise ValueError("Envoy config must be a YAML dictionary")

        static_resources = envoy_config.get("static_resources", {})
        clusters = static_resources.get("clusters", [])
        listeners = static_resources.get("listeners", [])

        # Parse clusters → Services
        services = []
        cluster_map = {}  # name → Service mapping

        for cluster in clusters:
            service = self._parse_cluster(cluster)
            if service:
                services.append(service)
                cluster_map[cluster.get("name")] = service

        # Parse listeners/routes and map to services
        for listener in listeners:
            self._parse_listener_routes(listener, cluster_map)

        # Create global config
        global_config = GlobalConfig(
            host="0.0.0.0",
            port=10000,  # Default Envoy admin port
        )

        config = Config(
            version="1.0",
            provider="envoy",
            global_config=global_config,
            services=services,
        )

        logger.info(
            f"Successfully parsed Envoy config: {len(services)} services, "
            f"{sum(len(s.routes) for s in services)} routes"
        )
        return config

    def _parse_cluster(self, cluster: Dict[str, Any]) -> Optional[Service]:
        """Parse Envoy cluster to GAL Service."""
        cluster_name = cluster.get("name", "")
        if not cluster_name:
            logger.warning("Skipping cluster without name")
            return None

        # Remove common suffixes to get service name
        service_name = cluster_name.replace("_cluster", "").replace("-cluster", "")

        # Parse load assignment (endpoints)
        targets = []
        load_assignment = cluster.get("load_assignment", {})
        endpoints = load_assignment.get("endpoints", [])

        for endpoint_group in endpoints:
            lb_endpoints = endpoint_group.get("lb_endpoints", [])
            for lb_endpoint in lb_endpoints:
                endpoint = lb_endpoint.get("endpoint", {})
                address = endpoint.get("address", {})
                socket_address = address.get("socket_address", {})

                host = socket_address.get("address")
                port = socket_address.get("port_value")

                if host and port:
                    weight = lb_endpoint.get("load_balancing_weight", {}).get("value", 1)
                    targets.append(UpstreamTarget(host=host, port=port, weight=weight))

        if not targets:
            logger.warning(f"Cluster {cluster_name} has no valid endpoints")
            return None

        # Parse health checks
        health_checks = cluster.get("health_checks", [])
        active_hc = None
        passive_hc = None

        if health_checks:
            hc = health_checks[0]  # Take first health check
            timeout = hc.get("timeout", "")
            interval = hc.get("interval", "")
            unhealthy_threshold = hc.get("unhealthy_threshold", 3)
            healthy_threshold = hc.get("healthy_threshold", 2)

            # Check if it's HTTP health check
            http_hc = hc.get("http_health_check", {})
            if http_hc:
                path = http_hc.get("path", "/health")
                active_hc = ActiveHealthCheck(
                    enabled=True,
                    http_path=path,
                    interval=interval or "10s",
                    timeout=timeout or "5s",
                    unhealthy_threshold=unhealthy_threshold,
                    healthy_threshold=healthy_threshold,
                )

        # Parse outlier detection (passive health checks)
        outlier_detection = cluster.get("outlier_detection", {})
        if outlier_detection:
            consecutive_5xx = outlier_detection.get("consecutive_5xx", 5)
            passive_hc = PassiveHealthCheck(enabled=True, max_failures=consecutive_5xx)

        # Parse load balancing policy
        lb_policy = cluster.get("lb_policy", "ROUND_ROBIN")
        lb_algorithm = self._map_envoy_lb_policy(lb_policy)

        # Create upstream
        health_check = None
        if active_hc or passive_hc:
            health_check = HealthCheckConfig(active=active_hc, passive=passive_hc)

        upstream = Upstream(
            targets=targets,
            health_check=health_check,
            load_balancer=LoadBalancerConfig(algorithm=lb_algorithm),
        )

        # Create service with empty routes (filled by listener parsing)
        service = Service(
            name=service_name,
            type="rest",  # Default to REST
            protocol="http",
            upstream=upstream,
            routes=[],
        )

        return service

    def _map_envoy_lb_policy(self, lb_policy: str) -> str:
        """Map Envoy LB policy to GAL algorithm."""
        mapping = {
            "ROUND_ROBIN": "round_robin",
            "LEAST_REQUEST": "least_conn",
            "RING_HASH": "ip_hash",
            "RANDOM": "round_robin",  # Fallback
            "MAGLEV": "ip_hash",  # Consistent hashing
        }
        return mapping.get(lb_policy, "round_robin")

    def _parse_listener_routes(
        self, listener: Dict[str, Any], cluster_map: Dict[str, Service]
    ) -> None:
        """Parse listener routes and add them to corresponding services."""
        filter_chains = listener.get("filter_chains", [])

        for filter_chain in filter_chains:
            filters = filter_chain.get("filters", [])

            for filter_config in filters:
                if filter_config.get("name") != "envoy.filters.network.http_connection_manager":
                    continue

                typed_config = filter_config.get("typed_config", {})
                route_config = typed_config.get("route_config", {})
                virtual_hosts = route_config.get("virtual_hosts", [])

                for vhost in virtual_hosts:
                    routes = vhost.get("routes", [])

                    for route in routes:
                        match = route.get("match", {})
                        route_action = route.get("route", {})

                        # Extract path
                        path_prefix = match.get("prefix", "/")

                        # Extract cluster
                        cluster_name = route_action.get("cluster", "")
                        if cluster_name in cluster_map:
                            service = cluster_map[cluster_name]

                            # Create GAL route
                            gal_route = Route(path_prefix=path_prefix)
                            service.routes.append(gal_route)

                            logger.debug(f"Mapped route {path_prefix} → service {service.name}")
