"""
Routing Migrator for Hephaestus

Migrates routing systems from anvil-extras to official Anvil routing,
handling route pattern conversion, data loading, and navigation updates.
"""

from typing import Dict, List, Any, Optional
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
import yaml
import re

from ..dependencies.container import FileSystem, ProjectManager


class RoutingSystem(Enum):
    """Types of routing systems."""

    ANVIL_EXTRAS = "anvil_extras"
    OFFICIAL_ROUTING = "official_routing"
    HASH_ROUTING = "hash_routing"
    CUSTOM = "custom"


class MigrationComplexity(Enum):
    """Migration complexity levels."""

    SIMPLE = "simple"
    MODERATE = "moderate"
    COMPLEX = "complex"
    VERY_COMPLEX = "very_complex"


@dataclass
class RouteMapping:
    """Mapping between old and new route definitions."""

    old_path: str
    new_path: str
    old_handler: str
    new_handler: str
    parameters: List[str]
    query_params: List[str]
    data_loader: Optional[str]
    requires_form_update: bool
    breaking_changes: List[str]


@dataclass
class MigrationResult:
    """Result of routing migration."""

    success: bool
    routes_migrated: int
    forms_updated: int
    navigation_updated: int
    data_loaders_created: int
    breaking_changes: List[str]
    warnings: List[str]
    validation_errors: List[str]
    migration_notes: List[str]


class RoutingMigrator:
    """Migrates routing systems to official Anvil routing."""

    def __init__(self, file_system: FileSystem, project_manager: ProjectManager):
        self.file_system = file_system
        self.project_manager = project_manager

        # Route pattern mappings
        self._route_patterns = self._initialize_route_patterns()

        # Navigation pattern mappings
        self._navigation_patterns = self._initialize_navigation_patterns()

        # Data loading patterns
        self._data_loading_patterns = self._initialize_data_loading_patterns()

        # Validation rules
        self._validation_rules = self._initialize_validation_rules()

    def migrate_routing_system(
        self,
        project_path: Path,
        preserve_old_routes: bool = False,
    ) -> MigrationResult:
        """Migrate from anvil-extras to official Anvil routing."""
        try:
            # Detect current routing system
            current_system = self._detect_routing_system(project_path)

            if current_system != RoutingSystem.ANVIL_EXTRAS:
                return MigrationResult(
                    success=False,
                    routes_migrated=0,
                    forms_updated=0,
                    navigation_updated=0,
                    data_loaders_created=0,
                    breaking_changes=[],
                    warnings=[
                        f"Current routing system is {current_system.value}, not anvil-extras"
                    ],
                    validation_errors=[],
                    migration_notes=[],
                )

            # Analyze existing routes
            routes = self._analyze_routes(project_path)

            # Create route mappings
            route_mappings = self._create_route_mappings(routes)

            # Generate official routing configuration
            routing_config = self._generate_routing_config(route_mappings)

            # Update forms with route decorators
            forms_updated = self._update_forms_with_routes(project_path, route_mappings)

            # Create data loaders
            data_loaders_created = self._create_data_loaders(
                project_path, route_mappings
            )

            # Update navigation calls
            navigation_updated = self._update_navigation_calls(
                project_path, route_mappings
            )

            # Save routing configuration
            self._save_routing_config(project_path, routing_config)

            # Validate migration
            validation_errors = self._validate_migration(project_path, route_mappings)

            # Generate migration notes
            migration_notes = self._generate_migration_notes(
                route_mappings, preserve_old_routes
            )

            # Identify breaking changes
            breaking_changes = self._identify_breaking_changes(route_mappings)

            return MigrationResult(
                success=len(validation_errors) == 0,
                routes_migrated=len(route_mappings),
                forms_updated=forms_updated,
                navigation_updated=navigation_updated,
                data_loaders_created=data_loaders_created,
                breaking_changes=breaking_changes,
                warnings=[],
                validation_errors=validation_errors,
                migration_notes=migration_notes,
            )

        except Exception as e:
            return MigrationResult(
                success=False,
                routes_migrated=0,
                forms_updated=0,
                navigation_updated=0,
                data_loaders_created=0,
                breaking_changes=[],
                warnings=[],
                validation_errors=[str(e)],
                migration_notes=[],
            )

    def analyze_routing_migration(self, project_path: Path) -> Dict[str, Any]:
        """Analyze routing migration complexity and requirements."""
        try:
            current_system = self._detect_routing_system(project_path)
            routes = self._analyze_routes(project_path)

            complexity = self._assess_migration_complexity(routes)

            route_details = []
            for route in routes:
                route_details.append(
                    {
                        "path": route.get("path", ""),
                        "handler": route.get("handler", ""),
                        "parameters": route.get("parameters", []),
                        "complexity": self._assess_route_complexity(route),
                        "requires_data_loader": self._requires_data_loader(route),
                        "breaking_changes": self._identify_route_breaking_changes(
                            route
                        ),
                    }
                )

            return {
                "current_system": current_system.value,
                "total_routes": len(routes),
                "complexity": complexity.value,
                "routes": route_details,
                "estimated_effort": self._estimate_migration_effort(routes),
                "prerequisites": self._identify_prerequisites(),
                "post_migration_tasks": self._identify_post_migration_tasks(),
            }

        except Exception as e:
            return {
                "error": str(e),
            }

    def create_routing_migration_plan(self, project_path: Path) -> Dict[str, Any]:
        """Create detailed migration plan for routing system."""
        try:
            routes = self._analyze_routes(project_path)
            route_mappings = self._create_route_mappings(routes)

            migration_steps = []

            # Step 1: Install official routing
            migration_steps.append(
                {
                    "step": 1,
                    "phase": "preparation",
                    "action": "install_official_routing",
                    "description": "Install official Anvil routing dependency",
                    "estimated_time": 5,
                }
            )

            # Step 2: Create routing configuration
            migration_steps.append(
                {
                    "step": 2,
                    "phase": "setup",
                    "action": "create_routing_config",
                    "description": "Generate routing configuration file",
                    "estimated_time": 15,
                }
            )

            # Step 3: Update forms
            for i, mapping in enumerate(route_mappings):
                migration_steps.append(
                    {
                        "step": len(migration_steps) + 1,
                        "phase": "forms",
                        "action": "update_form",
                        "description": f"Add @routing.route decorator to {mapping.new_handler}",
                        "route_path": mapping.new_path,
                        "estimated_time": 10,
                    }
                )

            # Step 4: Create data loaders
            data_loader_count = sum(1 for m in route_mappings if m.data_loader)
            if data_loader_count > 0:
                migration_steps.append(
                    {
                        "step": len(migration_steps) + 1,
                        "phase": "data_loading",
                        "action": "create_data_loaders",
                        "description": f"Create {data_loader_count} data loader functions",
                        "estimated_time": data_loader_count * 15,
                    }
                )

            # Step 5: Update navigation
            migration_steps.append(
                {
                    "step": len(migration_steps) + 1,
                    "phase": "navigation",
                    "action": "update_navigation",
                    "description": "Update all routing.navigate calls",
                    "estimated_time": 20,
                }
            )

            # Step 6: Testing
            migration_steps.append(
                {
                    "step": len(migration_steps) + 1,
                    "phase": "validation",
                    "action": "test_routes",
                    "description": "Test all migrated routes",
                    "estimated_time": 30,
                }
            )

            return {
                "project_path": str(project_path),
                "total_steps": len(migration_steps),
                "migration_steps": migration_steps,
                "estimated_total_time": sum(
                    step["estimated_time"] for step in migration_steps
                ),
                "phases": [
                    "preparation",
                    "setup",
                    "forms",
                    "data_loading",
                    "navigation",
                    "validation",
                ],
            }

        except Exception as e:
            return {
                "error": str(e),
            }

    def _initialize_route_patterns(self) -> Dict[str, Any]:
        """Initialize route pattern mappings."""
        return {
            # Path parameter patterns
            "anvil_extras_param": r":(\w+)",
            "official_param": r"{{\1}}",
            # Query parameter patterns
            "query_params": r"\?.*",
            # Route decorators
            "anvil_extras_decorator": r"@routing\.route\(",
            "official_decorator": r"@routing.route\(",
        }

    def _initialize_navigation_patterns(self) -> Dict[str, Any]:
        """Initialize navigation pattern mappings."""
        return {
            "anvil_extras_navigate": r"routing\.set_url_hash\(",
            "official_navigate": r"routing.navigate\(",
            "anvil_extras_get_url": r"routing\.get_url_hash\(\)",
            "official_get_url": r"routing.get_url_dict\(\)",
        }

    def _initialize_data_loading_patterns(self) -> Dict[str, Any]:
        """Initialize data loading patterns."""
        return {
            "form_init_pattern": r"def __init__\(self, \*\*properties\):",
            "data_loader_pattern": r"@routing.route_data_loader",
        }

    def _initialize_validation_rules(self) -> Dict[str, Any]:
        """Initialize validation rules."""
        return {
            "required_import": "from anvil import routing",
            "route_decorator_required": True,
            "unique_route_paths": True,
        }

    def _detect_routing_system(self, project_path: Path) -> RoutingSystem:
        """Detect the current routing system in use."""
        # Check for anvil-extras routing
        try:
            dependencies_file = project_path / "anvil.yaml"
            if dependencies_file.exists():
                content = self.file_system.read_text(str(dependencies_file))
                if "anvil_extras" in content.lower():
                    return RoutingSystem.ANVIL_EXTRAS
                if "routing" in content.lower():
                    return RoutingSystem.OFFICIAL_ROUTING
        except:
            pass

        # Check code for routing patterns
        try:
            for py_file in project_path.rglob("*.py"):
                content = self.file_system.read_text(str(py_file))
                if "routing.set_url_hash" in content:
                    return RoutingSystem.ANVIL_EXTRAS
                if "routing.navigate" in content:
                    return RoutingSystem.OFFICIAL_ROUTING
        except:
            pass

        return RoutingSystem.CUSTOM

    def _analyze_routes(self, project_path: Path) -> List[Dict[str, Any]]:
        """Analyze existing routes in the project."""
        routes = []

        try:
            for py_file in project_path.rglob("*.py"):
                content = self.file_system.read_text(str(py_file))

                # Look for route definitions
                route_pattern = r"@routing\.route\(['\"](.+?)['\"]\)"
                matches = re.finditer(route_pattern, content)

                for match in matches:
                    route_path = match.group(1)

                    # Extract parameters
                    params = re.findall(r":(\w+)", route_path)

                    # Extract handler (next function after decorator)
                    handler_pattern = r"@routing\.route\(['\"].*?['\"])\s+def\s+(\w+)"
                    handler_match = re.search(handler_pattern, content[match.start() :])
                    handler = handler_match.group(1) if handler_match else "unknown"

                    routes.append(
                        {
                            "path": route_path,
                            "handler": handler,
                            "parameters": params,
                            "file": str(py_file),
                        }
                    )
        except:
            pass

        return routes

    def _create_route_mappings(
        self, routes: List[Dict[str, Any]]
    ) -> List[RouteMapping]:
        """Create mappings from old to new route format."""
        mappings = []

        for route in routes:
            old_path = route["path"]

            # Convert path parameters from :param to {param}
            new_path = re.sub(r":(\w+)", r"{\1}", old_path)

            # Extract parameters
            parameters = route.get("parameters", [])

            # Determine if data loader needed
            data_loader = None
            if parameters:
                data_loader = f"load_{route['handler']}_data"

            # Identify breaking changes
            breaking_changes = []
            if "?" in old_path:
                breaking_changes.append("Query parameters need manual migration")

            mappings.append(
                RouteMapping(
                    old_path=old_path,
                    new_path=new_path,
                    old_handler=route["handler"],
                    new_handler=route["handler"],
                    parameters=parameters,
                    query_params=[],
                    data_loader=data_loader,
                    requires_form_update=True,
                    breaking_changes=breaking_changes,
                )
            )

        return mappings

    def _generate_routing_config(
        self, route_mappings: List[RouteMapping]
    ) -> Dict[str, Any]:
        """Generate official routing configuration."""
        routes = []

        for mapping in route_mappings:
            routes.append(
                {
                    "path": mapping.new_path,
                    "form": mapping.new_handler,
                    "data_loader": mapping.data_loader,
                }
            )

        return {
            "version": "1.0",
            "routes": routes,
        }

    def _update_forms_with_routes(
        self, project_path: Path, route_mappings: List[RouteMapping]
    ) -> int:
        """Update forms with official routing decorators."""
        updated_count = 0

        for mapping in route_mappings:
            # Find form file
            form_files = list(project_path.rglob(f"*{mapping.new_handler}*.py"))

            for form_file in form_files:
                try:
                    content = self.file_system.read_text(str(form_file))

                    # Check if already has official routing
                    if f'@routing.route("{mapping.new_path}")' in content:
                        continue

                    # Update routing import
                    if "from anvil import routing" not in content:
                        content = "from anvil import routing\n" + content

                    # Add route decorator
                    class_pattern = f"class {mapping.new_handler}"
                    if class_pattern in content:
                        # Add decorator before class
                        decorator = f'@routing.route("{mapping.new_path}")\n'
                        content = content.replace(
                            class_pattern, decorator + class_pattern
                        )

                        self.file_system.write_text(str(form_file), content)
                        updated_count += 1
                except:
                    pass

        return updated_count

    def _create_data_loaders(
        self, project_path: Path, route_mappings: List[RouteMapping]
    ) -> int:
        """Create data loader functions for routes."""
        created_count = 0

        data_loaders_file = project_path / "client_code" / "route_data_loaders.py"

        if not data_loaders_file.parent.exists():
            return 0

        data_loaders_code = [
            '"""Route data loaders for official Anvil routing."""',
            "",
            "from anvil import routing",
            "import anvil.server",
            "",
        ]

        for mapping in route_mappings:
            if mapping.data_loader and mapping.parameters:
                params_str = ", ".join(mapping.parameters)

                data_loaders_code.extend(
                    [
                        f"@routing.route_data_loader",
                        f"def {mapping.data_loader}({params_str}):",
                        f'    """Load data for {mapping.new_path} route."""',
                        f"    # TODO: Implement data loading logic",
                        f"    return {{",
                    ]
                    + [f"        '{param}': {param}," for param in mapping.parameters]
                    + [
                        f"    }}",
                        "",
                    ]
                )

                created_count += 1

        if created_count > 0:
            try:
                self.file_system.write_text(
                    str(data_loaders_file), "\n".join(data_loaders_code)
                )
            except:
                pass

        return created_count

    def _update_navigation_calls(
        self, project_path: Path, route_mappings: List[RouteMapping]
    ) -> int:
        """Update navigation calls to use official routing."""
        updated_count = 0

        for py_file in project_path.rglob("*.py"):
            try:
                content = self.file_system.read_text(str(py_file))
                original_content = content

                # Replace set_url_hash with navigate
                content = re.sub(
                    r"routing\.set_url_hash\((['\"])(.*?)\1\)",
                    r'routing.navigate("\2")',
                    content,
                )

                # Replace get_url_hash with get_url_dict
                content = content.replace(
                    "routing.get_url_hash()", "routing.get_url_dict()"
                )

                if content != original_content:
                    self.file_system.write_text(str(py_file), content)
                    updated_count += 1
            except:
                pass

        return updated_count

    def _save_routing_config(self, project_path: Path, routing_config: Dict[str, Any]):
        """Save routing configuration."""
        config_file = project_path / "routing_config.yaml"

        try:
            yaml_content = yaml.dump(routing_config, default_flow_style=False)
            self.file_system.write_text(str(config_file), yaml_content)
        except:
            pass

    def _validate_migration(
        self, project_path: Path, route_mappings: List[RouteMapping]
    ) -> List[str]:
        """Validate migrated routing."""
        errors = []

        # Check for duplicate route paths
        paths = [m.new_path for m in route_mappings]
        if len(paths) != len(set(paths)):
            errors.append("Duplicate route paths detected")

        # Check for invalid path patterns
        for mapping in route_mappings:
            if not mapping.new_path.startswith("/"):
                errors.append(f"Route path must start with /: {mapping.new_path}")

        return errors

    def _generate_migration_notes(
        self, route_mappings: List[RouteMapping], preserve_old: bool
    ) -> List[str]:
        """Generate migration notes."""
        notes = []

        notes.append(f"Migrated {len(route_mappings)} routes to official Anvil routing")

        data_loader_count = sum(1 for m in route_mappings if m.data_loader)
        if data_loader_count > 0:
            notes.append(f"Created {data_loader_count} data loader functions")

        if preserve_old:
            notes.append("Old routing code preserved for reference")
        else:
            notes.append("Old routing code should be removed manually")

        notes.append("Update routing dependency in anvil.yaml")
        notes.append("Test all routes after migration")

        return notes

    def _identify_breaking_changes(
        self, route_mappings: List[RouteMapping]
    ) -> List[str]:
        """Identify breaking changes in migration."""
        breaking = []

        for mapping in route_mappings:
            if mapping.breaking_changes:
                breaking.extend(
                    [
                        f"{mapping.old_path}: {change}"
                        for change in mapping.breaking_changes
                    ]
                )

        return breaking

    def _assess_migration_complexity(
        self, routes: List[Dict[str, Any]]
    ) -> MigrationComplexity:
        """Assess overall migration complexity."""
        if len(routes) == 0:
            return MigrationComplexity.SIMPLE

        complexity_score = 0

        # Factor: Number of routes
        if len(routes) > 20:
            complexity_score += 3
        elif len(routes) > 10:
            complexity_score += 2
        elif len(routes) > 5:
            complexity_score += 1

        # Factor: Routes with parameters
        param_routes = sum(1 for r in routes if r.get("parameters"))
        if param_routes > 10:
            complexity_score += 2
        elif param_routes > 5:
            complexity_score += 1

        # Determine complexity
        if complexity_score == 0:
            return MigrationComplexity.SIMPLE
        elif complexity_score <= 2:
            return MigrationComplexity.MODERATE
        elif complexity_score <= 4:
            return MigrationComplexity.COMPLEX
        else:
            return MigrationComplexity.VERY_COMPLEX

    def _assess_route_complexity(self, route: Dict[str, Any]) -> str:
        """Assess complexity of a single route."""
        complexity = 0

        if route.get("parameters"):
            complexity += len(route["parameters"])

        if "?" in route.get("path", ""):
            complexity += 2

        if complexity == 0:
            return "simple"
        elif complexity <= 2:
            return "moderate"
        else:
            return "complex"

    def _requires_data_loader(self, route: Dict[str, Any]) -> bool:
        """Check if route requires a data loader."""
        return len(route.get("parameters", [])) > 0

    def _identify_route_breaking_changes(self, route: Dict[str, Any]) -> List[str]:
        """Identify breaking changes for a route."""
        changes = []

        if "?" in route.get("path", ""):
            changes.append("Query parameters need manual migration")

        if route.get("parameters"):
            changes.append("Path parameters changed from :param to {param}")

        return changes

    def _estimate_migration_effort(self, routes: List[Dict[str, Any]]) -> str:
        """Estimate effort required for migration."""
        base_time = 30  # minutes
        time_per_route = 10

        total_time = base_time + (len(routes) * time_per_route)

        if total_time < 60:
            return f"{total_time} minutes"
        else:
            hours = total_time / 60
            return f"{hours:.1f} hours"

    def _identify_prerequisites(self) -> List[str]:
        """Identify prerequisites for migration."""
        return [
            "Install official Anvil routing dependency",
            "Backup project before migration",
            "Review current route structure",
            "Ensure all forms are committed to version control",
        ]

    def _identify_post_migration_tasks(self) -> List[str]:
        """Identify post-migration tasks."""
        return [
            "Test all routes in the application",
            "Verify navigation between pages works",
            "Check data loading for parameterized routes",
            "Update any documentation referencing old routing",
            "Remove anvil-extras routing dependency",
            "Monitor for any routing errors in production",
        ]
