"""
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

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 git_get_references, open_repo
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(tags=["boards"])


@board_router.get("/list", response_class=JSONResponse)
def boards_list(
	request: Request,
	project: str = Query(..., description="Project path"),
) -> 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 = Query(..., description="Project path"),
	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 = Query(..., description="Project path"),
	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(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": t.assignee,
		"due_date": t.due_date.isoformat() if t.due_date else None,
		"created_at": t.created_at.isoformat() if t.created_at else None,
		"updated_at": t.updated_at.isoformat() if 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)
	b = get_board(repo, ref)
	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 = Query(..., description="Project path"),
	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 = Query(..., description="Project path"),
	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 = Query(..., description="Project path"),
	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 = Query(..., description="Project path"),
	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(tags=["comments"])


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


@comment_router.get("/list", response_class=JSONResponse)
def comment_list(
	request: Request,
	project: str = Query(..., description="Project path"),
	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 = Query(..., description="Project path"),
	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 = Query(..., description="Project path"),
	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 = Query(..., description="Project path"),
	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) as e:
		raise HTTPException(status_code=404, detail="Comment not found") from e
	if not c:
		raise HTTPException(status_code=404, detail="Comment not found")
	return JSONResponse(content={"message": "Comment deleted"})