from __future__ import annotations

from collections.abc import Generator
from pathlib import Path
from unittest.mock import patch

import pygit2
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pygit2 import Oid

from pygittools.tasks import BOARD_REF_PREFIX, TASK_REF_PREFIX, Board, Comment, Task
from pygitweb.config import settings
from pygitweb.tasks import EMPTY_TREE_OID, board_router, comment_router, task_router


def _build_client() -> TestClient:
	app = FastAPI()
	app.include_router(board_router, prefix="/board")
	app.include_router(task_router, prefix="/tasks")
	app.include_router(comment_router, prefix="/comments")
	return TestClient(app)


def _create_initial_commit(repo: pygit2.Repository, repo_dir: Path) -> None:
	(repo_dir / "README.md").write_text("seed\n", encoding="utf-8")
	index = repo.index
	index.add("README.md")
	index.write()
	tree = index.write_tree()
	sig = pygit2.Signature("tester", "tester@example.com")
	repo.create_commit("HEAD", sig, sig, "initial", tree, [])


class TestTaskRoutes:
	class TestReadonlyResponses:
		@pytest.fixture(scope="class")
		def seeded_env(
			self, tmp_path_factory: pytest.TempPathFactory
		) -> Generator[dict[str, str | TestClient], None, None]:
			root: Path = tmp_path_factory.mktemp("tasks-routes-readonly")
			repo_dir: Path = root / "demo"
			repo = pygit2.init_repository(str(repo_dir), bare=False)
			project: str = "demo"

			board_ref: str = f"{BOARD_REF_PREFIX}Tasks"
			board = Board(EMPTY_TREE_OID, board_ref, tagger="", description="Main board")

			task_a_ref: str = f"{TASK_REF_PREFIX}task_a"
			task_a = Task(
				EMPTY_TREE_OID,
				task_a_ref,
				tagger="",
				title="Alpha",
				description="Alpha description",
				status=Task.Status.TODO,
				priority=Task.Priority.HIGH,
				assignee="alice",
			)
			task_a_oid = task_a.write(repo)
			comment_a = Comment(task_a_oid, "alice <alice@example.com>", "alpha note")
			comment_a_oid = comment_a.write(repo)
			task_a.comments = [str(comment_a_oid)]
			task_a.update_message()
			task_a_oid = task_a.write(repo)

			task_b_ref: str = f"{TASK_REF_PREFIX}task_b"
			task_b = Task(
				EMPTY_TREE_OID,
				task_b_ref,
				tagger="",
				title="Bravo",
				description="",
				status=Task.Status.IN_PROGRESS,
				priority=Task.Priority.MEDIUM,
				assignee=None,
			)
			task_b_oid = task_b.write(repo)

			board.tasks = [str(task_a_oid), str(task_b_oid)]
			board.update_message()
			board.write(repo)

			with (
				patch.object(settings, "PROJECTROOT", str(root)),
				patch.object(settings, "PROJECTS_LIST", str(root)),
				patch.object(settings, "STRICT_EXPORT", False),
				patch.object(settings, "EXPORT_OK", ""),
				patch.object(settings, "AUTH", None),
			):
				client = _build_client()
				yield {
					"client": client,
					"project": project,
					"board": "Tasks",
					"task_a_ref": task_a_ref,
					"task_b_ref": task_b_ref,
				}

		def test_board_list_response_format(self, seeded_env: dict[str, str | TestClient]) -> None:
			client = seeded_env["client"]
			assert isinstance(client, TestClient)
			resp = client.get("/board/list", params={"project": seeded_env["project"]})
			assert resp.status_code == 200
			body = resp.json()
			assert isinstance(body, list)
			assert body == [{"name": "Tasks", "description": "Main board", "task_count": 2}]

		def test_task_list_response_format(self, seeded_env: dict[str, str | TestClient]) -> None:
			client = seeded_env["client"]
			assert isinstance(client, TestClient)
			resp = client.get(
				"/tasks/list",
				params={"project": seeded_env["project"], "board": seeded_env["board"]},
			)
			assert resp.status_code == 200
			body = resp.json()
			assert isinstance(body, list)
			tasks_by_ref = {entry["ref"]: entry for entry in body}
			alpha = tasks_by_ref[str(seeded_env["task_a_ref"])]
			assert alpha["title"] == "Alpha"
			assert alpha["description"] == "Alpha description"
			assert alpha["status"] == "TODO"
			assert alpha["priority"] == "HIGH"
			assert alpha["assignee"] == "alice"
			assert alpha["comments_count"] == 1
			assert alpha["created_at"] is not None
			assert alpha["updated_at"] is not None

		def test_comment_list_response_format(self, seeded_env: dict[str, str | TestClient]) -> None:
			client = seeded_env["client"]
			assert isinstance(client, TestClient)
			resp = client.get(
				"/comments/list",
				params={
					"project": seeded_env["project"],
					"board": seeded_env["board"],
					"task": seeded_env["task_a_ref"],
				},
			)
			assert resp.status_code == 200
			body = resp.json()
			assert isinstance(body, list)
			assert len(body) == 1
			comment = body[0]
			assert comment["content"] == "alpha note"
			assert comment["tagger"] == "alice <alice@example.com>"
			assert comment["created_at"] is not None
			assert comment["edited_at"] is not None

	class TestMutableResponses:
		@pytest.fixture(scope="class")
		def mutable_env(
			self, tmp_path_factory: pytest.TempPathFactory
		) -> Generator[dict[str, str | TestClient], None, None]:
			root: Path = tmp_path_factory.mktemp("tasks-routes-mutable")
			repo_dir: Path = root / "demo"
			repo = pygit2.init_repository(str(repo_dir), bare=False)
			_create_initial_commit(repo, repo_dir)
			project: str = "demo"
			board_name: str = "Tasks"
			board_ref: str = f"{BOARD_REF_PREFIX}{board_name}"
			board = Board(EMPTY_TREE_OID, board_ref, tagger="", description="Mutable board")
			task_ref = f"{TASK_REF_PREFIX}mutable_seed"
			task = Task(
				EMPTY_TREE_OID,
				task_ref,
				tagger="",
				title="Mutable seed",
				description="",
				status=Task.Status.TODO,
				priority=Task.Priority.LOW,
			)
			task_oid = task.write(repo)
			comment = Comment(task_oid, "seed <seed@example.com>", "seed comment")
			comment_oid = comment.write(repo)
			task.comments = [str(comment_oid)]
			task.update_message()
			task_oid = task.write(repo)
			board.tasks = [str(task_oid)]
			board.update_message()
			board.write(repo)

			with (
				patch.object(settings, "PROJECTROOT", str(root)),
				patch.object(settings, "PROJECTS_LIST", str(root)),
				patch.object(settings, "STRICT_EXPORT", False),
				patch.object(settings, "EXPORT_OK", ""),
				patch.object(settings, "AUTH", None),
			):
				client = _build_client()
				yield {
					"client": client,
					"project": project,
					"board": board_name,
					"seed_comment_oid": str(comment_oid),
				}

		def test_task_create_and_update_response_format(self, mutable_env: dict[str, str | TestClient]) -> None:
			client = mutable_env["client"]
			assert isinstance(client, TestClient)
			create_resp = client.post(
				"/tasks/create",
				params={
					"project": mutable_env["project"],
					"board": mutable_env["board"],
					"title": "Created Task",
					"description": "Needs updates",
					"status": "TODO",
					"priority": "LOW",
				},
			)
			assert create_resp.status_code == 200
			created = create_resp.json()
			assert set(created.keys()) == {"task_id", "ref", "oid"}
			assert str(created["ref"]).startswith("refs/tags/tasks/task_")

			update_resp = client.post(
				"/tasks/update",
				params={
					"project": mutable_env["project"],
					"board": mutable_env["board"],
					"task": created["ref"],
				},
				json={
					"title": "Updated Task",
					"description": "Updated description",
					"status": "IN_REVIEW",
					"priority": "CRITICAL",
					"assignee": "bob",
					"due_date": "2025-02-03T04:05:06+00:00",
				},
			)
			assert update_resp.status_code == 200
			updated = update_resp.json()
			assert updated["ref"] == created["ref"]
			assert updated["title"] == "Updated Task"
			assert updated["description"] == "Updated description"
			assert updated["status"] == "IN_REVIEW"
			assert updated["priority"] == "CRITICAL"
			assert updated["assignee"] == "bob"
			assert updated["due_date"] == "2025-02-03T04:05:06+00:00"

		def test_comment_delete_response_format(self, mutable_env: dict[str, str | TestClient]) -> None:
			client = mutable_env["client"]
			assert isinstance(client, TestClient)
			delete_comment_resp = client.post(
				"/comments/delete",
				params={
					"project": mutable_env["project"],
					"board": mutable_env["board"],
					"comment": mutable_env["seed_comment_oid"],
				},
			)
			assert delete_comment_resp.status_code == 200
			assert delete_comment_resp.json() == {"message": "Comment deleted"}

		def test_task_delete_response_format(self, mutable_env: dict[str, str | TestClient]) -> None:
			client = mutable_env["client"]
			assert isinstance(client, TestClient)
			create_resp = client.post(
				"/tasks/create",
				params={
					"project": mutable_env["project"],
					"board": mutable_env["board"],
					"title": "To delete",
				},
			)
			task_ref: str = create_resp.json()["ref"]
			delete_resp = client.post(
				"/tasks/delete",
				params={
					"project": mutable_env["project"],
					"board": mutable_env["board"],
					"task": task_ref,
				},
			)
			assert delete_resp.status_code == 200
			assert delete_resp.json() == {"message": "Task deleted"}

			repo = pygit2.Repository(str(Path(settings.PROJECTROOT) / str(mutable_env["project"])))
			assert task_ref not in repo.references

		def test_board_create_and_delete_response_format(self, mutable_env: dict[str, str | TestClient]) -> None:
			client = mutable_env["client"]
			assert isinstance(client, TestClient)
			create_resp = client.post(
				"/board/create",
				params={
					"project": mutable_env["project"],
					"name": "temp-board",
					"description": "Temporary",
				},
			)
			assert create_resp.status_code == 200
			created = create_resp.json()
			assert set(created.keys()) == {"name", "ref", "oid"}
			assert created["name"] == "temp-board"
			assert created["ref"] == f"{BOARD_REF_PREFIX}temp-board"
			Oid(hex=created["oid"])

			delete_resp = client.post(
				"/board/delete",
				params={"project": mutable_env["project"], "name": "temp-board"},
			)
			assert delete_resp.status_code == 200
			assert delete_resp.json() == {"message": "Board deleted"}