"""REST endpoints for project management.

Delegates to domain CQRS handlers for business logic.
Both this API layer and CLI use the same command/query buses,
ensuring consistent behavior across all interfaces.
"""
from __future__ import annotations

from pathlib import Path
from typing import Any

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field, ValidationError

from proxym.config import get_settings
from proxym.observability import log_call
from proxym.projects import Project, SourceType
from proxym.storage import DEFAULT_SQLITE_PATH

router = APIRouter(tags=["projects"])


# --- Lazy singleton ---

_registry = None


def _get_registry():
    global _registry
    if _registry is None:
        from proxym.projects import ProjectRegistry
        persist = DEFAULT_SQLITE_PATH
        _registry = ProjectRegistry(persist_path=persist)
        # Auto-scan default directory
        settings = get_settings()
        root = settings.projects_root
        if root:
            _registry.scan_directory(root)
    if _registry is not None:
        _seed_workspace_project(_registry)
    return _registry


# --- CQRS buses (lazy) ---

def _cmd_bus():
    from proxym.domain.setup import get_command_bus
    return get_command_bus()


def _qry_bus():
    from proxym.domain.setup import get_query_bus
    return get_query_bus()


# --- Request models ---

class AddProjectReq(BaseModel):
    """Dodaj projekt — jedno z: path, git_url, ssh."""
    name: str | None = None
    path: str | None = None          # local: ~/github/my-app
    git_url: str | None = None       # git: https://github.com/user/repo.git
    clone_to: str | None = None      # gdzie klonować (domyślnie projects_root)
    ssh_host: str | None = None      # SSH host
    ssh_path: str | None = None      # SSH remote path
    ssh_user: str = "root"
    ssh_key: str | None = None
    ftp_url: str | None = None       # ftp://host/path
    default_tool: str | None = None
    description: str = ""
    environment_ids: list[str] = Field(default_factory=list)
    tool_names: list[str] = Field(default_factory=list)
    llm_names: list[str] = Field(default_factory=list)
    user_ids: list[str] = Field(default_factory=list)


class UpdateProjectReq(BaseModel):
    name: str | None = None
    default_tool: str | None = None
    description: str | None = None
    status: str | None = None
    environment_ids: list[str] | None = None
    tool_names: list[str] | None = None
    llm_names: list[str] | None = None
    user_ids: list[str] | None = None


class ScanReq(BaseModel):
    directory: str                    # folder do skanowania


# --- Helpers (kept for backward compat, delegates to domain) ---

def _enrich_project(project) -> dict[str, Any]:
    """Add assignment counts to project payload.

    Delegates to the domain handler's enrich_project for the single source of truth.
    """
    from proxym.domain.handlers.project_handler import enrich_project
    return enrich_project(project)


def _get_project(project_id: str):
    reg = _get_registry()
    project = reg.get(project_id)
    if not project:
        project = reg.find_by_name(project_id)
    return project


def _logical_project_key(project) -> str:
    return project.name if not getattr(project, "source_path", "") else project.id


def _seed_workspace_project(registry) -> None:
    """Ensure the active proxym workspace is represented in the registry.

    Users are auto-discovered from the current git repository, while projects
    are usually persisted in the registry. When the registry has not been
    populated yet, the current workspace would otherwise look empty in the
    dashboard even though the repo is active.
    """
    settings = get_settings()
    candidate_paths = []

    watch_dir = getattr(settings, "watch_dir", "")
    if watch_dir:
        candidate_paths.append(Path(watch_dir).expanduser())

    candidate_paths.append(Path(__file__).resolve().parents[3])

    for workspace_root in candidate_paths:
        try:
            resolved_root = workspace_root.resolve()
        except Exception:
            resolved_root = workspace_root

        if not resolved_root.exists() or not resolved_root.is_dir():
            continue
        if registry.find_by_path(str(resolved_root)):
            return

        registry.add(Project(
            name=resolved_root.name or "workspace",
            source_type=SourceType.local,
            source_path=str(resolved_root),
            local_path=str(resolved_root),
        ))
        return


# --- Endpoints (delegate to CQRS buses) ---

@router.get("/projects")
async def list_projects(
    status: str | None = None,
    source_type: str | None = None,
) -> list[dict[str, Any]]:
    """Lista wszystkich projektów z informacją o przypisanych obiektach."""
    from proxym.domain.queries import ListProjectsQuery
    return await _qry_bus().dispatch(ListProjectsQuery(
        status=status,
        source_type=source_type,
    ))


@router.get("/projects/{project_id}")
async def get_project(project_id: str) -> dict[str, Any]:
    from proxym.domain.queries import GetProjectQuery
    result = await _qry_bus().dispatch(GetProjectQuery(project_id=project_id))
    if not result:
        raise HTTPException(404, f"Project {project_id} not found")
    return result


@router.post("/projects")
@log_call(level="info")
async def add_project(req: AddProjectReq) -> dict[str, Any]:
    """Dodaj projekt — po nazwie lub z pełną konfiguracją.

    Delegates to AddProjectCommand via the CommandBus (CQRS).
    """
    from proxym.domain.commands import AddProjectCommand
    cmd = AddProjectCommand(
        actor="api",
        name=req.name,
        path=req.path,
        git_url=req.git_url,
        clone_to=req.clone_to,
        ssh_host=req.ssh_host,
        ssh_path=req.ssh_path,
        ssh_user=req.ssh_user,
        ssh_key=req.ssh_key,
        ftp_url=req.ftp_url,
        default_tool=req.default_tool,
        description=req.description,
        environment_ids=req.environment_ids,
        tool_names=req.tool_names,
        llm_names=req.llm_names,
        user_ids=req.user_ids,
    )
    try:
        return await _cmd_bus().dispatch(cmd)
    except ValueError as e:
        raise HTTPException(400, str(e))


@router.put("/projects/{project_id}")
async def update_project(project_id: str, req: UpdateProjectReq) -> dict[str, Any]:
    from proxym.domain.commands import UpdateProjectCommand
    cmd = UpdateProjectCommand(
        actor="api",
        project_id=project_id,
        name=req.name,
        default_tool=req.default_tool,
        description=req.description,
        status=req.status,
        environment_ids=req.environment_ids,
        tool_names=req.tool_names,
        llm_names=req.llm_names,
        user_ids=req.user_ids,
    )
    try:
        return await _cmd_bus().dispatch(cmd)
    except ValueError:
        raise HTTPException(404)


@router.delete("/projects/{project_id}")
async def remove_project(project_id: str) -> dict[str, str]:
    from proxym.domain.commands import RemoveProjectCommand
    try:
        return await _cmd_bus().dispatch(RemoveProjectCommand(
            actor="api", project_id=project_id,
        ))
    except ValueError:
        raise HTTPException(404)


@router.post("/projects/scan")
@log_call(level="info")
async def scan_directory(req: ScanReq) -> dict[str, Any]:
    """Skanuj folder i dodaj znalezione projekty."""
    from proxym.domain.commands import ScanDirectoryCommand
    return await _cmd_bus().dispatch(ScanDirectoryCommand(
        actor="api", directory=req.directory,
    ))


@router.get("/projects/{project_id}/tickets")
async def project_tickets(project_id: str) -> list[dict[str, Any]]:
    """Tickety powiązane z projektem (match by id or name)."""
    from proxym.domain.queries import GetProjectTicketsQuery
    return await _qry_bus().dispatch(GetProjectTicketsQuery(
        project_id=project_id,
    ))


@router.post("/projects/{project_id}/tickets")
async def create_project_ticket(project_id: str, req: dict[str, Any]) -> dict[str, Any]:
    """Stwórz ticket od razu powiązany z projektem."""
    # Ensure project exists
    project = _get_project(project_id)
    if not project:
        raise HTTPException(404, f"Project {project_id} not found")

    from proxym.dashboard._tickets_api import CreateTicketReq, create_ticket

    payload = dict(req or {})
    payload["project_id"] = _logical_project_key(project)
    try:
        ticket_req = CreateTicketReq(**payload)
    except ValidationError as e:
        # Request body validation errors should not surface as 500.
        raise HTTPException(422, {"detail": e.errors()})

    # Call create_ticket with validated/normalized types.
    return await create_ticket(**ticket_req.model_dump())
