"""
Traefik proxy provider implementation.

Generates Traefik dynamic configuration in YAML format with support
for HTTP routers, services, and middleware plugins.
"""

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

logger = logging.getLogger(__name__)


class TraefikProvider(Provider):
    """Traefik reverse proxy and load balancer provider.

    Generates dynamic configuration for Traefik, a modern HTTP reverse
    proxy and load balancer with automatic service discovery and
    Let's Encrypt support.

    Output Format:
        YAML file with dynamic configuration:
        - http.routers: Routing rules with PathPrefix matching
        - http.services: Backend service definitions with load balancing
        - http.middlewares: Transformation and manipulation plugins

    Transformations:
        Implemented using middleware plugins.
        Note: Middleware plugins require custom Go development.
        Generated config includes placeholder middleware definitions
        for default value transformations.

    gRPC Support:
        Traefik supports gRPC via HTTP/2 automatically.
        Routes are configured the same as REST services.

    Limitations:
        - Middleware plugins require Go development
        - No native computed field support
        - Transformations need custom middleware implementation
        - Focus on routing/load balancing, not payload manipulation

    Ideal For:
        - Docker Swarm and Kubernetes
        - Container-based deployments
        - Development environments
        - Automatic service discovery

    Example:
        >>> provider = TraefikProvider()
        >>> provider.name()
        'traefik'
        >>> config = Config.from_yaml("gateway.yaml")
        >>> output = provider.generate(config)
        >>> "http:" in output
        True

    See Also:
        https://doc.traefik.io/traefik/routing/overview/
    """

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

        Returns:
            str: "traefik"
        """
        return "traefik"

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

        Traefik has minimal validation requirements at config generation.
        Most validation occurs when Traefik loads the configuration.

        Args:
            config: Configuration to validate

        Returns:
            True (Traefik validates at runtime)

        Example:
            >>> provider = TraefikProvider()
            >>> config = Config(...)
            >>> provider.validate(config)
            True
        """
        logger.debug(f"Validating Traefik configuration: {len(config.services)} services")
        return True

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

        Creates Traefik dynamic config with routers, services, and
        optional middleware for transformations.

        Configuration Structure:
            - http:
                - routers: PathPrefix-based routing rules
                - services: LoadBalancer configurations
                - middlewares: Transformation plugins (if enabled)

        Args:
            config: Configuration object containing services

        Returns:
            Complete Traefik dynamic YAML configuration

        Example:
            >>> provider = TraefikProvider()
            >>> config = Config.from_yaml("config.yaml")
            >>> yaml_output = provider.generate(config)
            >>> "routers:" in yaml_output
            True
            >>> "services:" in yaml_output
            True
        """
        logger.info(f"Generating Traefik configuration for {len(config.services)} services")
        output = []
        output.append("# Traefik Configuration Generated by GAL")
        output.append("")
        output.append("http:")
        output.append("  routers:")
        
        for service in config.services:
            for i, route in enumerate(service.routes):
                router_name = f"{service.name}_router_{i}"
                output.append(f"    {router_name}:")
                output.append(f"      rule: 'PathPrefix(`{route.path_prefix}`)'")
                output.append(f"      service: {service.name}_service")

                # Collect middlewares for this route
                middlewares = []
                if route.authentication and route.authentication.enabled:
                    middlewares.append(f"{router_name}_auth")
                if service.transformation and service.transformation.enabled:
                    middlewares.append(f"{service.name}_transform")
                if route.rate_limit and route.rate_limit.enabled:
                    middlewares.append(f"{router_name}_ratelimit")
                if route.headers:
                    middlewares.append(f"{router_name}_headers")
                if route.cors and route.cors.enabled:
                    middlewares.append(f"{router_name}_cors")
                if route.circuit_breaker and route.circuit_breaker.enabled:
                    middlewares.append(f"{router_name}_circuitbreaker")

                # Add middlewares if any
                if middlewares:
                    output.append("      middlewares:")
                    for mw in middlewares:
                        output.append(f"        - {mw}")

                output.append("")
        
        output.append("  services:")
        for service in config.services:
            self._generate_traefik_service(service, output)
            output.append("")
        
        # Middlewares for authentication, transformations, rate limiting, headers, and CORS
        has_authentication = any(
            route.authentication and route.authentication.enabled
            for service in config.services
            for route in service.routes
        )
        has_transformations = any(s.transformation and s.transformation.enabled for s in config.services)
        has_rate_limits = any(
            route.rate_limit and route.rate_limit.enabled
            for service in config.services
            for route in service.routes
        )
        has_headers = any(
            route.headers
            for service in config.services
            for route in service.routes
        )
        has_cors = any(
            route.cors and route.cors.enabled
            for service in config.services
            for route in service.routes
        )
        has_circuit_breakers = any(
            route.circuit_breaker and route.circuit_breaker.enabled
            for service in config.services
            for route in service.routes
        )

        if has_authentication or has_transformations or has_rate_limits or has_headers or has_cors or has_circuit_breakers:
            output.append("  middlewares:")

            # Authentication middlewares
            for service in config.services:
                for i, route in enumerate(service.routes):
                    if route.authentication and route.authentication.enabled:
                        router_name = f"{service.name}_router_{i}"
                        auth = route.authentication

                        if auth.type == "basic":
                            output.append(f"    {router_name}_auth:")
                            output.append("      basicAuth:")
                            if auth.basic_auth and auth.basic_auth.users:
                                output.append("        users:")
                                for username, password in auth.basic_auth.users.items():
                                    # Traefik expects htpasswd format, but for simplicity using plain format
                                    # In production, use: htpasswd -nb username password
                                    output.append(f"          - '{username}:{password}'")
                                if auth.basic_auth.realm:
                                    output.append(f"        realm: '{auth.basic_auth.realm}'")
                            output.append("")

                        elif auth.type == "api_key":
                            # Traefik doesn't have native API key auth, use forwardAuth to external service
                            output.append(f"    {router_name}_auth:")
                            output.append("      forwardAuth:")
                            output.append("        address: 'http://api-key-validator:8080/validate'")
                            key_name = auth.api_key.key_name if auth.api_key else "X-API-Key"
                            output.append("        authRequestHeaders:")
                            output.append(f"          - '{key_name}'")
                            output.append("")

                        elif auth.type == "jwt":
                            # Traefik uses forwardAuth for JWT validation
                            output.append(f"    {router_name}_auth:")
                            output.append("      forwardAuth:")
                            if auth.jwt and auth.jwt.jwks_uri:
                                output.append(f"        address: 'http://jwt-validator:8080/validate'")
                            else:
                                output.append("        address: 'http://jwt-validator:8080/validate'")
                            output.append("        authRequestHeaders:")
                            output.append("          - 'Authorization'")
                            output.append("")

            # Transformation middlewares
            for service in config.services:
                if service.transformation and service.transformation.enabled:
                    output.append(f"    {service.name}_transform:")
                    output.append("      plugin:")
                    output.append(f"        {service.name}_transformer:")
                    output.append("          defaults:")
                    for key, value in service.transformation.defaults.items():
                        output.append(f"            {key}: '{value}'")
                    output.append("")

            # Rate limiting middlewares
            for service in config.services:
                for i, route in enumerate(service.routes):
                    if route.rate_limit and route.rate_limit.enabled:
                        router_name = f"{service.name}_router_{i}"
                        output.append(f"    {router_name}_ratelimit:")
                        output.append("      rateLimit:")
                        output.append(f"        average: {route.rate_limit.requests_per_second}")
                        output.append(f"        burst: {route.rate_limit.burst}")
                        output.append("")

            # Header manipulation middlewares (route-level)
            for service in config.services:
                for i, route in enumerate(service.routes):
                    if route.headers:
                        router_name = f"{service.name}_router_{i}"
                        headers = route.headers
                        output.append(f"    {router_name}_headers:")
                        output.append("      headers:")

                        # Request headers
                        if headers.request_add:
                            output.append("        customRequestHeaders:")
                            for key, value in headers.request_add.items():
                                output.append(f"          {key}: '{value}'")
                        if headers.request_remove:
                            output.append("        customRequestHeaders:")
                            for header_name in headers.request_remove:
                                output.append(f"          {header_name}: ''")  # Empty value removes header

                        # Response headers
                        if headers.response_add:
                            output.append("        customResponseHeaders:")
                            for key, value in headers.response_add.items():
                                output.append(f"          {key}: '{value}'")
                        if headers.response_remove:
                            output.append("        customResponseHeaders:")
                            for header_name in headers.response_remove:
                                output.append(f"          {header_name}: ''")  # Empty value removes header
                        output.append("")

            # Service-level header manipulation
            for service in config.services:
                if service.transformation and service.transformation.enabled and service.transformation.headers:
                    headers = service.transformation.headers
                    output.append(f"    {service.name}_headers:")
                    output.append("      headers:")

                    # Request headers
                    if headers.request_add:
                        output.append("        customRequestHeaders:")
                        for key, value in headers.request_add.items():
                            output.append(f"          {key}: '{value}'")
                    if headers.request_remove:
                        output.append("        customRequestHeaders:")
                        for header_name in headers.request_remove:
                            output.append(f"          {header_name}: ''")

                    # Response headers
                    if headers.response_add:
                        output.append("        customResponseHeaders:")
                        for key, value in headers.response_add.items():
                            output.append(f"          {key}: '{value}'")
                    if headers.response_remove:
                        output.append("        customResponseHeaders:")
                        for header_name in headers.response_remove:
                            output.append(f"          {header_name}: ''")
                    output.append("")

            # CORS middlewares (route-level)
            for service in config.services:
                for i, route in enumerate(service.routes):
                    if route.cors and route.cors.enabled:
                        router_name = f"{service.name}_router_{i}"
                        cors = route.cors
                        output.append(f"    {router_name}_cors:")
                        output.append("      headers:")
                        output.append("        accessControlAllowMethods:")
                        for method in cors.allowed_methods:
                            output.append(f"          - {method}")
                        output.append("        accessControlAllowOriginList:")
                        for origin in cors.allowed_origins:
                            output.append(f"          - '{origin}'")
                        if cors.allowed_headers:
                            output.append("        accessControlAllowHeaders:")
                            for header in cors.allowed_headers:
                                output.append(f"          - {header}")
                        if cors.expose_headers:
                            output.append("        accessControlExposeHeaders:")
                            for header in cors.expose_headers:
                                output.append(f"          - {header}")
                        output.append(f"        accessControlAllowCredentials: {str(cors.allow_credentials).lower()}")
                        output.append(f"        accessControlMaxAge: {cors.max_age}")
                        output.append("")

            # Circuit Breaker middlewares (route-level)
            for service in config.services:
                for i, route in enumerate(service.routes):
                    if route.circuit_breaker and route.circuit_breaker.enabled:
                        router_name = f"{service.name}_router_{i}"
                        cb = route.circuit_breaker
                        output.append(f"    {router_name}_circuitbreaker:")
                        output.append("      circuitBreaker:")

                        # Traefik uses expression-based circuit breaker
                        # Build expression based on unhealthy status codes and max failures
                        if cb.unhealthy_status_codes:
                            min_code = min(cb.unhealthy_status_codes)
                            max_code = max(cb.unhealthy_status_codes) + 1
                            # Calculate failure ratio threshold from max_failures
                            # Assuming a window of 10 requests, convert max_failures to ratio
                            failure_ratio = min(cb.max_failures / 10.0, 0.9)
                            expression = f"ResponseCodeRatio({min_code}, {max_code}, 0, 600) > {failure_ratio:.2f}"
                        else:
                            # Fallback to network error ratio
                            failure_ratio = min(cb.max_failures / 10.0, 0.9)
                            expression = f"NetworkErrorRatio() > {failure_ratio:.2f}"

                        output.append(f"        expression: '{expression}'")
                        output.append("")

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

    def _generate_traefik_service(self, service, output: list):
        """Generate Traefik service configuration with health checks and load balancing.

        Creates Traefik service LoadBalancer configuration supporting:
        - Multiple servers with weights
        - Health checks (HTTP/HTTPS probing)
        - Sticky sessions
        - Load balancing (implicit round-robin or weighted)

        Args:
            service: Service object with upstream configuration
            output: Output list to append YAML lines to
        """
        output.append(f"    {service.name}_service:")
        output.append("      loadBalancer:")

        # Configure servers
        output.append("        servers:")
        if service.upstream.targets:
            # Multiple targets mode
            for target in service.upstream.targets:
                output.append(f"        - url: 'http://{target.host}:{target.port}'")
                if service.upstream.load_balancer and service.upstream.load_balancer.algorithm == "weighted":
                    output.append(f"          weight: {target.weight}")
        else:
            # Simple mode (single host/port)
            output.append(f"        - url: 'http://{service.upstream.host}:{service.upstream.port}'")

        # Configure sticky sessions
        if service.upstream.load_balancer and service.upstream.load_balancer.sticky_sessions:
            output.append("        sticky:")
            output.append("          cookie:")
            cookie_name = service.upstream.load_balancer.cookie_name or "galSession"
            output.append(f"            name: {cookie_name}")
            output.append("            httpOnly: true")

        # Configure health checks
        if service.upstream.health_check and service.upstream.health_check.active:
            active = service.upstream.health_check.active
            if active.enabled:
                output.append("        healthCheck:")
                output.append(f"          path: {active.http_path}")
                output.append(f"          interval: {active.interval}")
                output.append(f"          timeout: {active.timeout}")

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

        Deploys configuration via file-based approach (Traefik File Provider).
        Optionally checks if Traefik API is reachable for verification.

        Deployment Methods:
            1. File Provider (standard): Write dynamic config to file
            2. API check (optional): Verify Traefik can access the config

        Args:
            config: Configuration to deploy
            output_file: Path to write config file (default: traefik.yaml)
            api_url: Traefik API/Dashboard URL for verification
                    (default: http://localhost:8080)

        Returns:
            True if deployment successful

        Raises:
            IOError: If file write fails

        Example:
            >>> provider = TraefikProvider()
            >>> config = Config.from_yaml("config.yaml")
            >>> # File-based deployment
            >>> provider.deploy(config, output_file="/etc/traefik/dynamic/gal.yaml")
            True
            >>> # With API verification
            >>> provider.deploy(config, api_url="http://traefik:8080")
            True

        Note:
            Traefik automatically reloads configuration from file providers.
            No API call needed for deployment - just write the file!
        """
        logger.info(f"Deploying Traefik configuration to file: {output_file or 'traefik.yaml'}")
        # Generate configuration
        generated_config = self.generate(config)

        # Determine output file
        if output_file is None:
            output_file = "traefik.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"Traefik configuration successfully written to {output_file}")
            print(f"✓ Traefik configuration written to {output_file}")
            print("  Traefik will auto-reload this file if configured with File Provider")
        except IOError as e:
            logger.error(f"Failed to write Traefik config file to {output_file}: {e}")
            print(f"✗ Failed to write config file: {e}")
            return False

        # Optionally verify Traefik API is reachable
        if api_url:
            api_url = api_url.rstrip('/')
            logger.debug(f"Checking Traefik API at {api_url}")
            try:
                # Check if Traefik API/Dashboard is reachable
                response = requests.get(f"{api_url}/api/overview", timeout=5)

                if response.status_code == 200:
                    logger.info(f"Traefik API is reachable at {api_url}")
                    print(f"✓ Traefik API is reachable at {api_url}")
                    print(f"  Dashboard: {api_url}/dashboard/")
                else:
                    logger.warning(f"Traefik API returned status {response.status_code}")
                    print(f"⚠ Traefik API returned status {response.status_code}")

            except requests.RequestException as e:
                logger.warning(f"Could not reach Traefik API at {api_url}: {e}")
                print(f"⚠ Could not reach Traefik API: {e}")
                print(f"  Config written to {output_file}")
                print("  Ensure Traefik File Provider is configured:")
                print(f"    --providers.file.filename={output_file}")

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