"""
Code Generation Engine for Import System

This module generates executable InfraDSL Python code from analyzed
cloud resources and dependency graphs.
"""

import logging
from typing import Dict, List, Any
from datetime import datetime
import re

from .models import (
    DependencyGraph,
    GeneratedCode,
    ImportConfig,
    CloudResource,
    ResourceType,
)

logger = logging.getLogger(__name__)


class CodeGenerator:
    """
    Generates InfraDSL Python code from cloud resources.

    This generator:
    1. Creates executable Python code from resource configurations
    2. Handles dependencies and proper resource ordering
    3. Optimizes code for readability and maintainability
    """

    def __init__(self):
        """Initialize the code generator"""
        self.resource_templates = self._initialize_resource_templates()
        self.provider_imports = self._initialize_provider_imports()

    async def generate_python_code(
        self, dependency_graph: DependencyGraph, config: ImportConfig
    ) -> GeneratedCode:
        """
        Generate InfraDSL Python code from dependency graph.

        Args:
            dependency_graph: Resource dependency graph
            config: Import configuration

        Returns:
            GeneratedCode with complete Python file content
        """
        logger.info(
            f"Generating Python code for {len(dependency_graph.resources)} resources"
        )

        # Get resources in dependency order
        creation_order = dependency_graph.get_creation_order()
        ordered_resources = [dependency_graph.resources[rid] for rid in creation_order]

        # Generate code sections
        imports = self._generate_imports(ordered_resources, config)
        header_comment = self._generate_header_comment(config)
        resource_definitions = await self._generate_resource_definitions(
            ordered_resources, config
        )

        # Combine all sections - simplified structure
        content_parts = [
            header_comment,
            "",
        ]

        if imports:
            content_parts.extend(
                [
                    "\n".join(imports),
                    "",
                ]
            )

        content_parts.append(resource_definitions)

        content = "\n".join(content_parts)

        # Determine output filename
        filename = config.output_file or self._generate_filename(config)

        generated_code = GeneratedCode(
            filename=filename,
            content=content,
            imports=imports,
            resources=[r.name for r in ordered_resources],
            dependencies={r.id: r.dependencies for r in ordered_resources},
            source_provider=config.provider,
            source_project=config.project,
        )

        logger.info(f"Generated {len(content)} characters of Python code")
        return generated_code

    def _generate_imports(
        self, resources: List[CloudResource], config: ImportConfig
    ) -> List[str]:
        """Generate import statements"""
        imports = set()

        # Remove provider-specific imports - not needed with string providers
        # Add resource-specific imports based on types used
        resource_types = {r.type for r in resources}

        if ResourceType.VIRTUAL_MACHINE in resource_types:
            imports.add("from infradsl.resources.compute import VirtualMachine")
            # Add enum imports for better code quality
            imports.add("from infradsl.resources.compute import InstanceSize")

        if ResourceType.DATABASE in resource_types:
            imports.add("from infradsl.resources.database import ManagedDatabase")

        if ResourceType.STORAGE in resource_types:
            imports.add("from infradsl.resources.storage import Volume")

        if ResourceType.NETWORK in resource_types:
            imports.add("from infradsl.resources.network import Network")

        if ResourceType.LOAD_BALANCER in resource_types:
            imports.add("from infradsl.resources.network import LoadBalancer")

        return sorted(list(imports))

    def _generate_header_comment(self, config: ImportConfig) -> str:
        """Generate header comment for the file"""
        return f'''"""
InfraDSL Infrastructure Code
Imported from {config.provider.upper()}

Generated on: {datetime.utcnow().isoformat()}
Provider: {config.provider}
Project: {config.project or 'N/A'}
Region: {config.region or 'N/A'}

This file was automatically generated by InfraDSL's "Codify My Cloud" import tool.
Review and modify the configuration as needed before applying.

To apply this infrastructure:
    infra apply {config.output_file or 'imported_infrastructure.py'}
"""'''

    async def _generate_resource_definitions(
        self, resources: List[CloudResource], config: ImportConfig
    ) -> str:
        """Generate resource definitions"""
        definitions = []

        # Remove provider setup - not needed anymore

        if config.group_by_type:
            # Group resources by type
            grouped_resources = self._group_resources_by_type(resources)

            for resource_type, type_resources in grouped_resources.items():
                if config.generate_comments:
                    definitions.append(
                        f"# {resource_type.value.replace('_', ' ').title()} Resources"
                    )

                for resource in type_resources:
                    definition = await self._generate_single_resource(resource, config)
                    definitions.append(definition)

                definitions.append("")  # Empty line between groups
        else:
            # Generate resources in dependency order
            for resource in resources:
                definition = await self._generate_single_resource(resource, config)
                definitions.append(definition)

        return "\n".join(definitions)

    async def _generate_single_resource(
        self, resource: CloudResource, config: ImportConfig
    ) -> str:
        """Generate code for a single resource"""
        # Get the appropriate template for this resource type
        template = self.resource_templates.get(
            resource.type, self.resource_templates[ResourceType.UNKNOWN]
        )

        # Prepare template variables
        variables = {
            "name": self._sanitize_variable_name(resource.name),
            "display_name": resource.name,
            "resource_id": resource.id,
            "provider": resource.provider,
            "region": resource.region,
            "zone": resource.zone,
            "project": resource.project,
            "configuration": resource.configuration,
            "tags": resource.tags,
            "metadata": resource.metadata,
            "generate_comments": config.generate_comments,
            # Add formatted configuration strings for templates
            "size_config": self._format_size_config(resource),
            "image_config": self._format_image_config(resource),
            "region_config": self._format_region_config(resource),
            "tags_config": self._format_tags_config(resource),
            "engine_config": self._format_engine_config(resource),
        }

        # Generate the resource definition
        definition = template.format(**variables)

        # Add comments if enabled
        if config.generate_comments:
            resource_type_str = resource.type.value if hasattr(resource.type, 'value') else str(resource.type)
            comment = (
                f"# Imported {resource_type_str}: {resource.name} (ID: {resource.id})"
            )
            definition = f"{comment}\n{definition}"

        return definition

    def _generate_provider_setup(self, config: ImportConfig) -> str:
        """Generate provider setup code - simplified to return empty since providers are handled by strings"""
        return ""

    def _generate_main_section(
        self, resources: List[CloudResource], config: ImportConfig
    ) -> str:
        """Generate main execution section - removed since nexus engine handles this automatically"""
        return ""

    def _initialize_resource_templates(self) -> Dict[ResourceType, str]:
        """Initialize code templates for different resource types"""
        return {
            ResourceType.VIRTUAL_MACHINE: self._get_vm_template(),
            ResourceType.DATABASE: self._get_database_template(),
            ResourceType.STORAGE: self._get_storage_template(),
            ResourceType.NETWORK: self._get_network_template(),
            ResourceType.LOAD_BALANCER: self._get_load_balancer_template(),
            ResourceType.SECURITY_GROUP: self._get_security_group_template(),
            ResourceType.UNKNOWN: self._get_generic_template(),
        }

    def _get_vm_template(self) -> str:
        """Template for virtual machine resources"""
        return """{name} = (
    VirtualMachine("{display_name}")
    .with_provider("{provider}")
    .project("{project}")
    .environment("imported"){size_config}{image_config}{region_config}{tags_config}
)"""

    def _get_database_template(self) -> str:
        """Template for database resources"""
        return """{name} = (
    ManagedDatabase("{display_name}")
    .with_provider("{provider}")
    .project("{project}")
    .environment("imported"){engine_config}{size_config}{region_config}{tags_config}
)"""

    def _get_storage_template(self) -> str:
        """Template for storage resources"""
        return """{name} = (
    Volume("{display_name}")
    .with_provider("{provider}")
    .project("{project}")
    .environment("imported"){size_config}{region_config}{tags_config}
)"""

    def _get_network_template(self) -> str:
        """Template for network resources"""
        return """{name} = (
    Network("{display_name}")
    .with_provider("{provider}")
    .project("{project}")
    .environment("imported"){region_config}{tags_config}
)"""

    def _get_load_balancer_template(self) -> str:
        """Template for load balancer resources"""
        return """{name} = (
    LoadBalancer("{display_name}")
    .with_provider("{provider}")
    .project("{project}")
    .environment("imported"){region_config}{tags_config}
)"""

    def _get_security_group_template(self) -> str:
        """Template for security group resources"""
        return """{name} = (
    SecurityGroup("{display_name}")
    .with_provider("{provider}")
    .project("{project}")
    .environment("imported"){region_config}{tags_config}
)"""

    def _get_generic_template(self) -> str:
        """Template for unknown/generic resources"""
        return """# TODO: Unknown resource type - manual configuration required
# {name} = (
#     Resource("{display_name}")
#     .with_provider("{provider}")
#     .project("{project}")
#     .environment("imported"){tags_config}
# )"""

    def _initialize_provider_imports(self) -> Dict[str, str]:
        """Initialize provider-specific import statements"""
        return {
            "gcp": "from infradsl.providers.gcp import GCP",
            "aws": "from infradsl.providers.aws import AWS",
            "digitalocean": "from infradsl.providers.digitalocean import DigitalOcean",
            "azure": "from infradsl.providers.azure import Azure",
        }

    def _group_resources_by_type(
        self, resources: List[CloudResource]
    ) -> Dict[ResourceType, List[CloudResource]]:
        """Group resources by their type"""
        grouped = {}

        for resource in resources:
            if resource.type not in grouped:
                grouped[resource.type] = []
            grouped[resource.type].append(resource)

        return grouped

    def _sanitize_variable_name(self, name: str) -> str:
        """Sanitize a name to be a valid Python variable name"""
        # Replace invalid characters with underscores
        sanitized = re.sub(r"[^a-zA-Z0-9_]", "_", name)

        # Ensure it starts with a letter or underscore
        if sanitized and sanitized[0].isdigit():
            sanitized = f"resource_{sanitized}"

        # Ensure it's not empty
        if not sanitized:
            sanitized = "unnamed_resource"

        # Ensure it's not a Python keyword
        python_keywords = {
            "and",
            "as",
            "assert",
            "break",
            "class",
            "continue",
            "def",
            "del",
            "elif",
            "else",
            "except",
            "exec",
            "finally",
            "for",
            "from",
            "global",
            "if",
            "import",
            "in",
            "is",
            "lambda",
            "not",
            "or",
            "pass",
            "print",
            "raise",
            "return",
            "try",
            "while",
            "with",
            "yield",
        }

        if sanitized.lower() in python_keywords:
            sanitized = f"{sanitized}_resource"

        return sanitized

    def _sanitize_tag_key(self, key: str) -> str:
        """Sanitize tag key to be a valid Python identifier"""
        # Replace dots with underscores
        sanitized = key.replace(".", "_")
        # Replace other invalid characters with underscores
        sanitized = re.sub(r"[^a-zA-Z0-9_]", "_", sanitized)
        # Ensure it starts with a letter or underscore
        if sanitized and sanitized[0].isdigit():
            sanitized = f"tag_{sanitized}"
        # Ensure it's not empty
        if not sanitized:
            sanitized = "tag_key"
        return sanitized

    def _format_size_config(self, resource: CloudResource) -> str:
        """Format size configuration for templates"""
        size = (
            resource.configuration.get("size")
            or resource.configuration.get("machine_type")
            or resource.configuration.get("instance_type")
        )
        if size:
            # Map common sizes to InfraDSL enums
            size_enum_mapping = {
                "s-1vcpu-2gb": "InstanceSize.SMALL",
                "s-2vcpu-4gb": "InstanceSize.MEDIUM",
                "s-4vcpu-8gb": "InstanceSize.LARGE",
                "s-8vcpu-16gb": "InstanceSize.XLARGE",
                "s-1vcpu-1gb": "InstanceSize.MICRO",
                "s-1vcpu-512mb-10gb": "InstanceSize.NANO",
                # GCP mappings
                "e2-micro": "InstanceSize.MICRO",
                "e2-small": "InstanceSize.SMALL",
                "e2-medium": "InstanceSize.MEDIUM",
                "e2-standard-2": "InstanceSize.LARGE",
                # AWS mappings
                "t3.micro": "InstanceSize.MICRO",
                "t3.small": "InstanceSize.SMALL",
                "t3.medium": "InstanceSize.MEDIUM",
                "t3.large": "InstanceSize.LARGE",
            }

            enum_size = size_enum_mapping.get(size)
            if enum_size:
                return f"\n    .size({enum_size})"
            else:
                return f'\n    .size("{size}")'
        return ""

    def _format_image_config(self, resource: CloudResource) -> str:
        """Format image configuration for templates - Rails-like style"""
        image = resource.configuration.get("image")
        if image:
            # Use intelligent image mapping for Rails-like methods
            rails_method = self._map_image_to_rails_method(image)
            if rails_method:
                return f"\n    {rails_method}"
            else:
                return f'\n    .image("{image}")'
        return ""

    def _map_image_to_rails_method(self, image_name: str) -> str:
        """Map image names to Rails-like method calls (.ubuntu(), .debian(), etc.)"""
        if not image_name:
            return None
            
        # Normalize image name for matching
        normalized = image_name.lower().replace("-", "_").replace(" ", "_")
        
        # Ubuntu mappings
        if "ubuntu" in normalized:
            if any(version in normalized for version in ["22", "2204", "22.04", "jammy"]):
                return '.ubuntu("22_04")'
            elif any(version in normalized for version in ["20", "2004", "20.04", "focal"]):
                return '.ubuntu("20_04")'
            elif any(version in normalized for version in ["18", "1804", "18.04", "bionic"]):
                return '.ubuntu("18_04")'
            elif any(version in normalized for version in ["24", "2404", "24.04", "noble"]):
                return '.ubuntu("24_04")'
            else:
                # Latest Ubuntu if no specific version detected
                return ".ubuntu()"
        
        # Debian mappings
        elif "debian" in normalized:
            if any(version in normalized for version in ["12", "bookworm"]):
                return '.debian("12")'
            elif any(version in normalized for version in ["11", "bullseye"]):
                return '.debian("11")'
            elif any(version in normalized for version in ["10", "buster"]):
                return '.debian("10")'
            elif any(version in normalized for version in ["9", "stretch"]):
                return '.debian("9")'
            else:
                # Latest Debian if no specific version detected
                return ".debian()"
        
        # CentOS mappings
        elif "centos" in normalized:
            if "7" in normalized:
                return '.centos("7")'
            elif "8" in normalized:
                return '.centos("8")'
            elif "9" in normalized:
                return '.centos("9")'
            else:
                return ".centos()"
        
        # Rocky Linux mappings
        elif "rocky" in normalized:
            if "8" in normalized:
                return '.rocky("8")'
            elif "9" in normalized:
                return '.rocky("9")'
            else:
                return ".rocky()"
        
        # AlmaLinux mappings
        elif "alma" in normalized:
            if "8" in normalized:
                return '.almalinux("8")'
            elif "9" in normalized:
                return '.almalinux("9")'
            else:
                return ".almalinux()"
        
        # Fedora mappings
        elif "fedora" in normalized:
            if "36" in normalized:
                return '.fedora("36")'
            elif "37" in normalized:
                return '.fedora("37")'
            elif "38" in normalized:
                return '.fedora("38")'
            elif "39" in normalized:
                return '.fedora("39")'
            elif "40" in normalized:
                return '.fedora("40")'
            else:
                return ".fedora()"
        
        # Amazon Linux mappings
        elif any(aws_indicator in normalized for aws_indicator in ["amazon", "amzn", "ami"]):
            if "2" in normalized:
                return '.amazon_linux("2")'
            elif "2023" in normalized:
                return '.amazon_linux("2023")'
            else:
                return ".amazon_linux()"
        
        # RHEL mappings
        elif any(rhel_indicator in normalized for rhel_indicator in ["rhel", "red_hat", "redhat"]):
            if "7" in normalized:
                return '.rhel("7")'
            elif "8" in normalized:
                return '.rhel("8")'
            elif "9" in normalized:
                return '.rhel("9")'
            else:
                return ".rhel()"
        
        # SUSE mappings
        elif "suse" in normalized or "sles" in normalized:
            if "15" in normalized:
                return '.suse("15")'
            elif "12" in normalized:
                return '.suse("12")'
            else:
                return ".suse()"
        
        # OpenSUSE mappings
        elif "opensuse" in normalized:
            return ".opensuse()"
        
        # Alpine mappings
        elif "alpine" in normalized:
            # Extract version number from alpine images
            import re
            version_match = re.search(r'(\d+)\.(\d+)', normalized)
            if version_match:
                major, minor = version_match.groups()
                return f'.alpine("{major}_{minor}")'
            else:
                return ".alpine()"
        
        # Windows mappings
        elif "windows" in normalized:
            if "2019" in normalized:
                return '.windows("2019")'
            elif "2022" in normalized:
                return '.windows("2022")'
            elif "2016" in normalized:
                return '.windows("2016")'
            else:
                return ".windows()"
        
        # FreeBSD mappings
        elif "freebsd" in normalized:
            if "13" in normalized:
                return '.freebsd("13")'
            elif "12" in normalized:
                return '.freebsd("12")'
            else:
                return ".freebsd()"
        
        # No mapping found - return None to fall back to .image()
        return None

    def _format_region_config(self, resource: CloudResource) -> str:
        """Format region configuration for templates"""
        region = resource.region or resource.configuration.get("region")
        if region:
            return f'\n    .region("{region}")'
        return ""

    def _format_tags_config(self, resource: CloudResource) -> str:
        """Format tags configuration for templates"""
        if resource.tags:
            # Format tags as dictionary argument to labels method
            tag_pairs = [f'        "{k}": "{v}"' for k, v in resource.tags.items()]
            if len(tag_pairs) > 3:  # Use multi-line format for more than 3 tags
                tags_str = ",\n".join(tag_pairs)
                return f"\n    .labels({{\n{tags_str}\n    }})"
            else:
                # Single line for few tags
                tag_pairs_inline = [f'"{k}": "{v}"' for k, v in resource.tags.items()]
                tags_str = ", ".join(tag_pairs_inline)
                return f"\n    .labels({{{tags_str}}})"
        return ""

    def _format_engine_config(self, resource: CloudResource) -> str:
        """Format engine configuration for databases"""
        engine = resource.configuration.get("engine")
        if engine:
            return f'\n    .engine("{engine}")'
        return ""

    def _generate_filename(self, config: ImportConfig) -> str:
        """Generate a filename for the output"""
        base_name = "imported_infrastructure"

        if config.project:
            base_name = f"{config.project}_infrastructure"

        if config.provider:
            base_name = f"{config.provider}_{base_name}"

        # Sanitize filename
        base_name = re.sub(r"[^a-zA-Z0-9_-]", "_", base_name)

        return f"{base_name}.py"

    def format_configuration_value(self, value: Any) -> str:
        """Format a configuration value for Python code"""
        if isinstance(value, str):
            return f'"{value}"'
        elif isinstance(value, bool):
            return str(value)
        elif isinstance(value, (int, float)):
            return str(value)
        elif isinstance(value, list):
            formatted_items = [self.format_configuration_value(item) for item in value]
            return f"[{', '.join(formatted_items)}]"
        elif isinstance(value, dict):
            formatted_items = [
                f'"{k}": {self.format_configuration_value(v)}' for k, v in value.items()
            ]
            return f"{{{', '.join(formatted_items)}}}"
        else:
            return f'"{str(value)}"'

    def optimize_generated_code(self, code: GeneratedCode) -> GeneratedCode:
        """
        Optimize generated code for readability and maintainability.

        Args:
            code: Generated code to optimize

        Returns:
            Optimized generated code
        """
        optimized_content = code.content

        # Remove excessive blank lines
        optimized_content = re.sub(r"\n\s*\n\s*\n", "\n\n", optimized_content)

        # Ensure consistent indentation
        lines = optimized_content.split("\n")
        optimized_lines = []

        for line in lines:
            # Standardize indentation to 4 spaces
            if line.strip():
                indent_level = len(line) - len(line.lstrip())
                if indent_level > 0:
                    # Convert tabs to spaces and normalize
                    normalized_indent = "    " * (indent_level // 4)
                    optimized_lines.append(normalized_indent + line.lstrip())
                else:
                    optimized_lines.append(line)
            else:
                optimized_lines.append("")

        optimized_content = "\n".join(optimized_lines)

        # Create optimized code object
        optimized_code = GeneratedCode(
            filename=code.filename,
            content=optimized_content,
            imports=code.imports,
            resources=code.resources,
            dependencies=code.dependencies,
            generated_at=code.generated_at,
            generator_version=code.generator_version,
            source_provider=code.source_provider,
            source_project=code.source_project,
        )

        return optimized_code
