import json
import os
from typing import Dict, Any, List, Optional
from abc import ABC, abstractmethod
import ast
from pathlib import Path

from ...models.context import (
    DiscoveryStageContext, 
    GenerationStageContext
)
from ...models.cloudformation import (
    Resource, 
    Output, 
    OutputValue, 
    OutputExport,
    CloudFormationTemplate,
    ResourceProperties
)
from ...utils.ast_utils import extract_call_arguments
from .infra_config import InfraConfig


class InfraFeature(ABC):
    _FEATURES: Dict[str, type['InfraFeature']] = {}

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        InfraFeature._FEATURES[cls.__name__] = cls


    def _get_name_value(self, config) -> str:
        """Get the name value from config, checking name field first, then logical_id"""
        if hasattr(config, 'name'):
            return getattr(config, 'name')
        elif hasattr(config, 'logical_id'):
            return getattr(config, 'logical_id')
        else:
            raise AttributeError(f"Config {type(config).__name__} must have either 'name' or 'logical_id' field")

    def discover(self, context: DiscoveryStageContext) -> List[Any]:
        """
        Discovery stage: Parse code file and extract config instances.
        Returns list of discovered config instances.
        Subclasses should override this to specify which config classes to discover.
        """
        return []

    @abstractmethod
    def generate(self, context: GenerationStageContext) -> Dict[str, CloudFormationTemplate]:
        """
        Generation stage: Generate CloudFormation from discovered configs.
        Uses configs from GenerationStageContext.
        Subclasses must implement this method.
        """
        pass

    def _discover_instances(self, context: DiscoveryStageContext, config_class: type) -> List[Any]:
        """Discover config instances using standard parsing logic"""
        return self._parse_pydantic_instantiation(context.file_path, config_class)

    def _parse_pydantic_instantiation(self, file_path: Path, config_class: type) -> List[Any]:
        """Standard parsing for Pydantic class instantiation"""
        instances = []
        try:
            with open(file_path, 'r') as f:
                tree = ast.parse(f.read(), filename=str(file_path))

            visitor = self._create_config_finder(config_class)
            visitor.visit(tree)
            instances = visitor.instances
        except (FileNotFoundError, SyntaxError, UnicodeDecodeError):
            pass

        return instances


    def map_properties(self, config):
        if hasattr(config, 'to_cloudformation_properties'):
            return config.to_cloudformation_properties()
        raise NotImplementedError("Subclasses must implement map_properties or use config with to_cloudformation_properties")

    def generate_outputs(self, config, resource_name):
        """Generate outputs using Pydantic models"""
        name_value = self._get_name_value(config)
        output_name = f"{resource_name}Arn"
        
        output_value = OutputValue(ref=resource_name)
        output_export = OutputExport(name=f"{name_value.replace('.', '-')}-Arn")
        output = Output(
            description=f"ARN of {name_value}",
            value=output_value,
            export=output_export
        )
        
        return {output_name: output}

    def _create_config_finder(self, config_class: type):
        """Create AST visitor for finding config instantiations"""
        class ConfigFinder(ast.NodeVisitor):
            def __init__(self, parent, target_config_class):
                self.parent = parent
                self.target_config_class = target_config_class
                self.instances = []

            def visit_Call(self, node: ast.Call) -> None:
                config_class_name = self.target_config_class.__name__

                if isinstance(node.func, ast.Name) and node.func.id == config_class_name:
                    self._handle_instantiation(node)

                elif (isinstance(node.func, ast.Attribute) and
                      isinstance(node.func.value, ast.Name) and
                      node.func.value.id == 'iic' and
                      node.func.attr == config_class_name):
                    self._handle_instantiation(node)

                self.generic_visit(node)

            def _handle_instantiation(self, node: ast.Call):
                args = extract_call_arguments(node)

                try:
                    config = self.target_config_class(**args)
                    self.instances.append(config)
                except Exception:
                    pass

        return ConfigFinder(self, config_class)

