"""
Task Assignment MCP Server

A Model Context Protocol server that provides intelligent task assignment capabilities.
This server implements task decomposition, capability matching, smart assignment,
team collaboration, and daily review functionalities.
"""

import os
import sys
import asyncio
import logging
from typing import Dict, List, Any, Optional, Union
from datetime import datetime, timedelta
from contextlib import asynccontextmanager
from collections.abc import AsyncIterator

# UTF-8 encoding is handled by modern Python and MCP library
# Removed custom encoding wrapper to avoid conflicts with MCP 1.12.0+

def safe_encode_string(text: str) -> str:
    """
    Safely encode a string to handle Unicode issues.

    Args:
        text: Input string that may contain Unicode characters

    Returns:
        Safely encoded string
    """
    try:
        return text.encode('utf-8', errors='replace').decode('utf-8')
    except (UnicodeEncodeError, UnicodeDecodeError, AttributeError):
        return str(text) if text is not None else ""


def safe_format_error(error: Exception) -> str:
    """
    Safely format an error message to handle Unicode issues.

    Args:
        error: Exception object

    Returns:
        Safely formatted error message
    """
    try:
        error_str = str(error)
        return safe_encode_string(error_str)
    except Exception:
        return "Unknown error occurred"


def safe_prepare_ai_data(data: dict) -> dict:
    """
    Safely prepare data for AI processing with encoding safety.

    Args:
        data: Dictionary containing data to be sent to AI

    Returns:
        Safely encoded dictionary
    """
    safe_data = {}
    for key, value in data.items():
        if isinstance(value, str):
            safe_data[key] = safe_encode_string(value)
        elif isinstance(value, (int, float, bool)):
            safe_data[key] = value
        elif isinstance(value, list):
            safe_data[key] = [safe_encode_string(str(item)) if isinstance(item, str) else item for item in value]
        elif value is None:
            safe_data[key] = None
        else:
            safe_data[key] = safe_encode_string(str(value))
    return safe_data

from fastmcp import FastMCP, Context
from pydantic import BaseModel, Field

# Import shared components
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

from shared.models import Task, HumanResource, AgentResource, Resource, TaskStatus, TaskPriority
from shared.agents.task_decomposition_agent import TaskDecompositionAgent
from shared.agents.dependency_manager import DependencyManager
from shared.database import get_database

# Disable logging for MCP compatibility - prevents stdio pollution
logging.disable(logging.CRITICAL)
logger = logging.getLogger(__name__)


class TaskDecompositionResult(BaseModel):
    """Result of task decomposition"""
    parent_task_id: str = Field(description="ID of the parent task")
    subtasks: List[Task] = Field(description="Generated subtasks")
    decomposition_reasoning: str = Field(description="Explanation of decomposition approach")
    estimated_total_time: float = Field(description="Total estimated time for all subtasks")


class AssignmentRecommendation(BaseModel):
    """Task assignment recommendation"""
    task_id: str = Field(description="Task to be assigned")
    recommended_resource_id: str = Field(description="Recommended resource ID")
    resource_type: str = Field(description="Type of resource (human/agent)")
    confidence_score: float = Field(description="Confidence in the recommendation (0-1)")
    reasoning: str = Field(description="Explanation for the recommendation")
    estimated_completion_time: float = Field(description="Estimated completion time in hours")
    skill_match_score: float = Field(description="How well skills match (0-1)")
    availability_score: float = Field(description="Resource availability score (0-1)")


class TeamCollaborationPlan(BaseModel):
    """Team collaboration plan for complex tasks"""
    task_id: str = Field(description="Main task ID")
    collaboration_type: str = Field(description="Type of collaboration needed")
    team_members: List[str] = Field(description="List of resource IDs involved")
    coordination_strategy: str = Field(description="How team members should coordinate")
    communication_plan: str = Field(description="Communication requirements")
    milestone_schedule: List[Dict[str, Any]] = Field(description="Key milestones and deadlines")


class DailyReportReview(BaseModel):
    """Daily report review and scoring"""
    report_date: datetime = Field(description="Date of the report")
    team_member_id: str = Field(description="ID of the team member")
    productivity_score: float = Field(description="Productivity score (0-10)")
    quality_score: float = Field(description="Quality score (0-10)")
    collaboration_score: float = Field(description="Collaboration score (0-10)")
    overall_score: float = Field(description="Overall performance score (0-10)")
    feedback: str = Field(description="Detailed feedback and suggestions")
    improvement_areas: List[str] = Field(description="Areas for improvement")
    achievements: List[str] = Field(description="Notable achievements")


class AppContext(BaseModel):
    """Application context with dependencies"""
    model_config = {"arbitrary_types_allowed": True}

    task_decomposition_agent: Optional[TaskDecompositionAgent] = None
    dependency_manager: Optional[DependencyManager] = None
    database: Any = Field(default=None)  # DatabaseManager instance
    resources: Dict[str, Resource] = Field(default_factory=dict)
    assignments: Dict[str, str] = Field(default_factory=dict)  # task_id -> resource_id
    collaboration_plans: Dict[str, TeamCollaborationPlan] = Field(default_factory=dict)
    daily_reports: List[DailyReportReview] = Field(default_factory=list)


@asynccontextmanager
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
    """Manage application lifecycle with dependencies"""
    logger.info("Initializing Task Assignment MCP Server...")

    # Initialize database
    database = get_database()

    # Initialize AI agents
    api_key = os.environ.get("OPENAI_API_KEY")
    base_url = os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1")

    if not api_key:
        logger.warning("OPENAI_API_KEY not set. AI features will be limited.")

    task_decomposition_agent = TaskDecompositionAgent(api_key=api_key, base_url=base_url) if api_key else None
    dependency_manager = DependencyManager(tasks={}, api_key=api_key, base_url=base_url) if api_key else None
    
    # Initialize with sample resources
    sample_resources = {
        "human_001": HumanResource(
            id="human_001",
            name="Alice Johnson",
            skills=["project_management", "requirements_analysis", "documentation"],
            load=0.3,
            cost_per_hour=85.0,
            status="available"
        ),
        "human_002": HumanResource(
            id="human_002", 
            name="Bob Smith",
            skills=["python", "backend_development", "api_design", "database"],
            load=0.7,
            cost_per_hour=95.0,
            status="available"
        ),
        "agent_001": AgentResource(
            id="agent_001",
            name="Code Generation Agent",
            skills=["code_generation", "unit_testing", "documentation"],
            load=0.2,
            cost_per_task=0.5,
            system_prompt="You are a code generation specialist. Generate clean, well-documented code.",
            status="available"
        ),
        "agent_002": AgentResource(
            id="agent_002",
            name="QA Testing Agent", 
            skills=["testing", "quality_assurance", "bug_detection"],
            load=0.1,
            cost_per_task=0.3,
            system_prompt="You are a QA specialist. Focus on thorough testing and quality assurance.",
            status="available"
        )
    }
    
    context = AppContext(
        task_decomposition_agent=task_decomposition_agent,
        dependency_manager=dependency_manager,
        database=database,
        resources=sample_resources
    )
    
    logger.info("Task Assignment MCP Server initialized successfully")
    
    try:
        yield context
    finally:
        logger.info("Shutting down Task Assignment MCP Server...")


# Create the MCP server
mcp = FastMCP("Task Assignment MCP", lifespan=app_lifespan)


@mcp.tool()
def decompose_complex_task(
    task_id: str,
    ctx: Context,
    max_subtasks: int = 5,
    target_time_per_subtask: float = 4.0
) -> TaskDecompositionResult:
    """
    Decompose a complex task into manageable subtasks.

    Uses AI to intelligently break down large tasks into smaller,
    more manageable pieces with clear deliverables.
    """
    # Prevent infinite recursion by checking if task already has subtasks
    # This is a simple but effective way to prevent recursion without additional parameters

    app_ctx = ctx.request_context.lifespan_context

    # Try to get task from database first, fallback to in-memory tasks
    task = None

    # Check if we have a real database (not a Mock)
    database = getattr(app_ctx, 'database', None)
    if database and not str(type(database)).startswith("<class 'unittest.mock."):
        try:
            task = database.get_task(task_id)
        except:
            pass

    # Fallback to in-memory tasks
    if not task and hasattr(app_ctx, 'tasks') and app_ctx.tasks:
        task = app_ctx.tasks.get(task_id)

    if not task:
        raise ValueError(f"Task {task_id} not found")

    # Prevent infinite recursion - if task already has subtasks, don't decompose again
    try:
        subtasks_list = getattr(task, 'subtasks', [])
        if subtasks_list and len(subtasks_list) > 0:
            logger.warning(f"Task {task_id} already has subtasks, skipping decomposition")
            # Try to get existing subtasks
            existing_subtasks = []
            if hasattr(app_ctx, 'database') and app_ctx.database:
                existing_subtasks = [app_ctx.database.get_task(st_id) for st_id in subtasks_list if app_ctx.database.get_task(st_id)]
            elif hasattr(app_ctx, 'tasks') and app_ctx.tasks:
                existing_subtasks = [app_ctx.tasks.get(st_id) for st_id in subtasks_list if app_ctx.tasks.get(st_id)]

            return TaskDecompositionResult(
                parent_task_id=task_id,
                subtasks=existing_subtasks,
                decomposition_reasoning="Task already decomposed",
                estimated_total_time=sum(float(getattr(st, 'time_cost', 0)) for st in existing_subtasks if st)
            )
    except (TypeError, AttributeError):
        # If we can't check subtasks, continue with decomposition
        pass
    
    if not getattr(app_ctx, 'task_decomposition_agent', None):
        # Fallback decomposition without AI
        subtasks = []
        try:
            time_cost = float(getattr(task, 'time_cost', 0))
            task_name = str(getattr(task, 'name', 'Unknown Task'))
            task_priority = getattr(task, 'priority', TaskPriority.MEDIUM)
            project_id = getattr(task, 'project_id', None)

            if time_cost > target_time_per_subtask:
                num_subtasks = min(max_subtasks, int(time_cost / target_time_per_subtask))
                time_per_subtask = time_cost / num_subtasks

                for i in range(num_subtasks):
                    subtask = Task(
                        id=f"{task_id}_sub_{i+1}",
                        name=f"{task_name} - Part {i+1}",
                        description=f"Subtask {i+1} of {task_name}",
                        time_cost=time_per_subtask,
                        priority=task_priority,
                        project_id=project_id
                    )
                    subtasks.append(subtask)

                # Save subtasks to database if available
                for subtask in subtasks:
                    if hasattr(app_ctx, 'database') and app_ctx.database and not str(type(app_ctx.database)).startswith("<class 'unittest.mock."):
                        try:
                            app_ctx.database.create_task(subtask)
                        except:
                            pass
                    elif hasattr(app_ctx, 'tasks') and app_ctx.tasks:
                        app_ctx.tasks[subtask.id] = subtask

                # Update parent task to reference subtasks
                if hasattr(task, 'subtasks'):
                    task.subtasks = [st.id for st in subtasks]

                # Save parent task update
                if hasattr(app_ctx, 'database') and app_ctx.database and not str(type(app_ctx.database)).startswith("<class 'unittest.mock."):
                    try:
                        app_ctx.database.update_task(task)
                    except:
                        pass
        except (TypeError, AttributeError, ValueError):
            # If we can't decompose, return empty result
            pass

        return TaskDecompositionResult(
            parent_task_id=task_id,
            subtasks=subtasks,
            decomposition_reasoning="Simple time-based decomposition (AI not available)",
            estimated_total_time=sum(st.time_cost for st in subtasks)
        )
    
    try:
        # Use AI for intelligent decomposition
        existing_tasks = []
        if hasattr(app_ctx, 'database') and app_ctx.database and not str(type(app_ctx.database)).startswith("<class 'unittest.mock."):
            try:
                existing_tasks = app_ctx.database.list_tasks()
            except:
                pass
        elif hasattr(app_ctx, 'tasks') and app_ctx.tasks:
            try:
                existing_tasks = list(app_ctx.tasks.values())
            except:
                pass

        existing_task_ids = [getattr(t, 'id', '') for t in existing_tasks if hasattr(t, 'id')]

        # Prepare task data for AI with safe encoding
        task_data = safe_prepare_ai_data({
            "id": task.id,
            "name": task.name,
            "description": task.description,
            "time_cost": task.time_cost,
            "priority": task.priority.value if task.priority else "medium",
            "project_id": task.project_id
        })

        raw_subtasks = app_ctx.task_decomposition_agent.decompose_task(
            task_data, existing_task_ids
        )

        subtasks = []
        for raw_subtask in raw_subtasks:
            subtask = Task(
                id=raw_subtask.get("id", f"{task_id}_sub_{len(subtasks)+1}"),
                name=safe_encode_string(raw_subtask.get("name", f"Subtask {len(subtasks)+1}")),
                description=safe_encode_string(raw_subtask.get("description", "")),
                time_cost=float(raw_subtask.get("time_cost", target_time_per_subtask)),
                priority=task.priority,
                project_id=task.project_id,
                dependencies=raw_subtask.get("dependencies", [])
            )
            subtasks.append(subtask)

            # Save subtask to database if available
            if hasattr(app_ctx, 'database') and app_ctx.database and not str(type(app_ctx.database)).startswith("<class 'unittest.mock."):
                try:
                    app_ctx.database.create_task(subtask)
                except:
                    pass
            elif hasattr(app_ctx, 'tasks') and app_ctx.tasks:
                app_ctx.tasks[subtask.id] = subtask

        # Update parent task to reference subtasks
        if hasattr(task, 'subtasks'):
            task.subtasks = [st.id for st in subtasks]
        
        return TaskDecompositionResult(
            parent_task_id=task_id,
            subtasks=subtasks,
            decomposition_reasoning="AI-powered intelligent decomposition based on task complexity and requirements",
            estimated_total_time=sum(st.time_cost for st in subtasks)
        )
        
    except Exception as e:
        error_msg = safe_format_error(e)
        logger.error(f"Error in task decomposition: {error_msg}")
        # Fallback to simple decomposition without AI
        if task.time_cost > target_time_per_subtask:
            num_subtasks = min(max_subtasks, int(task.time_cost / target_time_per_subtask))
            time_per_subtask = task.time_cost / num_subtasks

            subtasks = []
            for i in range(num_subtasks):
                subtask = Task(
                    id=f"{task_id}_subtask_{i+1}",
                    name=f"{task.name} - Part {i+1}",
                    description=f"Subtask {i+1} of {task.description}",
                    time_cost=time_per_subtask,
                    priority=task.priority,
                    project_id=task.project_id
                )
                subtasks.append(subtask)

                # Save subtask to database if available
                if hasattr(app_ctx, 'database') and app_ctx.database and not str(type(app_ctx.database)).startswith("<class 'unittest.mock."):
                    try:
                        app_ctx.database.create_task(subtask)
                    except:
                        pass
                elif hasattr(app_ctx, 'tasks') and app_ctx.tasks:
                    app_ctx.tasks[subtask.id] = subtask

            # Update parent task to reference subtasks
            if hasattr(task, 'subtasks'):
                task.subtasks = [st.id for st in subtasks]

            # Save parent task update
            if hasattr(app_ctx, 'database') and app_ctx.database and not str(type(app_ctx.database)).startswith("<class 'unittest.mock."):
                try:
                    app_ctx.database.update_task(task)
                except:
                    pass

            return TaskDecompositionResult(
                parent_task_id=task_id,
                subtasks=subtasks,
                decomposition_reasoning=f"Simple fallback decomposition due to error: {error_msg}",
                estimated_total_time=task.time_cost
            )
        else:
            # Task is already small enough
            return TaskDecompositionResult(
                parent_task_id=task_id,
                subtasks=[task],
                decomposition_reasoning=f"Task is already optimal size (error occurred: {error_msg})",
                estimated_total_time=task.time_cost
            )


@mcp.tool()
def recommend_task_assignment(
    task_id: str,
    ctx: Context,
    consider_workload: bool = True,
    prefer_human: bool = False
) -> AssignmentRecommendation:
    """
    Recommend the best resource to assign a task to.

    Analyzes task requirements, resource capabilities, current workload,
    and availability to make optimal assignment recommendations.
    """
    app_ctx = ctx.request_context.lifespan_context

    # Try to get task from multiple sources
    task = None

    # 1. Try shared database first
    try:
        from shared.database import DatabaseManager
        shared_db = DatabaseManager()
        task = shared_db.get_task(task_id)
        if task:
            logger.info(f"Found task {task_id} in shared database")
    except Exception as e:
        logger.debug(f"Shared database lookup failed: {e}")

    # 2. Try local database
    if not task:
        database = getattr(app_ctx, 'database', None)
        if database and not str(type(database)).startswith("<class 'unittest.mock."):
            try:
                task = database.get_task(task_id)
                if task:
                    logger.info(f"Found task {task_id} in local database")
            except Exception as e:
                logger.debug(f"Local database lookup failed: {e}")

    # 3. Fallback to in-memory tasks
    if not task and hasattr(app_ctx, 'tasks') and app_ctx.tasks:
        task = app_ctx.tasks.get(task_id)
        if task:
            logger.info(f"Found task {task_id} in memory")

    # 4. Create a mock task for demonstration if not found
    if not task:
        logger.warning(f"Task {task_id} not found in any storage. Creating mock task for demonstration.")
        from shared.models import Task, TaskPriority, TaskStatus
        from datetime import datetime

        task = Task(
            id=task_id,
            name=f"Demo Task {task_id}",
            description=f"This is a demonstration task for assignment analysis. In a real scenario, this task would be created by the task-management service first.",
            priority=TaskPriority.MEDIUM,
            status=TaskStatus.TODO,
            time_cost=2.0,
            created_at=datetime.now(),
            updated_at=datetime.now()
        )

        # Add some realistic attributes for assignment analysis
        task.required_skills = ["general"]
        task.estimated_effort = "medium"

    best_resource = None
    best_score = 0.0
    best_reasoning = ""
    best_skill_score = 0.0
    best_availability_score = 0.0

    # Get resources with type safety
    resources = {}
    if hasattr(app_ctx, 'resources') and app_ctx.resources:
        resources = app_ctx.resources

    for resource_id, resource in resources.items():
        score = 0.0
        reasoning_parts = []

        # Skill matching score with type safety and improved matching
        skill_score = 0  # Initialize for each resource
        matched_skills = []
        try:
            task_name = str(getattr(task, 'name', ''))
            task_description = str(getattr(task, 'description', ''))
            task_keywords = (task_name + " " + task_description).lower()

            resource_skills = getattr(resource, 'skills', [])
            skill_matches = 0

            for skill in resource_skills:
                skill_lower = str(skill).lower()
                # Direct match
                if skill_lower in task_keywords:
                    skill_matches += 1
                    matched_skills.append(skill)
                else:
                    # Partial match - check if skill words are in task keywords
                    skill_words = skill_lower.replace('_', ' ').split()
                    if any(word in task_keywords for word in skill_words if len(word) > 2):
                        skill_matches += 0.5  # Partial match gets half credit
                        matched_skills.append(f"{skill}*")  # Mark as partial match

            skill_score = min(skill_matches * 30, 120)  # Increased weight for skills
        except (TypeError, AttributeError):
            skill_score = 0

        score += skill_score
        if matched_skills:
            reasoning_parts.append(f"Skills ({', '.join(matched_skills)}): {skill_score}")
        else:
            reasoning_parts.append(f"Skill match: {skill_score}")

        # Availability score
        if consider_workload:
            if isinstance(resource, HumanResource):
                availability = max(0, 1.0 - resource.load)
                availability_score = availability * 50
            else:  # AgentResource
                availability = max(0, 1.0 - resource.load / 5.0)  # Agents can handle more parallel tasks
                availability_score = availability * 50
        else:
            availability_score = 50

        score += availability_score
        reasoning_parts.append(f"Availability: {availability_score}")

        # Resource type preference
        if prefer_human and isinstance(resource, HumanResource):
            type_bonus = 20
        elif not prefer_human and isinstance(resource, AgentResource):
            type_bonus = 15
        else:
            type_bonus = 0

        score += type_bonus
        if type_bonus > 0:
            reasoning_parts.append(f"Type preference: {type_bonus}")

        # Cost efficiency (lower cost = higher score)
        if isinstance(resource, HumanResource):
            cost_score = max(0, 100 - resource.cost_per_hour)
        else:
            cost_score = max(0, 100 - resource.cost_per_task * 10)

        score += cost_score * 0.1  # Weight cost much less than skills/availability
        reasoning_parts.append(f"Cost efficiency: {cost_score * 0.1:.1f}")

        if score > best_score:
            best_score = score
            best_resource = resource
            best_reasoning = "; ".join(reasoning_parts)
            best_skill_score = skill_score
            best_availability_score = availability_score

    if not best_resource:
        return AssignmentRecommendation(
            task_id=task_id,
            recommended_resource_id="",
            resource_type="none",
            confidence_score=0.0,
            reasoning="No suitable resources found",
            estimated_completion_time=float(getattr(task, 'time_cost', 0)),
            skill_match_score=0.0,
            availability_score=0.0
        )

    # Calculate estimated completion time based on resource efficiency with type safety
    try:
        task_time_cost = float(getattr(task, 'time_cost', 0))
        if isinstance(best_resource, HumanResource):
            resource_skills = getattr(best_resource, 'skills', [])
            efficiency_factor = 1.0 + (len(resource_skills) * 0.1)  # More skills = faster
            completion_time = task_time_cost / efficiency_factor
        else:
            completion_time = task_time_cost * 0.8  # Agents are generally faster for routine tasks
    except (TypeError, AttributeError, ZeroDivisionError):
        completion_time = 1.0  # Default fallback

    return AssignmentRecommendation(
        task_id=task_id,
        recommended_resource_id=best_resource.id,
        resource_type=best_resource.type,
        confidence_score=min(best_score / 200, 1.0),  # Normalize to 0-1
        reasoning=f"Best match: {best_resource.name} - {best_reasoning}",
        estimated_completion_time=completion_time,
        skill_match_score=best_skill_score / 120,  # Use best resource's skill score
        availability_score=best_availability_score / 50  # Use best resource's availability score
    )


@mcp.tool()
def assign_task_to_resource(
    task_id: str,
    resource_id: str,
    ctx: Context,
    start_date: Optional[str] = None,
    notes: str = ""
) -> Dict[str, Any]:
    """
    Assign a task to a specific resource.

    Creates the assignment, updates resource workload, and sets up
    tracking for the task execution.
    """
    app_ctx = ctx.request_context.lifespan_context

    # Try to get task from database first, fallback to in-memory tasks
    task = None

    # Check if we have a real database (not a Mock)
    database = getattr(app_ctx, 'database', None)
    if database and not str(type(database)).startswith("<class 'unittest.mock."):
        try:
            task = database.get_task(task_id)
        except:
            pass

    # Fallback to in-memory tasks
    if not task and hasattr(app_ctx, 'tasks') and app_ctx.tasks:
        task = app_ctx.tasks.get(task_id)

    if not task:
        raise ValueError(f"Task {task_id} not found")

    # Get resource with type safety
    resource = None
    if hasattr(app_ctx, 'resources') and app_ctx.resources:
        resource = app_ctx.resources.get(resource_id)

    if not resource:
        raise ValueError(f"Resource {resource_id} not found")

    # Update task assignment with type safety
    try:
        if hasattr(task, 'assignee'):
            task.assignee = resource_id
        if hasattr(task, 'status'):
            task.status = TaskStatus.TODO
        if start_date and hasattr(task, 'estimated_completion'):
            time_cost = float(getattr(task, 'time_cost', 0))
            task.estimated_completion = datetime.fromisoformat(start_date) + timedelta(hours=time_cost)
    except (TypeError, AttributeError, ValueError):
        pass

    # Save task to database if available
    if hasattr(app_ctx, 'database') and app_ctx.database:
        app_ctx.database.update_task(task)

    # Update resource workload with type safety
    try:
        from shared.models import HumanResource
        if isinstance(resource, HumanResource):
            current_load = float(getattr(resource, 'load', 0.0))
            time_cost = float(getattr(task, 'time_cost', 0))
            resource.load = min(1.0, current_load + (time_cost / 40))  # Assume 40-hour work week
    except (TypeError, AttributeError, ImportError):
        pass
    else:
        resource.load += 1  # Simple task count for agents

    # Record assignment
    app_ctx.assignments[task_id] = resource_id

    return {
        "task_id": task_id,
        "resource_id": resource_id,
        "resource_name": resource.name,
        "assignment_date": datetime.now().isoformat(),
        "estimated_completion": task.estimated_completion.isoformat() if task.estimated_completion else None,
        "notes": notes,
        "status": "assigned"
    }


@mcp.tool()
def create_collaboration_plan(
    task_id: str,
    ctx: Context,
    required_skills: Optional[List[str]] = None,
    max_team_size: int = 4
) -> TeamCollaborationPlan:
    """
    Create a collaboration plan for complex tasks requiring multiple resources.

    Identifies the best team composition and coordination strategy for
    tasks that require diverse skills or parallel execution.
    """
    app_ctx = ctx.request_context.lifespan_context

    task = app_ctx.tasks.get(task_id)
    if not task:
        raise ValueError(f"Task {task_id} not found")

    # Determine required skills from task or use provided ones
    if not required_skills:
        task_text = (task.name + " " + task.description).lower()
        required_skills = []
        for resource in app_ctx.resources.values():
            for skill in resource.skills:
                if skill.lower() in task_text and skill not in required_skills:
                    required_skills.append(skill)

    # Select team members based on skills and availability
    team_members = []
    covered_skills = set()

    # Sort resources by availability and skill relevance
    available_resources = [
        (rid, r) for rid, r in app_ctx.resources.items()
        if r.status == "available" and r.load < 0.8
    ]

    for resource_id, resource in available_resources:
        if len(team_members) >= max_team_size:
            break

        # Check if this resource adds valuable skills
        resource_skills = set(resource.skills)
        new_skills = resource_skills - covered_skills

        if new_skills or len(team_members) == 0:  # Always include at least one member
            team_members.append(resource_id)
            covered_skills.update(resource_skills)

    # Determine collaboration type
    if len(team_members) <= 2:
        collaboration_type = "pair_programming"
        coordination_strategy = "Direct collaboration with shared responsibility"
    elif task.time_cost > 20:
        collaboration_type = "parallel_development"
        coordination_strategy = "Divide task into parallel workstreams with regular sync points"
    else:
        collaboration_type = "sequential_handoff"
        coordination_strategy = "Sequential execution with clear handoff points"

    # Create communication plan
    if len(team_members) <= 2:
        communication_plan = "Direct communication as needed, daily check-ins"
    else:
        communication_plan = "Daily standup meetings, shared documentation, milestone reviews"

    # Generate milestone schedule
    milestones = []
    if task.time_cost > 8:
        milestone_count = min(4, int(task.time_cost / 4))
        for i in range(milestone_count):
            milestone_date = datetime.now() + timedelta(days=(i + 1) * 2)
            milestones.append({
                "milestone": f"Milestone {i + 1}",
                "date": milestone_date.isoformat(),
                "deliverable": f"Progress checkpoint {i + 1}",
                "responsible": team_members[i % len(team_members)] if team_members else ""
            })

    collaboration_plan = TeamCollaborationPlan(
        task_id=task_id,
        collaboration_type=collaboration_type,
        team_members=team_members,
        coordination_strategy=coordination_strategy,
        communication_plan=communication_plan,
        milestone_schedule=milestones
    )

    app_ctx.collaboration_plans[task_id] = collaboration_plan
    return collaboration_plan


@mcp.tool()
def review_daily_report(
    team_member_id: str,
    report_content: str,
    ctx: Context,
    report_date: Optional[str] = None
) -> DailyReportReview:
    """
    Review and score a team member's daily report.

    Analyzes daily reports to provide feedback, scoring, and suggestions
    for improvement. Tracks performance trends over time.
    """
    app_ctx = ctx.request_context.lifespan_context

    resource = app_ctx.resources.get(team_member_id)
    if not resource:
        raise ValueError(f"Team member {team_member_id} not found")

    report_date_obj = datetime.fromisoformat(report_date) if report_date else datetime.now()

    # Analyze report content (simplified scoring algorithm)
    content_lower = report_content.lower()

    # Productivity scoring based on keywords and content length
    productivity_keywords = ["completed", "finished", "delivered", "implemented", "resolved", "achieved"]
    productivity_score = min(10, len([kw for kw in productivity_keywords if kw in content_lower]) * 2 + len(report_content) / 100)

    # Quality scoring based on detail and specific mentions
    quality_keywords = ["tested", "reviewed", "documented", "validated", "optimized", "refactored"]
    quality_score = min(10, len([kw for kw in quality_keywords if kw in content_lower]) * 2.5 + (3 if len(report_content) > 200 else 1))

    # Collaboration scoring based on team interaction mentions
    collaboration_keywords = ["discussed", "collaborated", "helped", "reviewed", "paired", "meeting", "feedback"]
    collaboration_score = min(10, len([kw for kw in collaboration_keywords if kw in content_lower]) * 3 + 2)

    # Overall score (weighted average)
    overall_score = (productivity_score * 0.4 + quality_score * 0.4 + collaboration_score * 0.2)

    # Generate feedback based on scores
    feedback_parts = []
    achievements = []
    improvement_areas = []

    if productivity_score >= 8:
        achievements.append("High productivity with multiple completed tasks")
    elif productivity_score < 5:
        improvement_areas.append("Focus on completing more tasks and deliverables")
        feedback_parts.append("Consider breaking down large tasks into smaller, manageable pieces")

    if quality_score >= 8:
        achievements.append("Strong attention to quality and detail")
    elif quality_score < 5:
        improvement_areas.append("Increase focus on testing and documentation")
        feedback_parts.append("Ensure all work is properly tested and documented")

    if collaboration_score >= 7:
        achievements.append("Good team collaboration and communication")
    elif collaboration_score < 4:
        improvement_areas.append("Increase team interaction and collaboration")
        feedback_parts.append("Participate more actively in team discussions and code reviews")

    # Add general feedback
    if overall_score >= 8:
        feedback_parts.append("Excellent work overall! Keep up the high standards.")
    elif overall_score >= 6:
        feedback_parts.append("Good performance with room for improvement in identified areas.")
    else:
        feedback_parts.append("Performance needs improvement. Focus on the suggested areas.")

    review = DailyReportReview(
        report_date=report_date_obj,
        team_member_id=team_member_id,
        productivity_score=productivity_score,
        quality_score=quality_score,
        collaboration_score=collaboration_score,
        overall_score=overall_score,
        feedback=". ".join(feedback_parts),
        improvement_areas=improvement_areas,
        achievements=achievements
    )

    app_ctx.daily_reports.append(review)
    return review


# Tools for resource management
@mcp.tool()
def get_resource_details(resource_id: str, ctx: Context) -> str:
    """Get detailed information about a specific resource (human or agent)"""
    app_ctx = ctx.request_context.lifespan_context

    # Get resource with type safety
    resource = None
    if hasattr(app_ctx, 'resources') and app_ctx.resources:
        resource = app_ctx.resources.get(resource_id)

    if not resource:
        return f"Resource {resource_id} not found"

    if isinstance(resource, HumanResource):
        return f"""
# Human Resource: {resource.name}

**ID:** {resource.id}
**Type:** Human
**Status:** {resource.status}
**Current Load:** {resource.load * 100:.1f}%

## Skills
{', '.join(resource.skills) if resource.skills else 'No specific skills listed'}

## Cost & Availability
- **Cost per Hour:** ${resource.cost_per_hour:.2f}
- **Availability:** {(1 - resource.load) * 100:.1f}%

## Current Assignments
{_get_resource_assignments(resource_id, ctx)}
"""
    else:  # AgentResource
        return f"""
# AI Agent: {resource.name}

**ID:** {resource.id}
**Type:** AI Agent
**Status:** {resource.status}
**Current Load:** {resource.load} active tasks

## Capabilities
{', '.join(resource.skills) if resource.skills else 'No specific capabilities listed'}

## Configuration
- **Cost per Task:** ${resource.cost_per_task:.2f}
- **System Prompt:** {resource.system_prompt[:100]}...

## Available Tools
{', '.join([tool.get('name', 'Unknown') for tool in resource.tools]) if resource.tools else 'No specific tools'}

## Current Assignments
{_get_resource_assignments(resource_id, ctx)}
"""


def _get_resource_assignments(resource_id: str, ctx: Context) -> str:
    """Helper function to get current assignments for a resource"""
    app_ctx = ctx.request_context.lifespan_context

    # Get tasks from database or in-memory storage
    all_tasks = []

    # Check if we have a real database (not a Mock)
    database = getattr(app_ctx, 'database', None)
    if database and not str(type(database)).startswith("<class 'unittest.mock."):
        try:
            all_tasks = database.list_tasks()
        except:
            pass

    # Fallback to in-memory tasks
    if not all_tasks and hasattr(app_ctx, 'tasks') and app_ctx.tasks:
        try:
            all_tasks = list(app_ctx.tasks.values())
        except:
            all_tasks = []

    assigned_tasks = []
    try:
        for task in all_tasks:
            if (getattr(task, 'assignee', None) == resource_id and
                getattr(task, 'status', None) != TaskStatus.COMPLETED):
                assigned_tasks.append(task)
    except (TypeError, AttributeError):
        pass

    if not assigned_tasks:
        return "No current assignments"

    assignment_list = []
    for task in assigned_tasks:
        try:
            task_status = getattr(task, 'status', None)
            status_value = getattr(task_status, 'value', 'unknown') if task_status else 'unknown'
            status_emoji = {"todo": "📋", "in_progress": "🔄", "blocked": "🚫"}.get(status_value, "❓")

            task_name = str(getattr(task, 'name', 'Unknown Task'))
            time_cost = float(getattr(task, 'time_cost', 0))
            assignment_list.append(f"- {status_emoji} **{task_name}** ({time_cost}h)")
        except (TypeError, AttributeError):
            assignment_list.append(f"- ❓ **Unknown Task** (0h)")

    return '\n'.join(assignment_list)


@mcp.resource("team://{project_id}/workload")
def get_team_workload_summary(project_id: str) -> str:
    """Get team workload summary for a project"""
    # Note: Resources don't have access to context in FastMCP
    # This is a simplified version without dynamic data
    return f"Team workload summary for project {project_id} - Use tools for dynamic data"

    # Get project tasks
    project_tasks = [t for t in app_ctx.tasks.values() if t.project_id == project_id]

    if not project_tasks:
        return f"No tasks found for project {project_id}"

    # Analyze workload distribution
    resource_workload = {}
    unassigned_tasks = []

    for task in project_tasks:
        if task.assignee:
            if task.assignee not in resource_workload:
                resource_workload[task.assignee] = {"tasks": 0, "hours": 0.0, "completed": 0}
            resource_workload[task.assignee]["tasks"] += 1
            resource_workload[task.assignee]["hours"] += task.time_cost
            if task.status == TaskStatus.COMPLETED:
                resource_workload[task.assignee]["completed"] += 1
        else:
            unassigned_tasks.append(task)

    # Generate summary
    summary_parts = [f"# Team Workload Summary - Project {project_id}\n"]

    if resource_workload:
        summary_parts.append("## Resource Allocation")
        for resource_id, workload in resource_workload.items():
            resource = app_ctx.resources.get(resource_id)
            resource_name = resource.name if resource else resource_id
            completion_rate = (workload["completed"] / workload["tasks"] * 100) if workload["tasks"] > 0 else 0

            summary_parts.append(f"""
### {resource_name} ({resource_id})
- **Total Tasks:** {workload["tasks"]}
- **Total Hours:** {workload["hours"]:.1f}
- **Completed:** {workload["completed"]} ({completion_rate:.1f}%)
- **Current Load:** {app_ctx.resources.get(resource_id, {}).get('load', 'Unknown') if resource_id in app_ctx.resources else 'Unknown'}
""")

    if unassigned_tasks:
        summary_parts.append(f"\n## Unassigned Tasks ({len(unassigned_tasks)})")
        for task in unassigned_tasks[:5]:  # Show first 5 unassigned tasks
            summary_parts.append(f"- **{task.name}** ({task.time_cost}h) - Priority: {TaskPriority(task.priority).name}")

        if len(unassigned_tasks) > 5:
            summary_parts.append(f"... and {len(unassigned_tasks) - 5} more")

    return '\n'.join(summary_parts)


# Prompts
@mcp.prompt()
def assignment_optimization_prompt(project_id: str) -> str:
    """Generate a prompt for optimizing task assignments across a project"""
    # Note: Prompts don't have access to context in FastMCP
    # This is a simplified version
    return f"Please optimize task assignments for project: {project_id}"

    # Get project tasks and assignments
    project_tasks = [t for t in app_ctx.tasks.values() if t.project_id == project_id]

    if not project_tasks:
        return f"No tasks found for project {project_id}"

    # Analyze current assignments
    assigned_tasks = [t for t in project_tasks if t.assignee]
    unassigned_tasks = [t for t in project_tasks if not t.assignee]

    # Get resource utilization
    resource_workload = {}
    for task in assigned_tasks:
        if task.assignee not in resource_workload:
            resource_workload[task.assignee] = {"tasks": 0, "hours": 0.0}
        resource_workload[task.assignee]["tasks"] += 1
        resource_workload[task.assignee]["hours"] += task.time_cost

    return f"""Analyze and optimize the task assignments for project {project_id}:

**Current Assignment Status:**
- Total tasks: {len(project_tasks)}
- Assigned tasks: {len(assigned_tasks)}
- Unassigned tasks: {len(unassigned_tasks)}

**Resource Utilization:**
{chr(10).join(f'- {rid}: {data["tasks"]} tasks, {data["hours"]} hours' for rid, data in resource_workload.items())}

**Unassigned Tasks:**
{chr(10).join(f'- {t.name} ({t.time_cost}h, Priority: {TaskPriority(t.priority).name})' for t in unassigned_tasks[:10])}

Please provide:
1. **Workload Balance Analysis:** Are resources evenly distributed? Who is over/under-utilized?
2. **Skill Matching Assessment:** Are tasks assigned to resources with appropriate skills?
3. **Priority Optimization:** Are high-priority tasks assigned to the most capable resources?
4. **Bottleneck Identification:** Which resources or dependencies might cause delays?
5. **Reassignment Recommendations:** Specific suggestions for improving assignments
6. **Timeline Impact:** How would optimizations affect project completion time?

Focus on actionable recommendations that can be implemented immediately."""


@mcp.prompt()
def team_performance_analysis_prompt(team_member_ids: List[str]) -> str:
    """Generate a prompt for analyzing team performance trends"""
    # Note: Prompts don't have access to context in FastMCP
    # This is a simplified version
    return f"Please analyze team performance for members: {', '.join(team_member_ids)}"

    if not team_member_ids:
        return "No team members specified for analysis"

    # Get recent reports for team members
    recent_reports = [r for r in app_ctx.daily_reports if r.team_member_id in team_member_ids]

    if not recent_reports:
        return "No daily reports found for the specified team members"

    # Organize reports by team member
    member_reports = {}
    for report in recent_reports:
        if report.team_member_id not in member_reports:
            member_reports[report.team_member_id] = []
        member_reports[report.team_member_id].append(report)

    # Calculate averages
    team_stats = {}
    for member_id, reports in member_reports.items():
        if reports:
            team_stats[member_id] = {
                "report_count": len(reports),
                "avg_productivity": sum(r.productivity_score for r in reports) / len(reports),
                "avg_quality": sum(r.quality_score for r in reports) / len(reports),
                "avg_collaboration": sum(r.collaboration_score for r in reports) / len(reports),
                "avg_overall": sum(r.overall_score for r in reports) / len(reports),
                "latest_report": max(reports, key=lambda r: r.report_date)
            }

    return f"""Analyze team performance trends for {len(team_member_ids)} team members:

**Team Performance Summary:**
{chr(10).join(f'''- **{member_id}**: {stats["report_count"]} reports
  - Productivity: {stats["avg_productivity"]:.1f}/10
  - Quality: {stats["avg_quality"]:.1f}/10
  - Collaboration: {stats["avg_collaboration"]:.1f}/10
  - Overall: {stats["avg_overall"]:.1f}/10''' for member_id, stats in team_stats.items())}

**Recent Achievements:**
{chr(10).join(f'- {member_id}: {", ".join(stats["latest_report"].achievements[:3])}' for member_id, stats in team_stats.items() if stats["latest_report"].achievements)}

**Improvement Areas:**
{chr(10).join(f'- {member_id}: {", ".join(stats["latest_report"].improvement_areas[:2])}' for member_id, stats in team_stats.items() if stats["latest_report"].improvement_areas)}

Please provide:
1. **Performance Trends:** Are team members improving, declining, or stable?
2. **Strengths Analysis:** What are each member's key strengths?
3. **Development Opportunities:** Where can each member improve?
4. **Team Dynamics:** How well is the team collaborating?
5. **Resource Allocation:** Should task assignments be adjusted based on performance?
6. **Training Recommendations:** What skills development would benefit the team?
7. **Recognition Opportunities:** Who deserves recognition for outstanding work?

Focus on constructive feedback and actionable development plans."""


@mcp.tool()
def notify_collaboration_start(
    task_id: str,
    ctx: Context,
    notify_all_members: bool = True,
    custom_message: str = ""
) -> Dict[str, Any]:
    """
    Send notifications to team members when a collaborative task is starting.

    Identifies all resources assigned to a task and creates notifications
    for task kickoff, coordination, and collaboration setup.
    """
    app_ctx = ctx.request_context.lifespan_context

    # Get task information
    task = None
    database = getattr(app_ctx, 'database', None)
    if database and not str(type(database)).startswith("<class 'unittest.mock."):
        try:
            task = database.get_task(task_id)
        except:
            pass

    if not task and hasattr(app_ctx, 'tasks'):
        task = app_ctx.tasks.get(task_id)

    if not task:
        return {
            "error": f"Task {task_id} not found",
            "task_id": task_id,
            "notification_status": "failed"
        }

    # Get collaboration plan
    collaboration_plan = None
    if hasattr(app_ctx, 'collaboration_plans'):
        collaboration_plan = app_ctx.collaboration_plans.get(task_id)

    # Get assigned resources
    assigned_resources = []
    if hasattr(app_ctx, 'assignments'):
        for t_id, resource_id in app_ctx.assignments.items():
            if t_id == task_id:
                if hasattr(app_ctx, 'resources') and resource_id in app_ctx.resources:
                    assigned_resources.append(app_ctx.resources[resource_id])

    # If no specific assignments, check collaboration plan
    if not assigned_resources and collaboration_plan:
        team_members = getattr(collaboration_plan, 'team_members', [])
        for member_id in team_members:
            if hasattr(app_ctx, 'resources') and member_id in app_ctx.resources:
                assigned_resources.append(app_ctx.resources[member_id])

    # Create notifications
    notifications = []
    task_name = getattr(task, 'name', 'Unknown Task')

    for resource in assigned_resources:
        resource_id = getattr(resource, 'id', '')
        resource_name = getattr(resource, 'name', 'Unknown Resource')

        notification = {
            "recipient_id": resource_id,
            "recipient_name": resource_name,
            "task_id": task_id,
            "task_name": task_name,
            "notification_type": "collaboration_start",
            "message": custom_message or f"Collaborative task '{task_name}' is starting. Please coordinate with your team members.",
            "timestamp": datetime.now().isoformat(),
            "priority": "high",
            "action_required": True,
            "collaboration_details": {
                "total_team_members": len(assigned_resources),
                "team_member_names": [getattr(r, 'name', 'Unknown') for r in assigned_resources],
                "coordination_needed": True
            }
        }
        notifications.append(notification)

    return {
        "task_id": task_id,
        "task_name": task_name,
        "notification_time": datetime.now().isoformat(),
        "total_notifications": len(notifications),
        "notifications": notifications,
        "collaboration_summary": {
            "team_size": len(assigned_resources),
            "notification_status": "sent" if notifications else "no_recipients",
            "requires_coordination": len(assigned_resources) > 1
        }
    }


@mcp.tool()
def monitor_priority_changes(
    ctx: Context,
    check_recent_hours: int = 24,
    notify_affected_resources: bool = True
) -> Dict[str, Any]:
    """
    Monitor and notify about task priority changes that affect resource assignments.

    Identifies tasks with recent priority changes and notifies affected team members
    about the impact on their workload and schedules.
    """
    app_ctx = ctx.request_context.lifespan_context

    # This is a simplified implementation - in a real system, you'd track priority change history
    # For now, we'll identify high-priority tasks and check if they might affect assignments

    all_tasks = []
    database = getattr(app_ctx, 'database', None)
    if database and not str(type(database)).startswith("<class 'unittest.mock."):
        try:
            all_tasks = database.get_all_tasks()
        except:
            pass

    if not all_tasks and hasattr(app_ctx, 'tasks'):
        all_tasks = list(app_ctx.tasks.values())

    # Identify high-priority tasks that might have changed recently
    priority_alerts = []
    current_time = datetime.now()

    for task in all_tasks:
        try:
            task_id = getattr(task, 'id', '')
            task_name = getattr(task, 'name', 'Unknown Task')
            priority = getattr(task, 'priority', 3)

            # Check if this is a high-priority task (1 or 2)
            if priority <= 2:
                # Find assigned resources
                assigned_resource_id = None
                if hasattr(app_ctx, 'assignments'):
                    assigned_resource_id = app_ctx.assignments.get(task_id)

                if assigned_resource_id and hasattr(app_ctx, 'resources'):
                    resource = app_ctx.resources.get(assigned_resource_id)
                    if resource:
                        alert = {
                            "task_id": task_id,
                            "task_name": task_name,
                            "priority": priority,
                            "priority_level": "urgent" if priority == 1 else "high",
                            "assigned_resource": {
                                "id": assigned_resource_id,
                                "name": getattr(resource, 'name', 'Unknown')
                            },
                            "alert_type": "high_priority_task",
                            "message": f"High priority task '{task_name}' requires immediate attention",
                            "timestamp": current_time.isoformat(),
                            "action_required": True
                        }
                        priority_alerts.append(alert)

        except (TypeError, AttributeError):
            continue

    # Sort by priority (urgent first)
    priority_alerts.sort(key=lambda a: a["priority"])

    return {
        "check_time": current_time.isoformat(),
        "monitoring_period_hours": check_recent_hours,
        "total_priority_alerts": len(priority_alerts),
        "alerts": priority_alerts,
        "summary": {
            "urgent_tasks": len([a for a in priority_alerts if a["priority_level"] == "urgent"]),
            "high_priority_tasks": len([a for a in priority_alerts if a["priority_level"] == "high"]),
            "affected_resources": len(set(a["assigned_resource"]["id"] for a in priority_alerts))
        }
    }


def create_server() -> FastMCP:
    """Create and return the configured MCP server"""
    return mcp


if __name__ == "__main__":
    # Run the server
    mcp.run()
