from datetime import datetime
from enum import Enum
from typing import Any

from sqlalchemy import and_, case, func
from sqlalchemy.orm import Session
from common_logging import get_logger, log_execution

logger = get_logger(__name__)

class TaskStatus(str, Enum):
    PENDING = 'pending'
    IN_PROGRESS = 'in_progress'
    COMPLETED = 'completed'
    REVIEWED = 'reviewed'

class TaskPriority(str, Enum):
    LOW = 'low'
    MEDIUM = 'medium'
    HIGH = 'high'
    URGENT = 'urgent'

class AnnotationTaskManager:

    def __init__(self, db: Session):
        self.db = db

    @log_execution(logger)
    def create_task(self, title: str, description: str, content: str, task_type: str, priority: TaskPriority=TaskPriority.MEDIUM, deadline: datetime | None=None, assigned_to: int | None=None, created_by: int=None, metadata: dict | None=None) -> dict[str, Any]:
        from app.models import AnnotationTask
        task = AnnotationTask(title=title, description=description, content=content, task_type=task_type, status=TaskStatus.PENDING, priority=priority, deadline=deadline, assigned_to=assigned_to, created_by=created_by, meta_data=metadata or {})
        self.db.add(task)
        self.db.commit()
        self.db.refresh(task)
        logger.info(f'Created annotation task {task.id}: {title}')
        return self._task_to_dict(task)

    def assign_task(self, task_id: int, expert_id: int, assigned_by: int) -> dict[str, Any]:
        from app.models import AnnotationTask
        task = self.db.query(AnnotationTask).filter(AnnotationTask.id == task_id, not AnnotationTask.is_deleted).first()
        if not task:
            raise ValueError(f'Task {task_id} not found')
        task.assigned_to = expert_id
        task.assigned_by = assigned_by
        if task.status == TaskStatus.PENDING:
            task.status = TaskStatus.IN_PROGRESS
        self.db.commit()
        self.db.refresh(task)
        logger.info(f'Assigned task {task_id} to expert {expert_id}')
        return self._task_to_dict(task)

    def update_status(self, task_id: int, status: TaskStatus, updated_by: int) -> dict[str, Any]:
        from app.models import AnnotationTask
        task = self.db.query(AnnotationTask).filter(AnnotationTask.id == task_id, not AnnotationTask.is_deleted).first()
        if not task:
            raise ValueError(f'Task {task_id} not found')
        task.status = status
        task.updated_at = datetime.utcnow()
        if status == TaskStatus.COMPLETED:
            task.completed_at = datetime.utcnow()
        elif status == TaskStatus.REVIEWED:
            task.reviewed_at = datetime.utcnow()
        self.db.commit()
        self.db.refresh(task)
        logger.info(f'Updated task {task_id} status to {status}')
        return self._task_to_dict(task)

    def submit_annotation(self, task_id: int, annotation_data: dict[str, Any], submitted_by: int) -> dict[str, Any]:
        from app.models import AnnotationTask
        task = self.db.query(AnnotationTask).filter(AnnotationTask.id == task_id, not AnnotationTask.is_deleted).first()
        if not task:
            raise ValueError(f'Task {task_id} not found')
        task.annotation_data = annotation_data
        task.status = TaskStatus.COMPLETED
        task.completed_at = datetime.utcnow()
        self.db.commit()
        self.db.refresh(task)
        logger.info(f'Submitted annotation for task {task_id}')
        return self._task_to_dict(task)

    def add_review(self, task_id: int, quality_score: float, feedback: str, reviewed_by: int, approved: bool=True) -> dict[str, Any]:
        from app.models import AnnotationTask
        task = self.db.query(AnnotationTask).filter(AnnotationTask.id == task_id, not AnnotationTask.is_deleted).first()
        if not task:
            raise ValueError(f'Task {task_id} not found')
        if task.status != TaskStatus.COMPLETED:
            raise ValueError(f'Task {task_id} must be in completed status to review (current: {task.status})')
        task.quality_score = quality_score
        task.approved = approved
        task.reviewer_feedback = feedback
        task.reviewed_by = reviewed_by
        task.reviewed_at = datetime.utcnow()
        task.status = TaskStatus.REVIEWED if approved else TaskStatus.IN_PROGRESS
        self.db.commit()
        self.db.refresh(task)
        logger.info(f'Added review for task {task_id}: score={quality_score}, approved={approved}')
        return self._task_to_dict(task)

    def get_tasks_by_expert(self, expert_id: int, status: TaskStatus | None=None) -> list[dict[str, Any]]:
        from app.models import AnnotationTask
        query = self.db.query(AnnotationTask).filter(AnnotationTask.assigned_to == expert_id, not AnnotationTask.is_deleted)
        if status:
            query = query.filter(AnnotationTask.status == status)
        tasks = query.order_by(AnnotationTask.priority.desc(), AnnotationTask.deadline.asc()).all()
        return [self._task_to_dict(task) for task in tasks]

    def get_available_tasks(self, task_type: str | None=None, priority: TaskPriority | None=None) -> list[dict[str, Any]]:
        from app.models import AnnotationTask
        query = self.db.query(AnnotationTask).filter(AnnotationTask.status == TaskStatus.PENDING, AnnotationTask.assigned_to.is_(None))
        if task_type:
            query = query.filter(AnnotationTask.task_type == task_type)
        if priority:
            query = query.filter(AnnotationTask.priority == priority)
        tasks = query.order_by(AnnotationTask.priority.desc(), AnnotationTask.created_at.asc()).all()
        return [self._task_to_dict(task) for task in tasks]

    @log_execution(logger)
    def batch_review(self, task_ids: list[int], quality_score: float, feedback: str, approved: bool, reviewed_by: int) -> list[dict[str, Any]]:
        from app.models import AnnotationTask
        tasks = self.db.query(AnnotationTask).filter(AnnotationTask.id.in_(task_ids), not AnnotationTask.is_deleted).all()
        if len(tasks) != len(task_ids):
            found_ids = {task.id for task in tasks}
            missing = [task_id for task_id in task_ids if task_id not in found_ids]
            raise ValueError(f'Tasks not found: {missing}')
        invalid_tasks = [task.id for task in tasks if task.status != TaskStatus.COMPLETED]
        if invalid_tasks:
            raise ValueError(f'Tasks not completed: {invalid_tasks}')
        reviewed_at = datetime.utcnow()
        for task in tasks:
            task.quality_score = quality_score
            task.approved = approved
            task.reviewer_feedback = feedback
            task.reviewed_by = reviewed_by
            task.reviewed_at = reviewed_at
            task.status = TaskStatus.REVIEWED if approved else TaskStatus.IN_PROGRESS
        self.db.commit()
        for task in tasks:
            self.db.refresh(task)
        logger.info(f'Batch reviewed {len(tasks)} tasks: score={quality_score}, approved={approved}')
        return [self._task_to_dict(task) for task in tasks]

    @log_execution(logger)
    def assign_by_workload(self, task_ids: list[int], reviewer_ids: list[int], assigned_by: int) -> dict[str, Any]:
        from app.models import AnnotationTask
        if not task_ids:
            return {'assigned': []}
        if not reviewer_ids:
            raise ValueError('Reviewer list is empty')
        tasks = self.db.query(AnnotationTask).filter(AnnotationTask.id.in_(task_ids), not AnnotationTask.is_deleted).all()
        if len(tasks) != len(task_ids):
            found_ids = {task.id for task in tasks}
            missing = [task_id for task_id in task_ids if task_id not in found_ids]
            raise ValueError(f'Tasks not found: {missing}')
        workload_rows = self.db.query(AnnotationTask.assigned_to, func.count(AnnotationTask.id).label('count')).filter(AnnotationTask.assigned_to.in_(reviewer_ids), AnnotationTask.status == TaskStatus.IN_PROGRESS).group_by(AnnotationTask.assigned_to).all()
        workload_map = {row.assigned_to: row.count for row in workload_rows}
        workloads = {reviewer_id: workload_map.get(reviewer_id, 0) for reviewer_id in reviewer_ids}
        assignments: list[dict[str, Any]] = []
        for task in tasks:
            reviewer_id = min(workloads, key=workloads.get)
            task.assigned_to = reviewer_id
            task.assigned_by = assigned_by
            if task.status == TaskStatus.PENDING:
                task.status = TaskStatus.IN_PROGRESS
            workloads[reviewer_id] += 1
            assignments.append({'task_id': task.id, 'assigned_to': reviewer_id})
        self.db.commit()
        for task in tasks:
            self.db.refresh(task)
        logger.info(f'Assigned {len(tasks)} tasks by workload')
        return {'assigned': assignments}

    def get_review_analytics(self, start_time: datetime | None=None, end_time: datetime | None=None, reviewed_by: int | None=None, task_type: str | None=None, priority: TaskPriority | None=None) -> dict[str, Any]:
        from app.models import AnnotationTask
        base_filters = [AnnotationTask.status == TaskStatus.REVIEWED, not AnnotationTask.is_deleted]
        if start_time:
            base_filters.append(AnnotationTask.reviewed_at >= start_time)
        if end_time:
            base_filters.append(AnnotationTask.reviewed_at <= end_time)
        if reviewed_by:
            base_filters.append(AnnotationTask.reviewed_by == reviewed_by)
        if task_type:
            base_filters.append(AnnotationTask.task_type == task_type)
        if priority:
            base_filters.append(AnnotationTask.priority == priority)
        quality_row = self.db.query(func.avg(AnnotationTask.quality_score).label('avg_quality_score'), func.avg(case((AnnotationTask.approved.is_(True), 1.0), else_=0.0)).label('approval_rate'), func.count(AnnotationTask.id).label('total_reviews')).filter(*base_filters).one()
        efficiency_row = self.db.query(func.avg(func.extract('epoch', AnnotationTask.reviewed_at - AnnotationTask.completed_at)).label('avg_review_seconds'), func.sum(case((and_(AnnotationTask.deadline.isnot(None), AnnotationTask.reviewed_at > AnnotationTask.deadline), 1), else_=0)).label('overdue_count')).filter(*base_filters).one()
        reviewer_query = self.db.query(AnnotationTask.reviewed_by.label('reviewed_by'), func.count(AnnotationTask.id).label('review_count'), func.avg(AnnotationTask.quality_score).label('avg_quality_score')).filter(*base_filters).group_by(AnnotationTask.reviewed_by)
        reviewer_rows = reviewer_query.order_by(func.count(AnnotationTask.id).desc()).all()
        task_type_rows = self.db.query(AnnotationTask.task_type, func.count(AnnotationTask.id).label('count')).filter(*base_filters).group_by(AnnotationTask.task_type).all()
        priority_rows = self.db.query(AnnotationTask.priority, func.count(AnnotationTask.id).label('count')).filter(*base_filters).group_by(AnnotationTask.priority).all()
        return {'quality_metrics': {'avg_quality_score': float(quality_row.avg_quality_score or 0), 'approval_rate': float(quality_row.approval_rate or 0), 'total_reviews': int(quality_row.total_reviews or 0)}, 'efficiency_metrics': {'avg_review_seconds': float(efficiency_row.avg_review_seconds or 0), 'overdue_count': int(efficiency_row.overdue_count or 0)}, 'reviewer_ranking': [{'reviewed_by': row.reviewed_by, 'review_count': int(row.review_count), 'avg_quality_score': float(row.avg_quality_score or 0)} for row in reviewer_rows], 'distribution': {'task_type': [{'task_type': row.task_type, 'count': int(row.count)} for row in task_type_rows], 'priority': [{'priority': row.priority, 'count': int(row.count)} for row in priority_rows]}}

    def _task_to_dict(self, task) -> dict[str, Any]:
        return {'id': task.id, 'title': task.title, 'description': task.description, 'content': task.content, 'task_type': task.task_type, 'status': task.status, 'priority': task.priority, 'deadline': task.deadline.isoformat() if task.deadline else None, 'assigned_to': task.assigned_to, 'assigned_by': task.assigned_by, 'created_by': task.created_by, 'created_at': task.created_at.isoformat() if task.created_at else None, 'updated_at': task.updated_at.isoformat() if task.updated_at else None, 'completed_at': task.completed_at.isoformat() if task.completed_at else None, 'reviewed_at': task.reviewed_at.isoformat() if task.reviewed_at else None, 'reviewed_by': task.reviewed_by, 'quality_score': task.quality_score, 'approved': task.approved, 'reviewer_feedback': task.reviewer_feedback, 'annotation_data': task.annotation_data, 'meta_data': task.meta_data}

def get_annotation_task_manager(db: Session) -> AnnotationTaskManager:
    return AnnotationTaskManager(db)
