"""Engineering Manager for handling technical decisions and implementation strategy."""
import json
from datetime import datetime
from typing import Dict, List, Optional, Any, Set
from dataclasses import dataclass, asdict
import logging

logger = logging.getLogger(__name__)

@dataclass
class TechStack:
    """Represents the technology stack for a project or component."""
    languages: List[str]
    frameworks: List[str]
    databases: List[str]
    infrastructure: List[str]
    tools: List[str]
    version_requirements: Dict[str, str]

@dataclass
class ArchitectureComponent:
    """Represents a component in the system architecture."""
    id: str
    name: str
    description: str
    tech_stack: TechStack
    dependencies: List[str]
    interfaces: List[str]
    status: str
    created_at: str
    updated_at: str

@dataclass
class TechnicalTask:
    """Represents a technical implementation task."""
    id: str
    title: str
    description: str
    component_id: str
    requirement_ids: List[str]
    assigned_to: Optional[str]
    estimated_hours: float
    priority: int
    status: str
    dependencies: List[str]
    created_at: str
    updated_at: str
    completed_at: Optional[str] = None

class EngineeringManager:
    """Manages technical decisions and implementation strategy."""
    
    def __init__(self, context_manager, intelligence_manager, product_manager):
        """Initialize the Engineering Manager."""
        self.context_manager = context_manager
        self.intelligence_manager = intelligence_manager
        self.product_manager = product_manager
        self.tech_stacks: Dict[str, TechStack] = {}
        self.components: Dict[str, ArchitectureComponent] = {}
        self.tasks: Dict[str, TechnicalTask] = {}
        self.load_engineering_data()

    def load_engineering_data(self):
        """Load engineering data from context database."""
        context = self.context_manager.get_project_context()
        eng_data = context.get('engineering_data', {})
        
        # Load tech stacks
        for stack_id, stack_dict in eng_data.get('tech_stacks', {}).items():
            self.tech_stacks[stack_id] = TechStack(**stack_dict)
            
        # Load components
        for comp_dict in eng_data.get('components', []):
            component = ArchitectureComponent(**comp_dict)
            self.components[component.id] = component
            
        # Load tasks
        for task_dict in eng_data.get('tasks', []):
            task = TechnicalTask(**task_dict)
            self.tasks[task.id] = task

    def save_engineering_data(self):
        """Save engineering data to context database."""
        eng_data = {
            'tech_stacks': {
                stack_id: asdict(stack)
                for stack_id, stack in self.tech_stacks.items()
            },
            'components': [asdict(comp) for comp in self.components.values()],
            'tasks': [asdict(task) for task in self.tasks.values()]
        }
        
        self.context_manager.update_project_context({
            'engineering_data': eng_data
        })

    def define_tech_stack(self, stack_id: str, languages: List[str],
                         frameworks: List[str], databases: List[str],
                         infrastructure: List[str], tools: List[str],
                         version_requirements: Dict[str, str]) -> TechStack:
        """Define a technology stack."""
        tech_stack = TechStack(
            languages=languages,
            frameworks=frameworks,
            databases=databases,
            infrastructure=infrastructure,
            tools=tools,
            version_requirements=version_requirements
        )
        
        self.tech_stacks[stack_id] = tech_stack
        self.save_engineering_data()
        
        # Add insight about tech stack
        self.context_manager.add_insight(
            f"Defined tech stack: {stack_id} with {', '.join(languages)} languages",
            'engineering',
            'engineering_manager'
        )
        
        return tech_stack

    def add_component(self, name: str, description: str, tech_stack_id: str,
                     dependencies: List[str], interfaces: List[str]) -> ArchitectureComponent:
        """Add a new architecture component."""
        if tech_stack_id not in self.tech_stacks:
            raise ValueError(f"Tech stack {tech_stack_id} not found")
            
        component_id = f"component_{len(self.components) + 1}"
        now = datetime.now().isoformat()
        
        component = ArchitectureComponent(
            id=component_id,
            name=name,
            description=description,
            tech_stack=self.tech_stacks[tech_stack_id],
            dependencies=dependencies,
            interfaces=interfaces,
            status="design",
            created_at=now,
            updated_at=now
        )
        
        self.components[component_id] = component
        self.save_engineering_data()
        
        # Add insight about new component
        self.context_manager.add_insight(
            f"Added new component: {name}",
            'engineering',
            'engineering_manager'
        )
        
        return component

    def create_technical_task(self, title: str, description: str,
                            component_id: str, requirement_ids: List[str],
                            estimated_hours: float, priority: int = 1,
                            dependencies: List[str] = None) -> TechnicalTask:
        """Create a new technical task."""
        if component_id not in self.components:
            raise ValueError(f"Component {component_id} not found")
            
        task_id = f"task_{len(self.tasks) + 1}"
        now = datetime.now().isoformat()
        
        task = TechnicalTask(
            id=task_id,
            title=title,
            description=description,
            component_id=component_id,
            requirement_ids=requirement_ids,
            assigned_to=None,
            estimated_hours=estimated_hours,
            priority=priority,
            status="todo",
            dependencies=dependencies or [],
            created_at=now,
            updated_at=now
        )
        
        self.tasks[task_id] = task
        self.save_engineering_data()
        
        # Add insight about new task
        self.context_manager.add_insight(
            f"Created technical task: {title}",
            'engineering',
            'engineering_manager'
        )
        
        return task

    def assign_task(self, task_id: str, engineer_id: str) -> TechnicalTask:
        """Assign a technical task to an engineer."""
        if task_id not in self.tasks:
            raise ValueError(f"Task {task_id} not found")
            
        task = self.tasks[task_id]
        task_dict = asdict(task)
        task_dict['assigned_to'] = engineer_id
        task_dict['updated_at'] = datetime.now().isoformat()
        
        self.tasks[task_id] = TechnicalTask(**task_dict)
        self.save_engineering_data()
        
        return self.tasks[task_id]

    def update_task_status(self, task_id: str, status: str,
                          completion_notes: Optional[str] = None) -> TechnicalTask:
        """Update the status of a technical task."""
        if task_id not in self.tasks:
            raise ValueError(f"Task {task_id} not found")
            
        task = self.tasks[task_id]
        task_dict = asdict(task)
        task_dict['status'] = status
        task_dict['updated_at'] = datetime.now().isoformat()
        
        if status == "completed":
            task_dict['completed_at'] = datetime.now().isoformat()
            
        self.tasks[task_id] = TechnicalTask(**task_dict)
        self.save_engineering_data()
        
        # Add insight about task completion
        if status == "completed":
            self.context_manager.add_insight(
                f"Completed task: {task.title}",
                'engineering',
                'engineering_manager'
            )
        
        return self.tasks[task_id]

    def get_component_dependencies(self, component_id: str) -> Dict[str, List[str]]:
        """Get all dependencies for a component."""
        if component_id not in self.components:
            raise ValueError(f"Component {component_id} not found")
            
        component = self.components[component_id]
        dependencies = {
            'direct': component.dependencies,
            'tasks': [
                task.id for task in self.tasks.values()
                if task.component_id == component_id
            ],
            'requirements': set()
        }
        
        # Collect all requirements from tasks
        for task in self.tasks.values():
            if task.component_id == component_id:
                dependencies['requirements'].update(task.requirement_ids)
        
        dependencies['requirements'] = list(dependencies['requirements'])
        return dependencies

    def analyze_technical_debt(self) -> Dict[str, Any]:
        """Analyze technical debt across components."""
        analysis = {
            'outdated_dependencies': [],
            'complex_components': [],
            'bottlenecks': [],
            'maintenance_needs': []
        }
        
        # Analyze each component
        for component in self.components.values():
            # Check for outdated dependencies
            for dep_id in component.dependencies:
                dep = self.components.get(dep_id)
                if dep and dep.status == "legacy":
                    analysis['outdated_dependencies'].append({
                        'component': component.id,
                        'dependency': dep_id
                    })
            
            # Check component complexity
            tasks = [t for t in self.tasks.values() if t.component_id == component.id]
            if len(tasks) > 10 or len(component.interfaces) > 5:
                analysis['complex_components'].append({
                    'component': component.id,
                    'num_tasks': len(tasks),
                    'num_interfaces': len(component.interfaces)
                })
            
            # Identify bottlenecks
            if len(component.dependencies) > 3:
                analysis['bottlenecks'].append({
                    'component': component.id,
                    'num_dependencies': len(component.dependencies)
                })
            
            # Check maintenance needs
            incomplete_tasks = [t for t in tasks if t.status != "completed"]
            if len(incomplete_tasks) > 5:
                analysis['maintenance_needs'].append({
                    'component': component.id,
                    'pending_tasks': len(incomplete_tasks)
                })
        
        return analysis

    def get_implementation_plan(self) -> List[Dict[str, Any]]:
        """Get the implementation plan based on current tasks and components."""
        # Get all tasks and sort by priority and dependencies
        all_tasks = list(self.tasks.values())
        
        # Build dependency graph
        dependency_graph: Dict[str, Set[str]] = {}
        for task in all_tasks:
            dependency_graph[task.id] = set(task.dependencies)
        
        # Topologically sort tasks
        sorted_tasks = self._topological_sort(dependency_graph)
        
        # Build implementation plan
        plan = []
        for task_id in sorted_tasks:
            task = self.tasks[task_id]
            component = self.components[task.component_id]
            
            plan.append({
                'phase': len(plan) + 1,
                'task': asdict(task),
                'component': asdict(component),
                'requirements': task.requirement_ids,
                'estimated_hours': task.estimated_hours
            })
        
        return plan

    def _topological_sort(self, graph: Dict[str, Set[str]]) -> List[str]:
        """Perform topological sort on tasks based on dependencies."""
        visited = set()
        temp = set()
        order = []
        
        def visit(task_id: str):
            if task_id in temp:
                raise ValueError("Circular dependency detected")
            if task_id in visited:
                return
            
            temp.add(task_id)
            for dep_id in graph.get(task_id, set()):
                visit(dep_id)
            
            temp.remove(task_id)
            visited.add(task_id)
            order.insert(0, task_id)
        
        for task_id in graph:
            if task_id not in visited:
                visit(task_id)
        
        return order 