"""
Task-related routes: Task, comment, board creation, listing, and management.
Data is stored in the repo ODB/RefDB via distgit.tasks (Board, Task, Comment).
"""
from __future__ import annotations

import time
from typing import Any
from urllib.parse import quote

import pygit2
from fastapi import APIRouter, HTTPException, Query, Request
from fastapi.responses import JSONResponse

from distgit.tasks import Board, Comment, Task, get_board, get_comment, get_task, get_task_by_oid
from pygitweb.config import DISTGIT_AUTH, EXPORT_OK, PROJECTROOT, STRICT_EXPORT
from pygitweb.git_helpers import open_repo, git_get_references
from pygitweb.projects import git_get_projects_list
from pygitweb.validation import is_valid_project

BOARD_REF_PREFIX = "refs/boards/"
TASK_REF_PREFIX = "refs/tasks/"
# Well-known empty tree OID for boards/tasks when repo has no HEAD
EMPTY_TREE_OID = pygit2.Oid(hex="4b825dc642cb6eb9a060e54bf8d69288fbee4904")


def _require_auth_disabled() -> None:
    """Raise 401 if authentication is enabled (we will add real auth later)."""
    if DISTGIT_AUTH != "None":
        raise HTTPException(status_code=401, detail="Authentication required")


def _project_in_list(project: str) -> bool:
    lst = git_get_projects_list(
        filter_path="",
        paranoid=STRICT_EXPORT,
        export_ok=EXPORT_OK,
    )
    return any(p.get("path") == project for p in lst)


def _validate_project(project: str | None) -> str:
    if not project:
        raise HTTPException(status_code=400, detail="Project needed")
    if not is_valid_project(
        project,
        PROJECTROOT,
        EXPORT_OK,
        STRICT_EXPORT,
        _project_in_list,
    ):
        raise HTTPException(status_code=404, detail="No such project")
    return project


def _board_ref(name: str) -> str:
    return f"{BOARD_REF_PREFIX}{name}"


def _task_ref(task_id: str) -> str:
    return f"{TASK_REF_PREFIX}{task_id}"


def _repo_head_or_empty(repo: pygit2.Repository) -> pygit2.Oid:
    if repo.head:
        return repo.head.target
    return EMPTY_TREE_OID


def create_board_for_project(
    project: str,
    name: str = "Tasks",
    description: str = "",
) -> str:
    """Create a board in the project repo. Returns the board ref. Raises HTTPException on error."""
    repo = open_repo(project)
    ref = _board_ref(name)
    if ref in repo.references:
        raise HTTPException(status_code=409, detail="Board already exists")
    target = _repo_head_or_empty(repo)
    board = Board(target, ref, tagger="", description=description)
    board.write(repo)
    return ref


# ---------- Board Routes ----------

board_router = APIRouter(prefix="/boards", tags=["boards"])


@board_router.get("/list", response_class=JSONResponse)
def boards_list(request: Request, project: str) -> JSONResponse:
    """List all boards for the project."""
    _require_auth_disabled()
    _validate_project(project)
    repo = open_repo(project)
    refs = git_get_references(project, "refs/boards")
    boards = []
    for ref_name, _ in refs:
        try:
            b = get_board(repo, ref_name)
            if b:
                boards.append({
                    "name": ref_name.replace(BOARD_REF_PREFIX, ""),
                    "description": getattr(b, "description", "") or "",
                    "task_count": len(getattr(b, "tasks", [])),
                })
        except (ValueError, KeyError):
            continue
    return JSONResponse(content=boards)


@board_router.post("/create", response_class=JSONResponse)
def boards_create(
    request: Request,
    project: str,
    name: str = Query(..., description="Board name"),
    description: str = Query("", description="Board description"),
) -> JSONResponse:
    """Create a new board."""
    _require_auth_disabled()
    _validate_project(project)
    if not name or "/" in name or ".." in name:
        raise HTTPException(status_code=400, detail="Invalid board name")
    repo = open_repo(project)
    ref = _board_ref(name)
    if ref in repo.references:
        raise HTTPException(status_code=409, detail="Board already exists")
    target = _repo_head_or_empty(repo)
    board = Board(target, ref, tagger="", description=description or "")
    oid = board.write(repo)
    return JSONResponse(content={"name": name, "ref": ref, "oid": str(oid)})


@board_router.post("/delete", response_class=JSONResponse)
def boards_delete(
    request: Request,
    project: str,
    name: str = Query(..., description="Board name"),
) -> JSONResponse:
    """Delete a board (removes ref; tag object remains in ODB)."""
    _require_auth_disabled()
    _validate_project(project)
    repo = open_repo(project)
    ref = _board_ref(name)
    if ref not in repo.references:
        raise HTTPException(status_code=404, detail="Board not found")
    repo.references.delete(ref)
    return JSONResponse(content={"message": "Board deleted"})


# ---------- Task Routes ----------

task_router = APIRouter(prefix="/tasks", tags=["tasks"])


def _task_to_json(t: Task) -> dict[str, Any]:
    return {
        "ref": t.name,
        "title": t.title,
        "description": getattr(t, "description", "") or "",
        "status": t.status.value if t.status else None,
        "priority": t.priority.value if t.priority else None,
        "assignee": getattr(t, "assignee"),
        "due_date": t.due_date.isoformat() if getattr(t, "due_date") else None,
        "created_at": getattr(t, "created_at").isoformat() if getattr(t, "created_at") else None,
        "updated_at": getattr(t, "updated_at").isoformat() if getattr(t, "updated_at") else None,
        "comments_count": len(getattr(t, "comments", [])),
    }


# Status order for board columns
BOARD_STATUS_ORDER = ["TODO", "IN_PROGRESS", "IN_REVIEW", "DONE", "CANCELLED"]


def get_board_tasks_grouped(
    project: str,
    board_name: str,
) -> list[dict[str, Any]]:
    """
    Return tasks for a board grouped by status for board view.
    Returns a list of { "status": str, "label": str, "tasks": [ _task_to_json, ... ] }
    in BOARD_STATUS_ORDER. Skips validation/auth (caller must validate project).
    """
    repo = open_repo(project)
    ref = _board_ref(board_name)
    try:
        b = get_board(repo, ref)
    except (ValueError, KeyError):
        return [
            {"status": s, "label": s.replace("_", " ").title(), "tasks": []}
            for s in BOARD_STATUS_ORDER
        ]
    if not b:
        return [
            {"status": s, "label": s.replace("_", " ").title(), "tasks": []}
            for s in BOARD_STATUS_ORDER
        ]
    task_oids = getattr(b, "tasks", []) or []
    by_status: dict[str, list[dict[str, Any]]] = {s: [] for s in BOARD_STATUS_ORDER}
    for oid_hex in task_oids:
        try:
            oid = pygit2.Oid(hex=oid_hex) if isinstance(oid_hex, str) else oid_hex
            t = get_task_by_oid(repo, oid)
            if t:
                d = _task_to_json(t)
                status_key = (d.get("status") or "TODO") if d else "TODO"
                if status_key not in by_status:
                    by_status[status_key] = []
                by_status[status_key].append(d)
        except (ValueError, KeyError, TypeError):
            continue
    return [
        {"status": s, "label": s.replace("_", " ").title(), "tasks": by_status.get(s, [])}
        for s in BOARD_STATUS_ORDER
    ]


@task_router.get("/list", response_class=JSONResponse)
def task_list(
    request: Request,
    project: str,
    board: str = Query(..., description="Board name"),
) -> JSONResponse:
    """List all tasks on a board."""
    _require_auth_disabled()
    _validate_project(project)
    repo = open_repo(project)
    ref = _board_ref(board)
    b = get_board(repo, ref)
    if not b:
        raise HTTPException(status_code=404, detail="Board not found")
    task_oids = getattr(b, "tasks", [])
    tasks = []
    for oid_hex in task_oids:
        try:
            oid = pygit2.Oid(hex=oid_hex) if isinstance(oid_hex, str) else oid_hex
            t = get_task_by_oid(repo, oid)
            if t:
                tasks.append(_task_to_json(t))
        except (ValueError, KeyError, TypeError):
            continue
    return JSONResponse(content=tasks)


@task_router.post("/create", response_class=JSONResponse)
def task_create(
    request: Request,
    project: str,
    board: str = Query(..., description="Board name"),
    title: str = Query(..., description="Task title"),
    description: str = Query("", description="Task description"),
    status: str = Query("TODO", description="Task status"),
    priority: str = Query("LOW", description="Task priority"),
    assignee: str = Query("", description="Assignee"),
    due_date: str = Query("", description="Due date ISO"),
) -> JSONResponse:
    """Create a new task on a board."""
    _require_auth_disabled()
    _validate_project(project)
    repo = open_repo(project)
    board_ref = _board_ref(board)
    b = get_board(repo, board_ref)
    if not b:
        raise HTTPException(status_code=404, detail="Board not found")
    task_id = f"task_{int(time.time() * 1000)}"
    ref = _task_ref(task_id)
    target = _repo_head_or_empty(repo)
    status_enum = getattr(Task.Status, status, None) or Task.Status.TODO
    priority_enum = getattr(Task.Priority, priority, None) or Task.Priority.LOW
    due = None
    if due_date:
        try:
            from datetime import datetime
            due = datetime.fromisoformat(due_date.replace("Z", "+00:00"))
        except ValueError:
            pass
    task = Task(
        target, ref, tagger="",
        title=title,
        description=description or "",
        status=status_enum,
        priority=priority_enum,
        assignee=assignee or None,
        due_date=due,
    )
    oid = task.write(repo)
    b.tasks = getattr(b, "tasks", []) or []
    b.tasks.append(str(oid))
    b.update_message()
    b.write(repo)
    return JSONResponse(content={"task_id": task_id, "ref": ref, "oid": str(oid)})


@task_router.post("/update", response_class=JSONResponse)
async def task_update(
    request: Request,
    project: str,
    board: str = Query(..., description="Board name"),
    task_ref: str = Query(..., alias="task", description="Task ref (e.g. refs/tasks/task_123)"),
) -> JSONResponse:
    """Update a task (body: optional title, description, status, priority, assignee, due_date)."""
    _require_auth_disabled()
    _validate_project(project)
    try:
        body = await request.json() if request.headers.get("content-type", "").startswith("application/json") else {}
    except Exception:
        body = {}
    repo = open_repo(project)
    ref = task_ref if task_ref.startswith("refs/") else _task_ref(task_ref)
    t = get_task(repo, ref)
    if not t:
        raise HTTPException(status_code=404, detail="Task not found")
    if "title" in body:
        t.title = body["title"]
    if "description" in body:
        t.description = body.get("description", "")
    if "status" in body and hasattr(Task.Status, body["status"]):
        t.status = Task.Status(body["status"])
    if "priority" in body and hasattr(Task.Priority, body["priority"]):
        t.priority = Task.Priority(body["priority"])
    if "assignee" in body:
        t.assignee = body["assignee"] or None
    if "due_date" in body:
        try:
            from datetime import datetime
            t.due_date = datetime.fromisoformat(str(body["due_date"]).replace("Z", "+00:00")) if body["due_date"] else None
        except ValueError:
            pass
    old_oid = str(repo.references[ref].resolve().target)
    t.update_message()
    new_oid = t.write(repo)
    board_ref = _board_ref(board)
    b = get_board(repo, board_ref)
    if b and getattr(b, "tasks", None):
        b.tasks = [str(new_oid) if o == old_oid else o for o in b.tasks]
        b.update_message()
        b.write(repo)
    return JSONResponse(content=_task_to_json(t))


@task_router.post("/delete", response_class=JSONResponse)
def task_delete(
    request: Request,
    project: str,
    board: str = Query(..., description="Board name"),
    task_ref: str = Query(..., alias="task", description="Task ref"),
) -> JSONResponse:
    """Delete a task (remove ref and remove from board.tasks)."""
    _require_auth_disabled()
    _validate_project(project)
    repo = open_repo(project)
    ref = task_ref if task_ref.startswith("refs/") else _task_ref(task_ref)
    if ref not in repo.references:
        raise HTTPException(status_code=404, detail="Task not found")
    oid_hex = str(repo.references[ref].resolve().target)
    repo.references.delete(ref)
    board_ref = _board_ref(board)
    b = get_board(repo, board_ref)
    if b and getattr(b, "tasks", None):
        try:
            b.tasks = [x for x in b.tasks if x != oid_hex]
            b.update_message()
            b.write(repo)
        except Exception:
            pass
    return JSONResponse(content={"message": "Task deleted"})


# ---------- Comment Routes ----------

comment_router = APIRouter(prefix="/comments", tags=["comments"])


def _comment_to_json(c: Comment) -> dict[str, Any]:
    return {
        "content": c.content,
        "tagger": c.tagger,
        "created_at": getattr(c, "created_at").isoformat() if getattr(c, "created_at") else None,
        "edited_at": getattr(c, "edited_at").isoformat() if getattr(c, "edited_at") else None,
    }


@comment_router.get("/list", response_class=JSONResponse)
def comment_list(
    request: Request,
    project: str,
    board: str = Query(..., description="Board name"),
    task: str = Query(..., description="Task ref (e.g. refs/tasks/task_123)"),
) -> JSONResponse:
    """List all comments for a task."""
    _require_auth_disabled()
    _validate_project(project)
    repo = open_repo(project)
    task_ref = task if task.startswith("refs/") else _task_ref(task)
    t = get_task(repo, task_ref)
    if not t:
        raise HTTPException(status_code=404, detail="Task not found")
    comment_oids = getattr(t, "comments", []) or []
    comments = []
    for oid_hex in comment_oids:
        try:
            oid = pygit2.Oid(hex=oid_hex) if isinstance(oid_hex, str) else oid_hex
            c = get_comment(repo, oid)
            if c:
                comments.append(_comment_to_json(c))
        except (ValueError, KeyError, TypeError):
            continue
    return JSONResponse(content=comments)


@comment_router.post("/create", response_class=JSONResponse)
def comment_create(
    request: Request,
    project: str,
    board: str = Query(..., description="Board name"),
    task: str = Query(..., description="Task ref"),
    content: str = Query(..., description="Comment content"),
) -> JSONResponse:
    """Create a new comment on a task."""
    _require_auth_disabled()
    _validate_project(project)
    repo = open_repo(project)
    task_ref = task if task.startswith("refs/") else _task_ref(task)
    t = get_task(repo, task_ref)
    if not t:
        raise HTTPException(status_code=404, detail="Task not found")
    resolved = repo.revparse_single(task_ref)
    target_oid = resolved.oid if hasattr(resolved, "oid") else pygit2.Oid(hex=str(resolved))
    comment = Comment(target=target_oid, tagger="", content=content or "")
    comment_oid = comment.write(repo)
    t.comments = getattr(t, "comments", []) or []
    t.comments.append(str(comment_oid))
    t.update_message()
    t.write(repo)
    return JSONResponse(content={"oid": str(comment_oid), "message": "Comment created"})


@comment_router.post("/update", response_class=JSONResponse)
async def comment_update(
    request: Request,
    project: str,
    board: str = Query(..., description="Board name"),
    task: str = Query(..., description="Task ref (to update task.comments)"),
    comment_oid: str = Query(..., alias="comment", description="Comment OID hex"),
) -> JSONResponse:
    """Update a comment (body: content). Writes new comment object and updates task.comments."""
    _require_auth_disabled()
    _validate_project(project)
    try:
        body = await request.json() if request.headers.get("content-type", "").startswith("application/json") else {}
    except Exception:
        body = {}
    repo = open_repo(project)
    oid = pygit2.Oid(hex=comment_oid)
    c = get_comment(repo, oid)
    if not c:
        raise HTTPException(status_code=404, detail="Comment not found")
    if "content" in body:
        c.content = body["content"]
    from datetime import datetime
    c.edited_at = datetime.now()
    c.update_message()
    new_oid = c.write(repo)
    task_ref = task if task.startswith("refs/") else _task_ref(task)
    t = get_task(repo, task_ref)
    if t and getattr(t, "comments", None):
        t.comments = [str(new_oid) if str(co) == comment_oid else co for co in t.comments]
        t.update_message()
        t.write(repo)
    return JSONResponse(content={**_comment_to_json(c), "oid": str(new_oid), "message": "Comment updated"})


@comment_router.post("/delete", response_class=JSONResponse)
def comment_delete(
    request: Request,
    project: str,
    board: str = Query(..., description="Board name"),
    comment_oid: str = Query(..., alias="comment", description="Comment OID hex"),
) -> JSONResponse:
    """Delete a comment (object remains in ODB; caller may remove from task.comments)."""
    _require_auth_disabled()
    _validate_project(project)
    repo = open_repo(project)
    oid = pygit2.Oid(hex=comment_oid)
    try:
        c = get_comment(repo, oid)
    except (ValueError, KeyError):
        raise HTTPException(status_code=404, detail="Comment not found")
    if not c:
        raise HTTPException(status_code=404, detail="Comment not found")
    return JSONResponse(content={"message": "Comment deleted"})