"""Component dependency analyzer for Hephaestus."""

from __future__ import annotations

import re
from typing import Any

from dataclasses import dataclass, field


@dataclass
class ComponentDependency:
    """Represents a dependency between components."""

    source_component: str
    target_component: str
    dependency_type: str  # "event", "data_binding", "layout"
    description: str = ""
    strength: float = 1.0  # 0.0 to 1.0, how strong the dependency is

    def __str__(self) -> str:
        return f"{self.source_component} → {self.target_component} ({self.dependency_type})"


@dataclass
class ComponentInventory:
    """Inventory of components in a form."""

    form_path: str
    components: list[dict[str, Any]] = field(default_factory=list)
    dependencies: list[ComponentDependency] = field(default_factory=list)
    statistics: dict[str, Any] = field(default_factory=dict)
    analysis_timestamp: str = ""

    def __post_init__(self) -> None:
        if not self.analysis_timestamp:
            from datetime import datetime

            self.analysis_timestamp = datetime.now().isoformat()
        self._calculate_statistics()

    def get_components_by_type(self, component_type: str) -> list[dict[str, Any]]:
        """Get all components of a specific type."""
        return [comp for comp in self.components if comp.get("type") == component_type]

    def get_component_dependencies(
        self, component_name: str
    ) -> list[ComponentDependency]:
        """Get dependencies for a specific component."""
        return [
            dep for dep in self.dependencies if dep.target_component == component_name
        ]

    def get_component_dependents(
        self, component_name: str
    ) -> list[ComponentDependency]:
        """Get components that depend on a specific component."""
        return [
            dep for dep in self.dependencies if dep.source_component == component_name
        ]

    def get_critical_components(self) -> list[str]:
        """Get components that are critical (have dependents)."""
        critical = set()
        for dep in self.dependencies:
            critical.add(dep.target_component)
        return list(critical)

    def _calculate_statistics(self) -> None:
        """Calculate inventory statistics."""
        self.statistics = {
            "total_components": len(self.components),
            "component_types": {},
            "components_with_events": 0,
            "components_with_data_bindings": 0,
            "total_dependencies": len(self.dependencies),
            "dependency_types": {},
        }

        for component in self.components:
            # Count component types
            comp_type = component.get("type", "Unknown")
            self.statistics["component_types"][comp_type] = (
                self.statistics["component_types"].get(comp_type, 0) + 1
            )

            # Count components with bindings
            if component.get("event_bindings"):
                self.statistics["components_with_events"] += 1
            if component.get("data_bindings"):
                self.statistics["components_with_data_bindings"] += 1

        # Count dependency types
        for dep in self.dependencies:
            dep_type = dep.dependency_type
            self.statistics["dependency_types"][dep_type] = (
                self.statistics["dependency_types"].get(dep_type, 0) + 1
            )


class ComponentDependencyAnalyzer:
    """Analyze dependencies between components in Anvil forms."""

    def __init__(self):
        self._event_patterns = [
            r"self\.(\w+)\.",  # self.component_name
            r"get_by_id\(['\"](\w+)['\"]",  # get_by_id('component_name')
            r"\.(\w+)_click",  # component_name_click
            r"\.(\w+)_change",  # component_name_change
        ]

        self._data_binding_patterns = [
            r"self\.(\w+)\.",  # self.component_name
            r"get_by_id\(['\"](\w+)['\"]",  # get_by_id('component_name')
            r"item\['(\w+)'\]",  # item['component_name']
            r"row\['(\w+)'\]",  # row['component_name']
        ]

    def analyze_form_dependencies(
        self, form_config: dict[str, Any], form_path: str
    ) -> ComponentInventory:
        """Analyze all dependencies in a form."""
        components = form_config.get("components", [])
        dependencies = []

        # Analyze each component for outgoing dependencies
        for component in components:
            component_deps = self._analyze_component_dependencies(component, components)
            dependencies.extend(component_deps)

        return ComponentInventory(
            form_path=form_path,
            components=components,
            dependencies=dependencies,
        )

    def get_component_dependencies(
        self, form_config: dict[str, Any], component_name: str
    ) -> list[ComponentDependency]:
        """Get all dependencies for a specific component."""
        components = form_config.get("components", [])
        target_component = self._find_component(components, component_name)

        if not target_component:
            return []

        return self._analyze_component_dependencies(target_component, components)

    def get_component_dependents(
        self, form_config: dict[str, Any], component_name: str
    ) -> list[ComponentDependency]:
        """Get all components that depend on a specific component."""
        components = form_config.get("components", [])
        dependents = []

        for component in components:
            if component.get("name") == component_name:
                continue

            component_deps = self._analyze_component_dependencies(component, components)
            for dep in component_deps:
                if dep.target_component == component_name:
                    dependents.append(dep)

        return dependents

    def analyze_dependency_impact(
        self, form_config: dict[str, Any], component_name: str
    ) -> dict[str, Any]:
        """Analyze the impact of removing or modifying a component."""
        dependents = self.get_component_dependents(form_config, component_name)

        impact = {
            "component_name": component_name,
            "direct_dependents": len(dependents),
            "indirect_dependents": 0,
            "critical_paths": [],
            "risk_level": "low",
            "affected_functionality": [],
        }

        # Categorize dependents by type
        event_dependents = [d for d in dependents if d.dependency_type == "event"]
        data_dependents = [d for d in dependents if d.dependency_type == "data_binding"]
        layout_dependents = [d for d in dependents if d.dependency_type == "layout"]

        # Analyze affected functionality
        if event_dependents:
            impact["affected_functionality"].append("Event handling")
        if data_dependents:
            impact["affected_functionality"].append("Data binding")
        if layout_dependents:
            impact["affected_functionality"].append("Layout constraints")

        # Calculate indirect dependents
        for dependent in dependents:
            indirect_deps = self.get_component_dependents(
                form_config, dependent.source_component
            )
            impact["indirect_dependents"] += len(indirect_deps)

        # Determine risk level
        total_affected = impact["direct_dependents"] + impact["indirect_dependents"]
        if total_affected == 0:
            impact["risk_level"] = "none"
        elif total_affected <= 2:
            impact["risk_level"] = "low"
        elif total_affected <= 5:
            impact["risk_level"] = "medium"
        else:
            impact["risk_level"] = "high"

        # Identify critical paths
        critical_deps = [d for d in dependents if d.strength > 0.7]
        impact["critical_paths"] = [
            f"{d.source_component} → {d.target_component}" for d in critical_deps
        ]

        return impact

    def suggest_dependency_optimizations(
        self, form_config: dict[str, Any]
    ) -> list[dict[str, Any]]:
        """Suggest optimizations for component dependencies."""
        components = form_config.get("components", [])
        suggestions = []

        # Analyze circular dependencies
        circular_deps = self._detect_circular_dependencies(components)
        if circular_deps:
            suggestions.append(
                {
                    "type": "circular_dependency",
                    "severity": "high",
                    "description": "Circular dependencies detected",
                    "components": circular_deps,
                    "recommendation": "Refactor to break circular references",
                }
            )

        # Analyze high coupling
        high_coupling = self._detect_high_coupling(components)
        if high_coupling:
            suggestions.append(
                {
                    "type": "high_coupling",
                    "severity": "medium",
                    "description": "Components with high dependency count",
                    "components": high_coupling,
                    "recommendation": "Consider using mediator pattern or events",
                }
            )

        # Analyze unused components
        unused = self._detect_unused_components(components)
        if unused:
            suggestions.append(
                {
                    "type": "unused_components",
                    "severity": "low",
                    "description": "Components with no dependents",
                    "components": unused,
                    "recommendation": "Consider removing if truly unused",
                }
            )

        return suggestions

    def _analyze_component_dependencies(
        self, component: dict[str, Any], all_components: list[dict[str, Any]]
    ) -> list[ComponentDependency]:
        """Analyze dependencies for a single component."""
        dependencies = []
        component_name = component.get("name", "")

        # Analyze event bindings
        event_bindings = component.get("event_bindings", {})
        for event, handler in event_bindings.items():
            event_deps = self._extract_dependencies_from_code(
                handler, self._event_patterns
            )
            for dep_name in event_deps:
                if dep_name != component_name and self._component_exists(
                    dep_name, all_components
                ):
                    dependencies.append(
                        ComponentDependency(
                            source_component=component_name,
                            target_component=dep_name,
                            dependency_type="event",
                            description=f"Event handler for {event}",
                            strength=0.8,
                        )
                    )

        # Analyze data bindings
        data_bindings = component.get("data_bindings", [])
        for binding in data_bindings:
            code = binding.get("code", "")
            data_deps = self._extract_dependencies_from_code(
                code, self._data_binding_patterns
            )
            for dep_name in data_deps:
                if dep_name != component_name and self._component_exists(
                    dep_name, all_components
                ):
                    dependencies.append(
                        ComponentDependency(
                            source_component=component_name,
                            target_component=dep_name,
                            dependency_type="data_binding",
                            description=f"Data binding: {binding.get('property', 'unknown')}",
                            strength=0.9,
                        )
                    )

        # Analyze layout dependencies
        layout_props = component.get("layout_properties", {})
        layout_deps = self._analyze_layout_dependencies(
            component_name, layout_props, all_components
        )
        dependencies.extend(layout_deps)

        return dependencies

    def _extract_dependencies_from_code(
        self, code: str, patterns: list[str]
    ) -> list[str]:
        """Extract component names from code using regex patterns."""
        dependencies = set()

        for pattern in patterns:
            matches = re.findall(pattern, code)
            dependencies.update(matches)

        return list(dependencies)

    def _analyze_layout_dependencies(
        self,
        component_name: str,
        layout_props: dict[str, Any],
        all_components: list[dict[str, Any]],
    ) -> list[ComponentDependency]:
        """Analyze layout-related dependencies."""
        dependencies = []

        # Check for positioning relative to other components
        for prop_name, prop_value in layout_props.items():
            if isinstance(prop_value, str):
                layout_deps = self._extract_dependencies_from_code(
                    prop_value, self._event_patterns
                )
                for dep_name in layout_deps:
                    if dep_name != component_name and self._component_exists(
                        dep_name, all_components
                    ):
                        dependencies.append(
                            ComponentDependency(
                                source_component=component_name,
                                target_component=dep_name,
                                dependency_type="layout",
                                description=f"Layout property: {prop_name}",
                                strength=0.3,
                            )
                        )

        return dependencies

    def _component_exists(
        self, component_name: str, components: list[dict[str, Any]]
    ) -> bool:
        """Check if a component exists in component list."""
        return any(comp.get("name") == component_name for comp in components)

    def _find_component(
        self, components: list[dict[str, Any]], component_name: str
    ) -> dict[str, Any] | None:
        """Find a component by name."""
        for component in components:
            if component.get("name") == component_name:
                return component
        return None

    def _detect_circular_dependencies(
        self, components: list[dict[str, Any]]
    ) -> list[list[str]]:
        """Detect circular dependencies between components."""
        # Build dependency graph
        graph = {}
        for component in components:
            component_name = component.get("name", "")
            deps = self._analyze_component_dependencies(component, components)
            graph[component_name] = [dep.target_component for dep in deps]

        # Detect cycles using DFS
        cycles = []
        visited = set()
        rec_stack = set()

        def dfs(node: str, path: list[str]) -> None:
            if node in rec_stack:
                # Found a cycle
                cycle_start = path.index(node)
                cycle = path[cycle_start:] + [node]
                cycles.append(cycle)
                return

            if node in visited:
                return

            visited.add(node)
            rec_stack.add(node)

            for neighbor in graph.get(node, []):
                dfs(neighbor, path + [node])

            rec_stack.remove(node)

        for component_name in graph:
            if component_name not in visited:
                dfs(component_name, [])

        return cycles

    def _detect_high_coupling(
        self, components: list[dict[str, Any]]
    ) -> list[dict[str, Any]]:
        """Detect components with high coupling (many dependencies)."""
        high_coupling = []
        dependency_counts = {}

        # Count outgoing dependencies
        for component in components:
            component_name = component.get("name", "")
            deps = self._analyze_component_dependencies(component, components)
            dependency_counts[component_name] = len(deps)

        # Count incoming dependencies
        incoming_counts = {}
        for component in components:
            component_name = component.get("name", "")
            dependents = self.get_component_dependents(
                {"components": components}, component_name
            )
            incoming_counts[component_name] = len(dependents)

        # Identify high coupling (more than 5 total dependencies)
        for component in components:
            component_name = component.get("name", "")
            total_deps = dependency_counts.get(component_name, 0) + incoming_counts.get(
                component_name, 0
            )

            if total_deps > 5:
                high_coupling.append(
                    {
                        "name": component_name,
                        "outgoing": dependency_counts.get(component_name, 0),
                        "incoming": incoming_counts.get(component_name, 0),
                        "total": total_deps,
                    }
                )

        return high_coupling

    def _detect_unused_components(self, components: list[dict[str, Any]]) -> list[str]:
        """Detect components that have no dependents."""
        unused = []

        for component in components:
            component_name = component.get("name", "")
            dependents = self.get_component_dependents(
                {"components": components}, component_name
            )

            # Check if component has no dependents and no event bindings
            has_events = bool(component.get("event_bindings"))
            if not dependents and not has_events:
                unused.append(component_name)

        return unused
