"""
Kong API Gateway provider implementation.

Generates Kong declarative configuration (DB-less mode) in YAML format
with support for services, routes, and plugin-based transformations.
"""

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

logger = logging.getLogger(__name__)


class KongProvider(Provider):
    """Kong API Gateway provider.

    Generates declarative configuration for Kong Gateway in DB-less mode.
    Kong is a plugin-based API gateway with extensive enterprise features
    and a rich ecosystem of plugins.

    Output Format:
        YAML file in Kong Declarative Configuration format (v3.0):
        - _format_version: Version identifier
        - services: Service definitions with routes
        - routes: Path-based routing rules
        - plugins: Request transformer and other plugins

    Transformations:
        Implemented using Kong's request-transformer plugin.
        Default values are added as custom headers (x-default-*).
        Note: Computed fields (UUID, timestamp) are not natively supported
        and would require custom plugin development.

    gRPC Support:
        Native support via protocol: grpc in service definitions.
        Automatic HTTP/2 handling by Kong.

    Limitations:
        - Computed fields require custom plugins
        - Defaults added as headers, not body fields
        - Advanced transformations need Lua plugins

    Example:
        >>> provider = KongProvider()
        >>> provider.name()
        'kong'
        >>> config = Config.from_yaml("gateway.yaml")
        >>> output = provider.generate(config)
        >>> "_format_version:" in output
        True

    See Also:
        https://docs.konghq.com/gateway/latest/production/deployment-topologies/db-less-and-declarative-config/
    """

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

        Returns:
            str: "kong"
        """
        return "kong"

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

        Kong has minimal validation requirements. Most configurations
        are accepted and validated by Kong at runtime.

        Args:
            config: Configuration to validate

        Returns:
            True (Kong validates at runtime)

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

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

        Creates a Kong declarative config file (v3.0) with services,
        routes, and transformation plugins.

        Configuration Structure:
            - _format_version: '3.0'
            - services: Service and route definitions
            - plugins: Global and service-specific plugins

        Args:
            config: Configuration object containing services

        Returns:
            Complete Kong declarative YAML configuration

        Example:
            >>> provider = KongProvider()
            >>> config = Config.from_yaml("config.yaml")
            >>> yaml_output = provider.generate(config)
            >>> "services:" in yaml_output
            True
            >>> "routes:" in yaml_output
            True
        """
        logger.info(f"Generating Kong configuration for {len(config.services)} services")
        output = []
        output.append("# Kong Declarative Configuration Generated by GAL")
        output.append("_format_version: '3.0'")
        output.append("")

        # Generate upstreams first (if needed for load balancing or health checks)
        upstreams_needed = []
        for service in config.services:
            if (service.upstream.targets or
                service.upstream.health_check or
                service.upstream.load_balancer):
                upstreams_needed.append(service)

        if upstreams_needed:
            output.append("upstreams:")
            for service in upstreams_needed:
                self._generate_kong_upstream(service, output)
            output.append("")

        output.append("services:")

        for service in config.services:
            output.append(f"- name: {service.name}")
            if service.type == "grpc":
                output.append("  protocol: grpc")
            else:
                output.append("  protocol: http")

            # If using upstream (for LB/HC), reference it; otherwise use direct host/port
            if service in upstreams_needed:
                output.append(f"  host: {service.name}_upstream")
            else:
                output.append(f"  host: {service.upstream.host}")
                output.append(f"  port: {service.upstream.port}")

            output.append("  routes:")
            
            for route in service.routes:
                output.append(f"  - name: {service.name}_route")
                output.append("    paths:")
                output.append(f"    - {route.path_prefix}")
                if route.methods:
                    output.append("    methods:")
                    for method in route.methods:
                        output.append(f"    - {method}")

                # Collect plugins for this route
                route_plugins = []

                # Add authentication plugin if configured
                if route.authentication and route.authentication.enabled:
                    auth = route.authentication
                    if auth.type == "basic":
                        route_plugins.append({
                            "name": "basic-auth",
                            "config": {
                                "hide_credentials": True
                            }
                        })
                    elif auth.type == "api_key":
                        key_name = auth.api_key.key_name if auth.api_key else "X-API-Key"
                        in_location = auth.api_key.in_location if auth.api_key else "header"
                        route_plugins.append({
                            "name": "key-auth",
                            "config": {
                                "key_names": [key_name],
                                "key_in_header": in_location == "header",
                                "key_in_query": in_location == "query",
                                "hide_credentials": True
                            }
                        })
                    elif auth.type == "jwt":
                        jwt_config = {}
                        if auth.jwt:
                            if auth.jwt.issuer:
                                jwt_config["claims_to_verify"] = ["iss"]
                            if auth.jwt.audience:
                                jwt_config["claims_to_verify"] = jwt_config.get("claims_to_verify", []) + ["aud"]
                        route_plugins.append({
                            "name": "jwt",
                            "config": jwt_config if jwt_config else {}
                        })

                # Add rate limiting plugin if configured
                if route.rate_limit and route.rate_limit.enabled:
                    route_plugins.append({
                        "name": "rate-limiting",
                        "config": {
                            "second": route.rate_limit.requests_per_second,
                            "policy": "local",
                            "fault_tolerant": True,
                            "hide_client_headers": False
                        }
                    })

                # Add header manipulation plugins if configured
                if route.headers:
                    headers = route.headers
                    # Request header manipulation
                    if headers.request_add or headers.request_set or headers.request_remove:
                        req_config = {}
                        if headers.request_add:
                            req_config["add"] = {"headers": [f"{k}:{v}" for k, v in headers.request_add.items()]}
                        if headers.request_set:
                            req_config["replace"] = {"headers": [f"{k}:{v}" for k, v in headers.request_set.items()]}
                        if headers.request_remove:
                            req_config["remove"] = {"headers": headers.request_remove}
                        route_plugins.append({
                            "name": "request-transformer",
                            "config": req_config
                        })

                    # Response header manipulation
                    if headers.response_add or headers.response_set or headers.response_remove:
                        resp_config = {}
                        if headers.response_add:
                            resp_config["add"] = {"headers": [f"{k}:{v}" for k, v in headers.response_add.items()]}
                        if headers.response_set:
                            resp_config["replace"] = {"headers": [f"{k}:{v}" for k, v in headers.response_set.items()]}
                        if headers.response_remove:
                            resp_config["remove"] = {"headers": headers.response_remove}
                        route_plugins.append({
                            "name": "response-transformer",
                            "config": resp_config
                        })

                # Add CORS plugin if configured
                if route.cors and route.cors.enabled:
                    cors = route.cors
                    cors_config = {
                        "origins": cors.allowed_origins,
                        "methods": cors.allowed_methods,
                        "headers": cors.allowed_headers,
                        "credentials": cors.allow_credentials,
                        "max_age": cors.max_age
                    }
                    if cors.expose_headers:
                        cors_config["exposed_headers"] = cors.expose_headers
                    route_plugins.append({
                        "name": "cors",
                        "config": cors_config
                    })

                # Circuit Breaker: Kong does not have native circuit breaker support.
                # Third-party plugins exist (e.g., kong-circuit-breaker by Dream11),
                # but are not part of Kong's official plugin ecosystem.
                # For production use, consider using Envoy, APISIX, or Traefik which
                # provide native circuit breaker functionality.
                if route.circuit_breaker and route.circuit_breaker.enabled:
                    logger.warning(
                        f"Circuit breaker configured for route {service.name}/{route.path_prefix}, "
                        "but Kong does not have native circuit breaker support. "
                        "Consider using kong-circuit-breaker plugin or switch to a provider with native support."
                    )

                # Write all route plugins
                if route_plugins:
                    output.append("    plugins:")
                    for plugin in route_plugins:
                        output.append(f"    - name: {plugin['name']}")
                        if plugin['config']:
                            output.append("      config:")
                            for key, value in plugin['config'].items():
                                if isinstance(value, bool):
                                    output.append(f"        {key}: {str(value).lower()}")
                                elif isinstance(value, list):
                                    output.append(f"        {key}:")
                                    for item in value:
                                        # Quote wildcard * to avoid YAML alias interpretation
                                        if item == "*":
                                            output.append(f"        - '*'")
                                        else:
                                            output.append(f"        - {item}")
                                else:
                                    output.append(f"        {key}: {value}")

            # Add transformation plugins if enabled
            if service.transformation and service.transformation.enabled:
                output.append("  plugins:")

                # Handle defaults (legacy behavior - add as headers)
                if service.transformation.defaults:
                    output.append("  - name: request-transformer")
                    output.append("    config:")
                    output.append("      add:")
                    output.append("        headers:")
                    for key, value in service.transformation.defaults.items():
                        output.append(f"        - x-default-{key}: '{value}'")

                # Handle service-level header manipulation
                if service.transformation.headers:
                    headers = service.transformation.headers
                    # Request header manipulation
                    if headers.request_add or headers.request_set or headers.request_remove:
                        output.append("  - name: request-transformer")
                        output.append("    config:")
                        if headers.request_add:
                            output.append("      add:")
                            output.append("        headers:")
                            for key, value in headers.request_add.items():
                                output.append(f"        - {key}: '{value}'")
                        if headers.request_set:
                            output.append("      replace:")
                            output.append("        headers:")
                            for key, value in headers.request_set.items():
                                output.append(f"        - {key}: '{value}'")
                        if headers.request_remove:
                            output.append("      remove:")
                            output.append("        headers:")
                            for header_name in headers.request_remove:
                                output.append(f"        - {header_name}")

                    # Response header manipulation
                    if headers.response_add or headers.response_set or headers.response_remove:
                        output.append("  - name: response-transformer")
                        output.append("    config:")
                        if headers.response_add:
                            output.append("      add:")
                            output.append("        headers:")
                            for key, value in headers.response_add.items():
                                output.append(f"        - {key}: '{value}'")
                        if headers.response_set:
                            output.append("      replace:")
                            output.append("        headers:")
                            for key, value in headers.response_set.items():
                                output.append(f"        - {key}: '{value}'")
                        if headers.response_remove:
                            output.append("      remove:")
                            output.append("        headers:")
                            for header_name in headers.response_remove:
                                output.append(f"        - {header_name}")
            
            output.append("")

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

    def _generate_kong_upstream(self, service, output: list):
        """Generate Kong upstream configuration with health checks and load balancing.

        Creates Kong upstream configuration supporting:
        - Multiple targets with weights
        - Active health checks (HTTP/HTTPS probing)
        - Passive health checks (circuit breaker)
        - Load balancing algorithms

        Args:
            service: Service object with upstream configuration
            output: Output list to append YAML lines to

        Example:
            >>> output = []
            >>> provider._generate_kong_upstream(service, output)
            >>> "healthchecks:" in output
            True
        """
        upstream_name = f"{service.name}_upstream"
        output.append(f"- name: {upstream_name}")

        # Configure load balancing algorithm
        if service.upstream.load_balancer:
            algorithm = service.upstream.load_balancer.algorithm
            # Kong algorithms: round-robin, least-connections, consistent-hashing (ip_hash)
            kong_algo_map = {
                "round_robin": "round-robin",
                "least_conn": "least-connections",
                "ip_hash": "consistent-hashing",
                "weighted": "round-robin"  # Weighted uses round-robin with target weights
            }
            output.append(f"  algorithm: {kong_algo_map.get(algorithm, 'round-robin')}")

            # For consistent hashing (IP hash), configure hash_on
            if algorithm == "ip_hash":
                output.append("  hash_on: consumer")
                output.append("  hash_fallback: ip")
        else:
            output.append("  algorithm: round-robin")

        # Configure health checks
        if service.upstream.health_check:
            hc = service.upstream.health_check
            output.append("  healthchecks:")

            # Active health checks
            if hc.active and hc.active.enabled:
                active = hc.active
                output.append("    active:")
                output.append("      type: http")
                output.append(f"      http_path: {active.http_path}")
                output.append("      timeout: " + str(self._parse_kong_duration(active.timeout)))
                output.append("      concurrency: 10")
                output.append("      healthy:")
                output.append(f"        interval: {self._parse_kong_duration(active.interval)}")
                output.append(f"        successes: {active.healthy_threshold}")
                output.append(f"        http_statuses: {active.healthy_status_codes}")
                output.append("      unhealthy:")
                output.append(f"        interval: {self._parse_kong_duration(active.interval)}")
                output.append(f"        http_failures: {active.unhealthy_threshold}")
                output.append("        timeouts: 3")
                output.append("        http_statuses: [429, 500, 503]")

            # Passive health checks
            if hc.passive and hc.passive.enabled:
                passive = hc.passive
                output.append("    passive:")
                output.append("      type: http")
                output.append("      healthy:")
                output.append("        successes: 1")
                output.append("        http_statuses: [200, 201, 202, 204, 301, 302, 303, 304, 307, 308]")
                output.append("      unhealthy:")
                output.append(f"        http_failures: {passive.max_failures}")
                output.append(f"        http_statuses: {passive.unhealthy_status_codes}")
                output.append("        tcp_failures: 0")
                output.append("        timeouts: 0")

        # Configure targets
        output.append("  targets:")
        if service.upstream.targets:
            # Multiple targets mode
            for target in service.upstream.targets:
                output.append(f"  - target: {target.host}:{target.port}")
                output.append(f"    weight: {target.weight * 100}")  # Kong uses 0-1000 scale
        else:
            # Simple mode (single host/port)
            output.append(f"  - target: {service.upstream.host}:{service.upstream.port}")
            output.append("    weight: 100")

    def _parse_kong_duration(self, duration: str) -> int:
        """Parse duration string to seconds for Kong configuration.

        Args:
            duration: Duration string (e.g., "10s", "1m")

        Returns:
            int: Duration in seconds

        Example:
            >>> provider._parse_kong_duration("10s")
            10
        """
        duration = duration.strip()
        if duration.endswith('s'):
            return int(duration[:-1])
        elif duration.endswith('m'):
            return int(duration[:-1]) * 60
        elif duration.endswith('h'):
            return int(duration[:-1]) * 3600
        else:
            return int(duration)

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

        Deploys configuration via Kong Admin API (DB-less mode) or file-based.

        Deployment Methods:
            1. Admin API (recommended): POST config to Kong Admin API
            2. File-based: Write config to file for Kong to load

        Args:
            config: Configuration to deploy
            output_file: Path to write config file (default: kong.yaml)
            admin_url: Kong Admin API URL (default: http://localhost:8001)

        Returns:
            True if deployment successful

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

        Example:
            >>> provider = KongProvider()
            >>> config = Config.from_yaml("config.yaml")
            >>> # File-based deployment
            >>> provider.deploy(config, output_file="/etc/kong/kong.yaml")
            True
            >>> # Via Admin API
            >>> provider.deploy(config, admin_url="http://kong:8001")
            True
        """
        logger.info(f"Deploying Kong configuration to file: {output_file or 'kong.yaml'}")
        # Generate configuration
        generated_config = self.generate(config)

        # Determine output file
        if output_file is None:
            output_file = "kong.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"Kong configuration successfully written to {output_file}")
            print(f"✓ Kong configuration written to {output_file}")
        except IOError as e:
            logger.error(f"Failed to write Kong config file to {output_file}: {e}")
            print(f"✗ Failed to write config file: {e}")
            return False

        # Optionally deploy via Admin API
        if admin_url:
            admin_url = admin_url.rstrip('/')
            logger.debug(f"Checking Kong Admin API at {admin_url}")
            try:
                # Check if Kong Admin API is reachable
                response = requests.get(f"{admin_url}/status", timeout=5)

                if response.status_code == 200:
                    logger.info(f"Kong Admin API is reachable at {admin_url}")
                    print(f"✓ Kong Admin API is reachable at {admin_url}")

                    # Upload declarative config
                    with open(output_file, 'rb') as f:
                        config_data = f.read()

                    logger.debug(f"Uploading configuration to Kong Admin API")
                    upload_response = requests.post(
                        f"{admin_url}/config",
                        data=config_data,
                        headers={"Content-Type": "application/x-yaml"},
                        timeout=10
                    )

                    if upload_response.status_code in (200, 201):
                        logger.info(f"Configuration deployed successfully to Kong")
                        print(f"✓ Configuration deployed successfully to Kong")
                        return True
                    else:
                        logger.error(f"Failed to deploy config to Kong: {upload_response.status_code}")
                        print(f"✗ Failed to deploy config: {upload_response.status_code}")
                        print(f"  Response: {upload_response.text}")
                        return False
                else:
                    logger.warning(f"Kong Admin API returned status {response.status_code}")
                    print(f"⚠ Kong Admin API returned status {response.status_code}")

            except requests.RequestException as e:
                logger.warning(f"Could not reach Kong Admin API at {admin_url}: {e}")
                print(f"⚠ Could not reach Kong Admin API: {e}")
                print(f"  Config written to {output_file}, use: kong config db_import {output_file}")

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