"""
Team overview: what everyone is working on across projects, plus per-user weekly
sprint point capacity. There is no database; capacity is persisted via capacity_config.py.
"""

from __future__ import annotations

import re
from collections import defaultdict
from typing import Annotated, Any
from urllib.parse import quote

from fastapi import APIRouter, Depends, Form, HTTPException, Query
from fastapi.responses import HTMLResponse, RedirectResponse

from pygitweb.auth import (
	access_token_from_request,
	ensure_active_user_if_auth_enabled,
	has_permission,
	principal_from_session,
)
from pygitweb.capacity_config import current_iso_week, get_user_week_capacity, set_user_week_capacity
from pygitweb.config import settings
from pygitweb.dependencies import filter_projects_by_read_access
from pygitweb.permissions import Permission
from pygitweb.projects import git_get_projects_list
from pygitweb.tasks import get_all_project_tasks
from pygitweb.templates_env import Cell, html_link, render_page

team_router = APIRouter(tags=["team"])

# Tasks in these statuses count as a person's current workload.
OPEN_STATUSES = frozenset({"TODO", "IN_PROGRESS", "IN_REVIEW"})

_ISO_WEEK_RE = re.compile(r"^\d{4}-W(0[1-9]|[1-4]\d|5[0-3])$")


def _gather_open_tasks_by_assignee(token: str | None) -> dict[str, list[dict[str, Any]]]:
	projects = filter_projects_by_read_access(
		git_get_projects_list(paranoid=settings.STRICT_EXPORT, export_ok=settings.EXPORT_OK),
		token,
	)
	by_assignee: dict[str, list[dict[str, Any]]] = defaultdict(list)
	for pr in projects:
		project = pr.get("path", "")
		if not project:
			continue
		try:
			tasks = get_all_project_tasks(project)
		except Exception:
			continue
		for t in tasks:
			if t.get("status") not in OPEN_STATUSES:
				continue
			assignee = (t.get("assignee") or "").strip() or "Unassigned"
			t["project"] = project
			by_assignee[assignee].append(t)
	return by_assignee


def _ensure_can_set_capacity(token: str | None, target_user: str) -> None:
	if not settings.AUTH:
		return
	ensure_active_user_if_auth_enabled(token)
	principal = principal_from_session(token)
	if principal is not None and principal.username == target_user:
		return
	if has_permission(Permission.SETTINGS, token=token):
		return
	raise HTTPException(status_code=403, detail="Insufficient permissions")


@team_router.get("", response_class=HTMLResponse)
def team_page(
	token: Annotated[str | None, Depends(access_token_from_request)],
	week: Annotated[str | None, Query()] = None,
) -> HTMLResponse:
	"""Team overview: open tasks grouped by assignee, with weekly point capacity vs. load."""
	iso_week = (week or current_iso_week()).strip()
	if not _ISO_WEEK_RE.match(iso_week):
		iso_week = current_iso_week()
	by_assignee = _gather_open_tasks_by_assignee(token)

	principal = principal_from_session(token)
	current_username = principal.username if principal else None
	can_edit_others = (not settings.AUTH) or has_permission(Permission.SETTINGS, token=token)

	groups: list[dict[str, Any]] = []
	for assignee in sorted(by_assignee, key=lambda a: (a == "Unassigned", a.lower())):
		tasks = sorted(by_assignee[assignee], key=lambda t: (t.get("priority") or "", t.get("title") or ""))
		points_total = sum(t.get("points") or 0 for t in tasks)
		is_unassigned = assignee == "Unassigned"
		capacity = None if is_unassigned else get_user_week_capacity(assignee, iso_week)

		rows: list[list[Cell]] = []
		for t in tasks:
			project_enc = quote(t["project"], safe="/")
			board_enc = quote(t["board"], safe="")
			task_enc = quote(t["ref"], safe="")
			project_url = f"/project/{project_enc}"
			task_url = f"{project_url}/board/?b={board_enc}&task={task_enc}"
			rows.append([
				html_link(task_url, t.get("title") or "(untitled)"),
				html_link(project_url, t["project"]),
				t.get("board") or "",
				(t.get("status") or "").replace("_", " ").title(),
				(t.get("priority") or "").title(),
				str(t.get("points") if t.get("points") is not None else ""),
			])
		can_edit = (not is_unassigned) and (can_edit_others or assignee == current_username)
		capacity_url = f"/team/capacity?user={quote(assignee, safe='')}&week={quote(iso_week, safe='')}"
		capacity_pct = 0
		if capacity:
			capacity_pct = min(100, round(100 * points_total / capacity))
		groups.append({
			"assignee": assignee,
			"points_total": points_total,
			"capacity": capacity,
			"capacity_pct": capacity_pct,
			"over_capacity": capacity is not None and points_total > capacity,
			"can_edit": can_edit,
			"capacity_url": capacity_url,
			"rows": rows,
		})

	return render_page(
		"team.html",
		title=f"{settings.SITE_NAME} - Team",
		iso_week=iso_week,
		groups=groups,
		nav="team",
	)


@team_router.get("/capacity", response_class=HTMLResponse)
def team_capacity_form(
	token: Annotated[str | None, Depends(access_token_from_request)],
	user: Annotated[str | None, Query()] = None,
	week: Annotated[str | None, Query()] = None,
) -> HTMLResponse:
	"""Form to set a user's sprint point capacity for a given ISO week."""
	ensure_active_user_if_auth_enabled(token)
	principal = principal_from_session(token)
	target_user = (user or (principal.username if principal else "")).strip()
	if not target_user:
		raise HTTPException(status_code=400, detail="user is required")
	iso_week = (week or current_iso_week()).strip()
	if not _ISO_WEEK_RE.match(iso_week):
		iso_week = current_iso_week()
	current_points = get_user_week_capacity(target_user, iso_week)
	return render_page(
		"team_capacity_form.html",
		title=f"{settings.SITE_NAME} - Set capacity",
		target_user=target_user,
		iso_week=iso_week,
		current_points=current_points,
		nav="team",
	)


@team_router.post("/capacity/submit", response_class=HTMLResponse)
def team_capacity_submit(
	token: Annotated[str | None, Depends(access_token_from_request)],
	user: Annotated[str, Form()],
	week: Annotated[str, Form()],
	points: Annotated[str, Form()],
) -> RedirectResponse:
	"""Save a user's sprint point capacity for a given ISO week."""
	target_user = user.strip()
	iso_week = week.strip()
	if not target_user:
		raise HTTPException(status_code=400, detail="user is required")
	if not _ISO_WEEK_RE.match(iso_week):
		raise HTTPException(status_code=400, detail="week must be in ISO format YYYY-Www")
	_ensure_can_set_capacity(token, target_user)
	try:
		points_val = int(points)
	except ValueError as exc:
		raise HTTPException(status_code=400, detail="points must be an integer") from exc
	if points_val < 0:
		raise HTTPException(status_code=400, detail="points must be non-negative")
	set_user_week_capacity(target_user, iso_week, points_val)
	return RedirectResponse(url=f"/team?week={quote(iso_week, safe='')}", status_code=303)