"""Project command & query handlers.

Contains the shared business logic for project operations.
Both CLI (via HTTP → API → handler) and Web (API → handler) use the same handlers.
"""

from __future__ import annotations

from pathlib import Path
from typing import Any

import structlog

from proxym.domain.events import (
    EventStore,
    ProjectCreated,
    ProjectUpdated,
    ProjectRemoved,
    ProjectScanned,
)
from proxym.domain.commands.project_commands import (
    AddProjectCommand,
    UpdateProjectCommand,
    RemoveProjectCommand,
    ScanDirectoryCommand,
)
from proxym.domain.queries.project_queries import (
    ListProjectsQuery,
    GetProjectQuery,
    GetProjectTicketsQuery,
)

logger = structlog.get_logger()


# ---------------------------------------------------------------------------
# Enrichment helper (extracted from _projects_api.py for reuse)
# ---------------------------------------------------------------------------

def enrich_project(project: Any) -> dict[str, Any]:
    """Add assignment counts to project payload: tickets, environments, tools, users.

    This is the single source of truth for project enrichment, used by
    both API responses and CLI output.
    """
    data = project.model_dump()
    data["project_key"] = project.name if not getattr(project, "source_path", "") else project.id
    pid = project.id
    pname = project.name

    # Tickets assigned to this project
    matched = []
    try:
        from proxym.dashboard._tickets_api import _tickets
        mgr = _tickets()
        all_tickets = mgr.list_tickets()
        matched = [t for t in all_tickets if t.project_id in (pid, pname, data["project_key"])]
        data["ticket_count"] = len(matched)
    except Exception:
        data["ticket_count"] = 0

    # Project-scoped tools
    try:
        explicit_tools = list(getattr(project, "tool_names", []) or [])
        if explicit_tools:
            data["assigned_tools"] = explicit_tools
        else:
            tools_used = sorted({
                t.assigned_tool.tool
                for t in matched
                if getattr(t, "assigned_tool", None) and getattr(t.assigned_tool, "tool", "")
            })
            data["assigned_tools"] = tools_used
    except Exception:
        data["assigned_tools"] = []

    # Project-scoped environments
    try:
        from proxym.dashboard._environments_api import get_environment_registry
        env_reg = get_environment_registry()
        envs = []
        for env_id in getattr(project, "environment_ids", []) or []:
            env = env_reg.get(env_id)
            if env:
                envs.append({
                    "id": env.id,
                    "name": env.name,
                    "kind": env.kind.value if hasattr(env.kind, "value") else str(env.kind),
                    "path": env.path,
                    "target": env.target_label(),
                })
        data["assigned_environments"] = envs
    except Exception:
        data["assigned_environments"] = []

    # Tool details
    try:
        from proxym.tools import ToolRegistry
        reg_tools = ToolRegistry.get()
        tools = []
        for tool_name in getattr(project, "tool_names", []) or []:
            tool = reg_tools.get_tool(tool_name)
            if tool:
                tools.append({
                    "name": tool.name,
                    "default_model": tool.default_model,
                    "default_instance": tool.default_instance,
                    "description": tool.description,
                })
            else:
                tools.append({"name": tool_name, "default_model": "", "default_instance": "", "description": ""})
        data["assigned_tool_details"] = tools
    except Exception:
        data["assigned_tool_details"] = []

    # LLMs
    try:
        data["assigned_llms"] = list(getattr(project, "llm_names", []) or [])
    except Exception:
        data["assigned_llms"] = []

    # Users
    try:
        from proxym.dashboard.users import UserManager
        user_mgr = UserManager.get()
        users = []
        for user_id in getattr(project, "user_ids", []) or []:
            user = user_mgr.get_user(user_id)
            if user:
                users.append({"id": user.id, "name": user.name or user.email or user.id, "email": user.email, "role": user.role})
        data["assigned_users"] = users
    except Exception:
        data["assigned_users"] = []

    return data


# ---------------------------------------------------------------------------
# Command Handler
# ---------------------------------------------------------------------------

class ProjectCommandHandler:
    """Handles project commands — the single write path for all project operations.

    Shared by both API endpoints and CLI (via API).
    Emits domain events for every state change.
    """

    def __init__(self, registry: Any, event_store: EventStore) -> None:
        self._registry = registry
        self._event_store = event_store

    async def handle(self, command: Any) -> dict[str, Any]:
        """Dispatch to the appropriate handler method."""
        if isinstance(command, AddProjectCommand):
            return await self._handle_add(command)
        elif isinstance(command, UpdateProjectCommand):
            return await self._handle_update(command)
        elif isinstance(command, RemoveProjectCommand):
            return await self._handle_remove(command)
        elif isinstance(command, ScanDirectoryCommand):
            return await self._handle_scan(command)
        else:
            raise ValueError(f"Unknown command: {type(command).__name__}")

    async def _handle_add(self, cmd: AddProjectCommand) -> dict[str, Any]:
        """Add a project — unified logic for all source types."""
        from proxym.projects import Project, SourceType
        from proxym.config import get_settings

        reg = self._registry

        if cmd.path:
            path = str(Path(cmd.path).expanduser().resolve())
            project = Project(
                name=cmd.name or Path(path).name,
                source_type=SourceType.local,
                source_path=path,
                local_path=path,
                default_tool=cmd.default_tool,
                description=cmd.description,
                environment_ids=cmd.environment_ids,
                tool_names=cmd.tool_names,
                llm_names=cmd.llm_names,
                user_ids=cmd.user_ids,
            )
            result = enrich_project(reg.add(project))

        elif cmd.git_url:
            settings = get_settings()
            clone_to = cmd.clone_to or settings.projects_root or "~/github"
            result = enrich_project(reg.add_git_repo(cmd.git_url, clone_to=clone_to))

        elif cmd.ssh_host and cmd.ssh_path:
            result = enrich_project(reg.add_ssh_project(
                host=cmd.ssh_host, path=cmd.ssh_path,
                user=cmd.ssh_user, ssh_key=cmd.ssh_key, name=cmd.name,
            ))

        elif cmd.ftp_url:
            name = cmd.name or cmd.ftp_url.rsplit("/", 1)[-1]
            project = Project(
                name=name, source_type=SourceType.ftp,
                source_path=cmd.ftp_url, description=cmd.description,
                default_tool=cmd.default_tool,
                environment_ids=cmd.environment_ids,
                tool_names=cmd.tool_names,
                llm_names=cmd.llm_names,
                user_ids=cmd.user_ids,
            )
            result = enrich_project(reg.add(project))

        elif cmd.name:
            project = Project(
                name=cmd.name,
                source_type=SourceType.local,
                source_path="",
                local_path="",
                default_tool=cmd.default_tool,
                description=cmd.description,
                environment_ids=cmd.environment_ids,
                tool_names=cmd.tool_names,
                llm_names=cmd.llm_names,
                user_ids=cmd.user_ids,
            )
            result = enrich_project(reg.add(project))

        else:
            raise ValueError("Provide at least 'name'.")

        # Emit event
        self._event_store.append(ProjectCreated(
            aggregate_id=result.get("id", ""),
            data={
                "name": result.get("name", ""),
                "source_type": result.get("source_type", ""),
                "source_path": result.get("source_path", ""),
            },
            actor=cmd.actor,
        ))

        return result

    async def _handle_update(self, cmd: UpdateProjectCommand) -> dict[str, Any]:
        """Update a project."""
        updates = {}
        for field_name in ("name", "default_tool", "description", "status",
                           "environment_ids", "tool_names", "llm_names", "user_ids"):
            val = getattr(cmd, field_name, None)
            if val is not None:
                updates[field_name] = val

        result = self._registry.update(cmd.project_id, updates)
        if not result:
            raise ValueError(f"Project {cmd.project_id} not found")

        enriched = enrich_project(result)

        self._event_store.append(ProjectUpdated(
            aggregate_id=cmd.project_id,
            data={"updates": updates},
            actor=cmd.actor,
        ))

        return enriched

    async def _handle_remove(self, cmd: RemoveProjectCommand) -> dict[str, str]:
        """Remove a project."""
        if not self._registry.remove(cmd.project_id):
            raise ValueError(f"Project {cmd.project_id} not found")

        self._event_store.append(ProjectRemoved(
            aggregate_id=cmd.project_id,
            actor=cmd.actor,
        ))

        return {"status": "deleted"}

    async def _handle_scan(self, cmd: ScanDirectoryCommand) -> dict[str, Any]:
        """Scan a directory for projects."""
        added = self._registry.scan_directory(cmd.directory)

        self._event_store.append(ProjectScanned(
            aggregate_id=cmd.directory,
            data={
                "scanned": cmd.directory,
                "added_count": len(added),
                "project_names": [p.name for p in added],
            },
            actor=cmd.actor,
        ))

        return {
            "scanned": cmd.directory,
            "added": len(added),
            "projects": [p.model_dump() for p in added],
        }


# ---------------------------------------------------------------------------
# Query Handler
# ---------------------------------------------------------------------------

class ProjectQueryHandler:
    """Handles project queries — the single read path for all project data.

    Shared by both API endpoints and CLI (via API).
    Read-only, never emits events.
    """

    def __init__(self, registry: Any) -> None:
        self._registry = registry

    async def handle(self, query: Any) -> Any:
        """Dispatch to the appropriate query handler."""
        if isinstance(query, ListProjectsQuery):
            return await self._handle_list(query)
        elif isinstance(query, GetProjectQuery):
            return await self._handle_get(query)
        elif isinstance(query, GetProjectTicketsQuery):
            return await self._handle_tickets(query)
        else:
            raise ValueError(f"Unknown query: {type(query).__name__}")

    async def _handle_list(self, query: ListProjectsQuery) -> list[dict[str, Any]]:
        """List projects with optional filters."""
        from proxym.projects import ProjectStatus, SourceType

        s = ProjectStatus(query.status) if query.status else None
        st = SourceType(query.source_type) if query.source_type else None
        projects = self._registry.list_projects(status=s, source_type=st)
        return [enrich_project(p) for p in projects]

    async def _handle_get(self, query: GetProjectQuery) -> dict[str, Any] | None:
        """Get a single project by ID or name."""
        project = self._registry.get(query.project_id)
        if not project:
            project = self._registry.find_by_name(query.project_id)
        if not project:
            return None
        return enrich_project(project)

    async def _handle_tickets(self, query: GetProjectTicketsQuery) -> list[dict[str, Any]]:
        """Get tickets associated with a project."""
        from proxym.dashboard._tickets_api import _tickets

        project = self._registry.get(query.project_id)
        if not project:
            project = self._registry.find_by_name(query.project_id)
        match_values = {query.project_id}
        if project:
            match_values.add(project.id)
            match_values.add(project.name)
            pk = project.name if not getattr(project, "source_path", "") else project.id
            match_values.add(pk)

        mgr = _tickets()
        all_tickets = mgr.list_tickets()
        return [
            t.summary() for t in all_tickets
            if getattr(t, "project_id", None) in match_values
        ]
