"""
Project Manager

High-level interface for managing projects, their agents, and state.
"""

import os
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import List, Dict, Optional, Any

from ..persistence.database import OrchestratorDB
from ..intelligence.domain_analyzer import TaskDomain, DomainAnalysis
from ..agents.templates import (
    AgentTemplate,
    AgentTeamComposition,
    get_template,
    compose_team_for_task,
)


@dataclass
class Project:
    """Represents a project with its metadata and agents."""

    id: str
    name: str
    domain: TaskDomain
    description: Optional[str] = None
    secondary_domains: List[TaskDomain] = field(default_factory=list)
    workspace_path: Optional[str] = None
    status: str = "active"
    planning_complete: bool = False
    planning_notes: Optional[Dict[str, Any]] = None
    total_tasks: int = 0
    completed_tasks: int = 0
    created_at: Optional[datetime] = None
    last_accessed_at: Optional[datetime] = None

    # Associated agents (populated separately)
    agents: List[Dict[str, Any]] = field(default_factory=list)
    context: Dict[str, Any] = field(default_factory=dict)

    @classmethod
    def from_db_row(cls, row: Dict[str, Any]) -> "Project":
        """Create a Project from a database row."""
        secondary = row.get("secondary_domains", [])
        if isinstance(secondary, str):
            import json
            secondary = json.loads(secondary) if secondary else []

        return cls(
            id=row["id"],
            name=row["name"],
            domain=TaskDomain(row["domain"]) if row.get("domain") else TaskDomain.GENERAL,
            description=row.get("description"),
            secondary_domains=[TaskDomain(d) for d in secondary],
            workspace_path=row.get("workspace_path"),
            status=row.get("status", "active"),
            planning_complete=bool(row.get("planning_complete", False)),
            planning_notes=row.get("planning_notes"),
            total_tasks=row.get("total_tasks", 0),
            completed_tasks=row.get("completed_tasks", 0),
            created_at=row.get("created_at"),
            last_accessed_at=row.get("last_accessed_at"),
        )

    @property
    def progress_percentage(self) -> float:
        """Calculate completion percentage."""
        if self.total_tasks == 0:
            return 0.0
        return (self.completed_tasks / self.total_tasks) * 100


@dataclass
class ProjectAgent:
    """Represents an agent assigned to a project."""

    template_id: str
    role: str
    status: str
    template: Optional[AgentTemplate] = None
    instance_id: Optional[str] = None
    tasks_completed: int = 0
    tokens_used: int = 0


class ProjectManager:
    """
    Manages project lifecycle, agents, and state.

    Provides high-level operations for:
    - Creating and configuring projects
    - Assigning agents to projects
    - Tracking project progress
    - Session resume functionality
    """

    def __init__(self, db: OrchestratorDB, workspace_root: Optional[Path] = None):
        """
        Initialize the project manager.

        Args:
            db: Database instance for persistence
            workspace_root: Root directory for project workspaces
        """
        self.db = db
        self.workspace_root = workspace_root or Path.home() / ".orchestrator" / "projects"
        self._current_project_id: Optional[str] = None

    @property
    def current_project(self) -> Optional[Project]:
        """Get the currently active project."""
        if self._current_project_id:
            return self.get_project(self._current_project_id)
        return None

    def create_project(
        self,
        name: str,
        analysis: DomainAnalysis,
        description: Optional[str] = None,
        create_workspace: bool = True,
    ) -> Project:
        """
        Create a new project based on task analysis.

        Args:
            name: Project name
            analysis: Domain analysis result
            description: Optional project description
            create_workspace: Whether to create a workspace directory

        Returns:
            The created Project
        """
        # Generate project ID
        project_id = self.db.generate_project_id(name)

        # Determine workspace path
        workspace_path = None
        if create_workspace:
            workspace_path = str(self.workspace_root / project_id)
            os.makedirs(workspace_path, exist_ok=True)

        # Extract domains
        secondary_domains = [
            d.domain.value
            for d in analysis.all_domains[1:]
            if d.confidence >= 0.4
        ]

        # Create in database
        self.db.create_project(
            project_id=project_id,
            name=name,
            domain=analysis.primary_domain.value,
            description=description or f"Project for: {analysis.task_text[:100]}",
            secondary_domains=secondary_domains,
            workspace_path=workspace_path,
        )

        # Store original task as context
        self.db.set_project_context(project_id, "original_task", analysis.task_text)
        self.db.set_project_context(project_id, "domain_analysis", {
            "primary_domain": analysis.primary_domain.value,
            "primary_confidence": analysis.primary_confidence,
            "complexity": analysis.complexity,
            "suggested_team_size": analysis.suggested_team_size,
        })

        # Set as current project
        self._current_project_id = project_id

        return self.get_project(project_id)

    def get_project(self, project_id: str) -> Optional[Project]:
        """
        Get a project by ID with all associated data.

        Args:
            project_id: Project identifier

        Returns:
            Project with agents and context loaded, or None
        """
        row = self.db.get_project(project_id)
        if not row:
            return None

        project = Project.from_db_row(row)

        # Load agents
        project.agents = self.db.get_project_agents(project_id)

        # Load context
        project.context = self.db.get_all_project_context(project_id)

        return project

    def list_recent_projects(self, limit: int = 10) -> List[Project]:
        """Get recently accessed projects."""
        rows = self.db.get_recent_projects(limit)
        return [Project.from_db_row(row) for row in rows]

    def list_active_projects(self) -> List[Project]:
        """Get all active projects."""
        rows = self.db.get_active_projects()
        return [Project.from_db_row(row) for row in rows]

    def resume_project(self, project_id: str) -> Project:
        """
        Resume a project, making it the current project.

        Args:
            project_id: Project to resume

        Returns:
            The resumed Project

        Raises:
            ValueError: If project not found
        """
        project = self.get_project(project_id)
        if not project:
            raise ValueError(f"Project not found: {project_id}")

        # Update access time
        self.db.update_project_accessed(project_id)

        # Record resume event
        self.db.add_project_event(project_id, "resumed", {
            "status": project.status,
            "progress": f"{project.completed_tasks}/{project.total_tasks}",
        })

        # Set as current
        self._current_project_id = project_id

        return self.get_project(project_id)

    def assign_team(
        self,
        project_id: str,
        team: AgentTeamComposition,
    ) -> List[ProjectAgent]:
        """
        Assign a team of agents to a project.

        Args:
            project_id: Target project
            team: Team composition from planner

        Returns:
            List of assigned ProjectAgents
        """
        assigned = []

        # Add lead agent
        self.db.add_project_agent(
            project_id=project_id,
            agent_template_id=team.lead.id,
            role="lead",
        )
        assigned.append(ProjectAgent(
            template_id=team.lead.id,
            role="lead",
            status="planned",
            template=team.lead,
        ))

        # Add specialists
        for specialist in team.specialists:
            self.db.add_project_agent(
                project_id=project_id,
                agent_template_id=specialist.id,
                role="specialist",
            )
            assigned.append(ProjectAgent(
                template_id=specialist.id,
                role="specialist",
                status="planned",
                template=specialist,
            ))

        # Add reviewers
        for reviewer in team.reviewers:
            self.db.add_project_agent(
                project_id=project_id,
                agent_template_id=reviewer.id,
                role="reviewer",
            )
            assigned.append(ProjectAgent(
                template_id=reviewer.id,
                role="reviewer",
                status="planned",
                template=reviewer,
            ))

        # Add support
        for support in team.support:
            self.db.add_project_agent(
                project_id=project_id,
                agent_template_id=support.id,
                role="support",
            )
            assigned.append(ProjectAgent(
                template_id=support.id,
                role="support",
                status="planned",
                template=support,
            ))

        return assigned

    def get_project_agents(self, project_id: str) -> List[ProjectAgent]:
        """
        Get all agents assigned to a project.

        Args:
            project_id: Project identifier

        Returns:
            List of ProjectAgents with templates loaded
        """
        rows = self.db.get_project_agents(project_id)
        agents = []

        for row in rows:
            template = get_template(row["agent_template_id"])
            agents.append(ProjectAgent(
                template_id=row["agent_template_id"],
                role=row["role"],
                status=row["status"],
                template=template,
                instance_id=row.get("agent_instance_id"),
                tasks_completed=row.get("tasks_completed", 0),
                tokens_used=row.get("tokens_used", 0),
            ))

        return agents

    def activate_agent(
        self,
        project_id: str,
        template_id: str,
        instance_id: str,
    ) -> None:
        """
        Link an actual agent instance to a project agent slot.

        Args:
            project_id: Project identifier
            template_id: Agent template ID
            instance_id: Actual agent instance ID
        """
        self.db.link_project_agent_instance(project_id, template_id, instance_id)

    def complete_planning(
        self,
        project_id: str,
        notes: Dict[str, Any],
    ) -> None:
        """
        Mark a project's planning phase as complete.

        Args:
            project_id: Project identifier
            notes: Planning decisions and notes
        """
        self.db.update_project_planning(project_id, True, notes)
        self.db.add_project_event(project_id, "planning_complete", notes)

    def update_progress(
        self,
        project_id: str,
        total_tasks: int,
        completed_tasks: int,
    ) -> None:
        """Update a project's progress counts."""
        self.db.update_project_progress(project_id, total_tasks, completed_tasks)

    def set_context(self, project_id: str, key: str, value: Any) -> None:
        """Set a context value for the project."""
        self.db.set_project_context(project_id, key, value)

    def get_context(self, project_id: str, key: str) -> Optional[Any]:
        """Get a context value from the project."""
        return self.db.get_project_context(project_id, key)

    def add_file(
        self,
        project_id: str,
        file_path: str,
        file_type: str = "source",
        description: Optional[str] = None,
    ) -> None:
        """Track a file as part of the project."""
        self.db.add_project_file(project_id, file_path, file_type, description)

    def get_files(
        self,
        project_id: str,
        file_type: Optional[str] = None,
    ) -> List[Dict[str, Any]]:
        """Get files tracked in the project."""
        return self.db.get_project_files(project_id, file_type)

    def archive_project(self, project_id: str) -> None:
        """Archive a project."""
        self.db.update_project_status(project_id, "archived")
        self.db.add_project_event(project_id, "archived", None)
        if self._current_project_id == project_id:
            self._current_project_id = None

    def complete_project(self, project_id: str) -> None:
        """Mark a project as completed."""
        self.db.update_project_status(project_id, "completed")
        self.db.add_project_event(project_id, "completed", None)

    def get_history(self, project_id: str, limit: int = 50) -> List[Dict[str, Any]]:
        """Get project history events."""
        return self.db.get_project_history(project_id, limit)

    def format_project_summary(self, project: Project) -> str:
        """
        Format a human-readable project summary.

        Args:
            project: Project to summarize

        Returns:
            Formatted string summary
        """
        lines = [
            f"Project: {project.name}",
            f"ID: {project.id}",
            f"Domain: {project.domain.value}",
            f"Status: {project.status}",
        ]

        if project.description:
            lines.append(f"Description: {project.description[:100]}...")

        if project.total_tasks > 0:
            lines.append(
                f"Progress: {project.completed_tasks}/{project.total_tasks} "
                f"({project.progress_percentage:.0f}%)"
            )

        if project.workspace_path:
            lines.append(f"Workspace: {project.workspace_path}")

        if project.agents:
            lines.append(f"Team: {len(project.agents)} agent(s)")
            for agent in project.agents:
                template = get_template(agent["agent_template_id"])
                name = template.name if template else agent["agent_template_id"]
                lines.append(f"  - {name} ({agent['role']})")

        return "\n".join(lines)


# Global project manager instance
_manager: Optional[ProjectManager] = None


def get_project_manager(db: Optional[OrchestratorDB] = None) -> ProjectManager:
    """
    Get or create the global project manager.

    Args:
        db: Database instance (required on first call)

    Returns:
        ProjectManager instance
    """
    global _manager
    if _manager is None:
        if db is None:
            raise ValueError("Database required for first initialization")
        _manager = ProjectManager(db)
    return _manager


def set_project_manager(manager: ProjectManager) -> None:
    """Set the global project manager instance."""
    global _manager
    _manager = manager
