"""Task Data Access Object"""
from typing import List, Optional, Dict, Any
from datetime import datetime, timezone

from sqlalchemy import select, update, and_, or_
from sqlalchemy.orm import selectinload
from sqlalchemy.ext.asyncio import AsyncSession

from src.dao.base import BaseDAO
from src.database.models import Task as TaskModel
from src.models.task import TaskCreate, TaskUpdate, TaskResponse, TaskStatus, TaskPriority


class TaskDAO(BaseDAO[TaskModel]):
    """Data Access Object for Task operations"""
    
    def __init__(self, session: AsyncSession):
        super().__init__(session, TaskModel)
    
    async def create_task(self, task_data: TaskCreate) -> TaskResponse:
        """Create a new task"""
        try:
            # Validate task_data is not a coroutine
            if hasattr(task_data, '__await__'):
                raise ValueError("task_data is a coroutine - ensure proper async handling")
            
            # Validate required fields
            if not hasattr(task_data, 'name') or not task_data.name:
                raise ValueError("Task name is required")
            if not hasattr(task_data, 'description') or not task_data.description:
                raise ValueError("Task description is required")
            
            # Create task model - ID will be auto-generated by database
            # Extract created_by_agent_type from metadata if present
            created_by_agent_type = None
            if task_data.metadata and 'created_by_agent_type' in task_data.metadata:
                created_by_agent_type = task_data.metadata['created_by_agent_type']
            
            task = TaskModel(
                title=task_data.name,  # name is the internal field name
                description=task_data.description,
                priority=task_data.priority.value,
                agent_id=task_data.assigned_to,
                meta_data=task_data.metadata,  # metadata is the internal field name
                created_by_agent_type=created_by_agent_type,  # Store in model field
                requires_documentation=task_data.requires_documentation,
                documentation_paths=task_data.documentation_paths,
                documentation_context=task_data.documentation_context
            )
            
            # Handle dependencies
            dependencies_list = getattr(task_data, 'dependencies', [])
            if dependencies_list:
                # Fetch dependency tasks
                stmt = select(TaskModel).where(TaskModel.id.in_(dependencies_list))
                result = await self.session.execute(stmt)
                dependencies = result.scalars().all()
                
                if len(dependencies) != len(dependencies_list):
                    raise ValueError("One or more dependency tasks not found")
                
                task.dependencies = dependencies
            
            # Add and flush to get ID
            await self.add(task)
            await self.flush()
            
            # Convert to response model
            return await self._to_response(task)
            
        except Exception as e:
            await self.rollback()
            self.handle_error(e, "create_task")
    
    async def get_task(self, task_id: int) -> Optional[TaskResponse]:
        """Get a single task by ID"""
        try:
            stmt = select(TaskModel).where(TaskModel.id == task_id)
            result = await self.session.execute(stmt)
            task = result.scalar_one_or_none()
            
            if not task:
                return None
            
            return await self._to_response(task)
        except Exception as e:
            self.handle_error(e, "get_task")
    
    async def update_task_status(self, task_id: int, update_data: TaskUpdate) -> TaskResponse:
        """Update task status and progress"""
        try:
            # Fetch task
            stmt = select(TaskModel).where(TaskModel.id == task_id)
            result = await self.session.execute(stmt)
            task = result.scalar_one_or_none()
            
            if not task:
                raise ValueError(f"Task {task_id} not found")
            
            # Update fields
            if update_data.status is not None:
                task.status = update_data.status
                
                # Set timestamps based on status
                if update_data.status == TaskStatus.IN_PROGRESS and not task.started_at:
                    task.started_at = datetime.now(timezone.utc)
                elif update_data.status in [TaskStatus.COMPLETED, TaskStatus.FAILED]:
                    task.completed_at = datetime.now(timezone.utc)
                    if update_data.status == TaskStatus.COMPLETED:
                        task.progress = 100
            
            if update_data.progress is not None:
                task.progress = update_data.progress
            
            if update_data.result is not None:
                task.result = update_data.result
                
            if update_data.error is not None:
                task.error = update_data.error
                
            if update_data.assigned_to is not None:
                task.assigned_to = update_data.assigned_to
            
            task.updated_at = datetime.now(timezone.utc)
            
            await self.flush()
            return await self._to_response(task)
            
        except Exception as e:
            await self.rollback()
            self.handle_error(e, "update_task_status")
    
    async def get_task_tree(self, root_task_id: int) -> Dict[str, Any]:
        """Get hierarchical task tree starting from root task"""
        try:
            # Fetch root task with dependencies and dependents
            stmt = (
                select(TaskModel)
                .options(selectinload(TaskModel.dependencies))
                .options(selectinload(TaskModel.dependents))
                .where(TaskModel.id == root_task_id)
            )
            result = await self.session.execute(stmt)
            root_task = result.scalar_one_or_none()
            
            if not root_task:
                raise ValueError(f"Task {root_task_id} not found")
            
            # Build tree recursively
            return await self._build_task_tree(root_task)
            
        except Exception as e:
            self.handle_error(e, "get_task_tree")
    
    async def _build_task_tree(self, task: TaskModel, visited: Optional[set] = None) -> Dict[str, Any]:
        """Recursively build task tree"""
        if visited is None:
            visited = set()
            
        if task.id in visited:
            return {"id": task.id, "name": task.name, "circular_reference": True}
            
        visited.add(task.id)
        
        # Reload task with dependents to avoid lazy loading
        stmt = (
            select(TaskModel)
            .options(selectinload(TaskModel.dependents))
            .where(TaskModel.id == task.id)
        )
        result = await self.session.execute(stmt)
        task = result.scalar_one()
        
        # Get children (tasks that depend on this task)
        children = []
        for dependent in task.dependents:
            if dependent.id not in visited:
                child_tree = await self._build_task_tree(dependent, visited)
                children.append(child_tree)
        
        return {
            "id": str(task.id),  # Convert to string
            "name": task.title,  # Map 'title' to 'name'
            "status": task.status,  # Already a string in database
            "progress": task.progress if hasattr(task, 'progress') else 0,
            "children": children
        }
    
    async def find_tasks_by_status(self, status: TaskStatus, limit: Optional[int] = None) -> List[TaskResponse]:
        """Find all tasks with given status"""
        try:
            stmt = select(TaskModel).where(TaskModel.status == status)
            
            if limit:
                stmt = stmt.limit(limit)
                
            result = await self.session.execute(stmt)
            tasks = result.scalars().all()
            
            return [await self._to_response(task) for task in tasks]
            
        except Exception as e:
            self.handle_error(e, "find_tasks_by_status")
    
    async def bulk_create_tasks(self, tasks_data: List[TaskCreate]) -> List[TaskResponse]:
        """Create multiple tasks in a single transaction"""
        try:
            created_tasks = []
            
            # Don't use nested transaction context, we're already in a session
            for task_data in tasks_data:
                # Extract created_by_agent_type from metadata if present
                created_by_agent_type = None
                if task_data.metadata and 'created_by_agent_type' in task_data.metadata:
                    created_by_agent_type = task_data.metadata['created_by_agent_type']
                
                task = TaskModel(
                    title=task_data.name,
                    description=task_data.description,
                    priority=task_data.priority.value,
                    agent_id=task_data.assigned_to,
                    meta_data=task_data.metadata,
                    created_by_agent_type=created_by_agent_type,
                    requires_documentation=task_data.requires_documentation,
                    documentation_paths=task_data.documentation_paths,
                    documentation_context=task_data.documentation_context
                )
                self.session.add(task)
                created_tasks.append(task)
            
            await self.session.flush()
            
            return [await self._to_response(task) for task in created_tasks]
            
        except Exception as e:
            await self.session.rollback()
            raise ValueError(f"Failed to create tasks: {str(e)}")
    
    async def _to_response(self, task: TaskModel) -> TaskResponse:
        """Convert TaskModel to TaskResponse"""
        # Get dependency IDs - always reload to avoid lazy loading issues
        stmt = (
            select(TaskModel)
            .options(selectinload(TaskModel.dependencies))
            .options(selectinload(TaskModel.dependents))
            .where(TaskModel.id == task.id)
        )
        result = await self.session.execute(stmt)
        task_with_deps = result.scalar_one()
        
        dependency_ids = [dep.id for dep in task_with_deps.dependencies] if task_with_deps.dependencies else []
        dependent_ids = [dep.id for dep in task_with_deps.dependents] if task_with_deps.dependents else []
        
        return TaskResponse(
            id=str(task.id),  # Convert integer ID to string for response
            name=task.title,  # Map 'title' field to 'name' in response
            description=task.description,
            status=TaskStatus(task.status),  # Convert string to enum
            priority=TaskPriority(task.priority),  # Convert string to enum
            progress=task.progress if hasattr(task, 'progress') else 0,  # Default to 0 if not set
            created_at=task.created_at,
            updated_at=task.updated_at,
            started_at=task.started_at,
            completed_at=task.completed_at,
            dependencies=[str(dep_id) for dep_id in dependency_ids],  # Convert to strings
            dependents=[str(dep_id) for dep_id in dependent_ids],  # Convert to strings
            assigned_to=task.agent_id,  # Map 'agent_id' to 'assigned_to'
            result=task.result if hasattr(task, 'result') else None,
            error=task.error if hasattr(task, 'error') else None,
            metadata=task.meta_data or {},
            requires_documentation=task.requires_documentation,
            documentation_paths=task.documentation_paths or [],
            documentation_context=task.documentation_context
        )
    
    async def get_tasks_by_status(
        self,
        status: str,
        limit: int = 100
    ) -> List[TaskResponse]:
        """Get tasks by status
        
        Args:
            status: Task status to filter by
            limit: Maximum number of tasks to return
            
        Returns:
            List of tasks with the specified status
        """
        stmt = (
            select(TaskModel)
            .where(TaskModel.status == status)
            .order_by(TaskModel.created_at.desc())
            .limit(limit)
        )
        
        result = await self.session.execute(stmt)
        tasks = result.scalars().all()
        
        return [await self._to_response(task) for task in tasks]