"""Project management API endpoints."""

from __future__ import annotations

from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import FileResponse
from pydantic import BaseModel

router = APIRouter(prefix="/projects", tags=["projects"])


class ProjectCreateRequest(BaseModel):
    title: str
    description: str = ""
    target_date: str = ""
    related_goals: list[str] = []
    assigned_agents: list[str] = []
    linked_kbs: list[str] = []


class ProjectRenameRequest(BaseModel):
    title: str


class ProjectUpdateRequest(BaseModel):
    title: str | None = None
    description: str | None = None
    target_date: str | None = None
    related_goals: list[str] | None = None
    assigned_agents: list[str] | None = None
    linked_kbs: list[str] | None = None


class StatusRequest(BaseModel):
    status: str


class MilestoneRequest(BaseModel):
    milestone: str


class LogRequest(BaseModel):
    note: str


@router.get("")
async def list_projects(request: Request, status: str | None = None) -> list[dict]:
    """List all projects."""
    svc = request.app.state.project_service
    return svc.list_projects(status_filter=status)


@router.post("")
async def create_project(request: Request, body: ProjectCreateRequest) -> dict:
    """Create a new project."""
    svc = request.app.state.project_service
    path = svc.create_project(
        title=body.title,
        description=body.description,
        target_date=body.target_date,
        related_goals=body.related_goals,
        assigned_agents=body.assigned_agents,
        linked_kbs=body.linked_kbs,
    )
    return {"path": path, "status": "active"}


@router.get("/linkable")
async def list_linkable(request: Request) -> dict:
    """List goals, subagents, and KBs that can be linked to projects."""
    goals = []
    try:
        coach = request.app.state.coach
        goals = [
            {"slug": g["slug"], "title": g["title"], "status": g["status"]}
            for g in coach.list_goals()
        ]
    except Exception:
        pass

    agents = []
    try:
        orchestrator = request.app.state.orchestrator
        agents = orchestrator.list_agents()
    except Exception:
        pass

    kbs = []
    try:
        kb_service = request.app.state.kb_service
        kbs = kb_service.list_kbs()
    except Exception:
        pass

    return {"goals": goals, "agents": agents, "kbs": kbs}


@router.get("/{slug}")
async def get_project(request: Request, slug: str) -> dict:
    """Get a project's full detail."""
    svc = request.app.state.project_service
    detail = svc.get_project_detail(slug)
    if not detail:
        raise HTTPException(status_code=404, detail=f"Project not found: {slug}")
    return detail


@router.put("/{slug}")
async def update_project(request: Request, slug: str, body: ProjectUpdateRequest) -> dict:
    """Update a project's fields."""
    svc = request.app.state.project_service
    existing = svc.get_project_detail(slug)
    if not existing:
        raise HTTPException(status_code=404, detail=f"Project not found: {slug}")

    svc.update_project(
        slug=slug,
        title=body.title,
        description=body.description,
        target_date=body.target_date,
        related_goals=body.related_goals,
        assigned_agents=body.assigned_agents,
        linked_kbs=body.linked_kbs,
    )

    # Emit user_override events when linking fields change — so the agent sees
    # the user manually adjusted associations it may have proposed.
    activity = getattr(request.app.state, "activity_logger", None)
    if activity:
        for field, new_val in (
            ("related_goals", body.related_goals),
            ("assigned_agents", body.assigned_agents),
            ("linked_kbs", body.linked_kbs),
        ):
            if new_val is None:
                continue
            old_val = list(existing.get(field, []) or [])
            added = [x for x in new_val if x not in old_val]
            removed = [x for x in old_val if x not in new_val]
            if not added and not removed:
                continue
            activity.log(
                category="user_override",
                action=f"{field}_changed",
                summary=(
                    f"User changed {field} on project '{slug}': "
                    f"+{added or '[]'} -{removed or '[]'}"
                ),
                details={"project": slug, "field": field, "added": added, "removed": removed},
                importance="medium",
            )
    return {"slug": slug, "status": "updated"}


@router.get("/{slug}/tasks")
async def list_project_tasks(request: Request, slug: str) -> list[dict]:
    """List all tasks filed under a project."""
    task_svc = request.app.state.task_service
    return task_svc.list_tasks(project=slug)


@router.patch("/{slug}/status")
async def update_status(request: Request, slug: str, body: StatusRequest) -> dict:
    """Update a project's status."""
    svc = request.app.state.project_service
    try:
        svc.update_status(slug, body.status)
    except FileNotFoundError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    return {"slug": slug, "status": body.status}


@router.post("/{slug}/milestones")
async def add_milestone(request: Request, slug: str, body: MilestoneRequest) -> dict:
    """Add a milestone to a project."""
    svc = request.app.state.project_service
    if not svc.get_project(slug):
        raise HTTPException(status_code=404, detail=f"Project not found: {slug}")
    svc.add_milestone(slug, body.milestone)
    return {"slug": slug, "status": "milestone_added"}


@router.post("/{slug}/log")
async def add_log_entry(request: Request, slug: str, body: LogRequest) -> dict:
    """Add a log entry to a project."""
    svc = request.app.state.project_service
    if not svc.get_project(slug):
        raise HTTPException(status_code=404, detail=f"Project not found: {slug}")
    svc.add_log(slug, body.note)
    return {"slug": slug, "status": "logged"}


@router.post("/{slug}/rename")
async def rename_project(request: Request, slug: str, body: ProjectRenameRequest) -> dict:
    """Rename a project: move its folder, update title, rewrite task refs."""
    svc = request.app.state.project_service
    try:
        result = svc.rename_project(slug, body.title)
    except FileNotFoundError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc

    activity = getattr(request.app.state, "activity_logger", None)
    if activity and result["slug"] != slug:
        activity.log(
            category="user_override",
            action="project_renamed",
            summary=f"User renamed project '{slug}' → '{result['slug']}' ('{body.title}')",
            details={"old_slug": slug, "new_slug": result["slug"], "tasks_updated": result.get("tasks_updated", 0)},
            importance="medium",
        )
    return {"slug": result["slug"], "tasks_updated": result.get("tasks_updated", 0)}


@router.get("/{slug}/output")
async def list_output_files(request: Request, slug: str) -> list[dict]:
    """List files in a project's output/ directory."""
    svc = request.app.state.project_service
    if not svc.get_project(slug):
        raise HTTPException(status_code=404, detail=f"Project not found: {slug}")
    return svc.list_output_files(slug)


@router.get("/{slug}/output/{filename}")
async def download_output_file(request: Request, slug: str, filename: str) -> FileResponse:
    """Download a file from a project's output/ directory."""
    svc = request.app.state.project_service
    if not svc.get_project(slug):
        raise HTTPException(status_code=404, detail=f"Project not found: {slug}")

    out_dir = svc._resolve(f"projects/{slug}/output")  # noqa: SLF001
    file_path = out_dir / filename

    if not file_path.exists() or not file_path.is_file():
        raise HTTPException(status_code=404, detail=f"File not found: {filename}")

    # Security: ensure the resolved path is inside the output dir
    try:
        file_path.resolve().relative_to(out_dir.resolve())
    except ValueError as exc:
        raise HTTPException(status_code=403, detail="Access denied") from exc

    return FileResponse(
        path=str(file_path),
        filename=filename,
        media_type="application/octet-stream",
    )


class IngestOutputRequest(BaseModel):
    filename: str
    kb: str


@router.post("/{slug}/output/ingest")
async def ingest_output_file(request: Request, slug: str, body: IngestOutputRequest) -> dict:
    """Copy an output file's companion .md into a KB inbox for ingestion."""
    import shutil

    svc = request.app.state.project_service
    if not svc.get_project(slug):
        raise HTTPException(status_code=404, detail=f"Project not found: {slug}")

    out_dir = svc._resolve(f"projects/{slug}/output")  # noqa: SLF001
    source = out_dir / body.filename

    # Prefer companion .md for ingestion
    if source.suffix != ".md":
        companion = source.with_suffix(source.suffix + ".md")
        if companion.exists():
            source = companion
    if not source.exists():
        raise HTTPException(status_code=404, detail=f"File not found: {body.filename}")

    kb_inbox = svc._resolve(f"knowledge/{body.kb}/inbox")  # noqa: SLF001
    if not kb_inbox.parent.exists():
        raise HTTPException(status_code=404, detail=f"Knowledge base '{body.kb}' does not exist")
    kb_inbox.mkdir(parents=True, exist_ok=True)

    dest = kb_inbox / source.name
    shutil.copy2(str(source), str(dest))

    # Flip ingested flag on the companion
    companion_path = out_dir / (body.filename + ".md") if not body.filename.endswith(".md") else out_dir / body.filename
    if companion_path.exists():
        try:
            text = companion_path.read_text(encoding="utf-8")
            text = text.replace("ingested: false", "ingested: true")
            companion_path.write_text(text, encoding="utf-8")
        except Exception:
            pass

    return {"ingested": True, "kb": body.kb, "file": source.name}


@router.delete("/{slug}")
async def delete_project(request: Request, slug: str) -> dict:
    """Delete a project."""
    svc = request.app.state.project_service
    if not svc.get_project(slug):
        raise HTTPException(status_code=404, detail=f"Project not found: {slug}")
    svc.delete_project(slug)
    return {"slug": slug, "status": "deleted"}
