"""
Envoy Proxy provider implementation.

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

import os
import logging
import requests
from typing import Optional
from ..provider import Provider
from ..config import Config

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 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))

        # 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 transformation filter if any service has transformations
        has_transformations = any(s.transformation and s.transformation.enabled for s in config.services)
        if has_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("                function envoy_on_request(request_handle)")
            output.append("                  -- Transformation logic here")
            output.append("                  local path = request_handle:headers():get(':path')")
            
            for service in config.services:
                if service.transformation and service.transformation.enabled:
                    output.append(f"                  -- {service.name} transformations")
                    for route in service.routes:
                        output.append(f"                  if string.find(path, '{route.path_prefix}') then")
                        output.append("                    local body = request_handle:body()")
                        output.append("                    if body then")
                        output.append("                      -- Apply defaults and computed fields")
                        output.append("                    end")
                        output.append("                  end")
            
            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}")

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

    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 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
