from __future__ import annotations

import asyncio
import sys
import tempfile
import unittest
from pathlib import Path

import httpx

sys.path.insert(0, str(Path(__file__).resolve().parents[3]))

from backend.app.dependencies import get_task_service
from backend.app.db.task_repository import TaskRepository
from backend.app.main import app
from backend.app.services.task_service import TaskService


class TasksApiGoldenTest(unittest.TestCase):
    def setUp(self) -> None:
        self.temp_dir = tempfile.TemporaryDirectory()
        self.db_path = str(Path(self.temp_dir.name) / "tasks.db")
        self.repo = TaskRepository(self.db_path)
        self.service = TaskService(self.repo)

        async def _override_task_service() -> TaskService:
            return self.service

        app.dependency_overrides[get_task_service] = _override_task_service

    def tearDown(self) -> None:
        app.dependency_overrides.clear()
        self.temp_dir.cleanup()

    def _get(self, url: str) -> httpx.Response:
        async def _run() -> httpx.Response:
            transport = httpx.ASGITransport(app=app)
            async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client:
                return await client.get(url)

        return asyncio.run(_run())

    def _post(self, url: str, payload: dict | None = None) -> httpx.Response:
        async def _run() -> httpx.Response:
            transport = httpx.ASGITransport(app=app)
            async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client:
                return await client.post(url, json=payload)

        return asyncio.run(_run())

    def _insert_task(
        self,
        *,
        task_id: str,
        operation: str,
        status: str,
        source: str,
        destination: str,
        created_at: str,
        started_at: str | None = None,
        finished_at: str | None = None,
        done_bytes: int | None = None,
        total_bytes: int | None = None,
        done_items: int | None = None,
        total_items: int | None = None,
        current_item: str | None = None,
        failed_item: str | None = None,
        error_code: str | None = None,
        error_message: str | None = None,
    ) -> None:
        self.repo.insert_task_for_testing(
            {
                "id": task_id,
                "operation": operation,
                "status": status,
                "source": source,
                "destination": destination,
                "done_bytes": done_bytes,
                "total_bytes": total_bytes,
                "done_items": done_items,
                "total_items": total_items,
                "current_item": current_item,
                "failed_item": failed_item,
                "error_code": error_code,
                "error_message": error_message,
                "created_at": created_at,
                "started_at": started_at,
                "finished_at": finished_at,
            }
        )

    def test_get_tasks_empty_list(self) -> None:
        response = self._get("/api/tasks")

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json(), {"items": []})

    def test_get_tasks_list_shape(self) -> None:
        self._insert_task(
            task_id="task-old",
            operation="copy",
            status="completed",
            source="storage1/a.txt",
            destination="storage2/a.txt",
            created_at="2026-03-10T10:00:00Z",
            finished_at="2026-03-10T10:00:05Z",
        )
        self._insert_task(
            task_id="task-new",
            operation="move",
            status="running",
            source="storage1/b.txt",
            destination="storage2/b.txt",
            created_at="2026-03-10T10:01:00Z",
        )

        response = self._get("/api/tasks")

        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            response.json(),
            {
                "items": [
                    {
                        "id": "task-new",
                        "operation": "move",
                        "status": "running",
                        "source": "storage1/b.txt",
                        "destination": "storage2/b.txt",
                        "created_at": "2026-03-10T10:01:00Z",
                        "finished_at": None,
                    },
                    {
                        "id": "task-old",
                        "operation": "copy",
                        "status": "completed",
                        "source": "storage1/a.txt",
                        "destination": "storage2/a.txt",
                        "created_at": "2026-03-10T10:00:00Z",
                        "finished_at": "2026-03-10T10:00:05Z",
                    },
                ]
            },
        )

    def test_get_task_detail_queued(self) -> None:
        self._insert_task(
            task_id="task-queued",
            operation="copy",
            status="queued",
            source="storage1/a.txt",
            destination="storage2/a.txt",
            created_at="2026-03-10T10:00:00Z",
        )

        response = self._get("/api/tasks/task-queued")

        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            response.json(),
            {
                "id": "task-queued",
                "operation": "copy",
                "status": "queued",
                "source": "storage1/a.txt",
                "destination": "storage2/a.txt",
                "done_bytes": None,
                "total_bytes": None,
                "done_items": None,
                "total_items": None,
                "current_item": None,
                "failed_item": None,
                "error_code": None,
                "error_message": None,
                "created_at": "2026-03-10T10:00:00Z",
                "started_at": None,
                "finished_at": None,
            },
        )

    def test_get_task_detail_running(self) -> None:
        self._insert_task(
            task_id="task-running",
            operation="move",
            status="running",
            source="storage1/a.txt",
            destination="storage2/a.txt",
            created_at="2026-03-10T10:00:00Z",
            started_at="2026-03-10T10:00:01Z",
            done_bytes=1024,
            total_bytes=2048,
            done_items=1,
            total_items=2,
            current_item="storage1/a.txt",
        )

        response = self._get("/api/tasks/task-running")

        self.assertEqual(response.status_code, 200)
        body = response.json()
        self.assertEqual(body["status"], "running")
        self.assertEqual(body["done_bytes"], 1024)
        self.assertEqual(body["total_bytes"], 2048)
        self.assertEqual(body["current_item"], "storage1/a.txt")

    def test_get_task_detail_completed(self) -> None:
        self._insert_task(
            task_id="task-completed",
            operation="copy",
            status="completed",
            source="storage1/a.txt",
            destination="storage2/a.txt",
            created_at="2026-03-10T10:00:00Z",
            started_at="2026-03-10T10:00:01Z",
            finished_at="2026-03-10T10:00:03Z",
            done_bytes=2048,
            total_bytes=2048,
        )

        response = self._get("/api/tasks/task-completed")

        self.assertEqual(response.status_code, 200)
        body = response.json()
        self.assertEqual(body["status"], "completed")
        self.assertEqual(body["finished_at"], "2026-03-10T10:00:03Z")
        self.assertEqual(body["error_code"], None)

    def test_get_task_detail_failed(self) -> None:
        self._insert_task(
            task_id="task-failed",
            operation="move",
            status="failed",
            source="storage1/a.txt",
            destination="storage2/a.txt",
            created_at="2026-03-10T10:00:00Z",
            started_at="2026-03-10T10:00:01Z",
            finished_at="2026-03-10T10:00:02Z",
            failed_item="storage1/a.txt",
            error_code="io_error",
            error_message="write failed",
        )

        response = self._get("/api/tasks/task-failed")

        self.assertEqual(response.status_code, 200)
        body = response.json()
        self.assertEqual(body["status"], "failed")
        self.assertEqual(body["failed_item"], "storage1/a.txt")
        self.assertEqual(body["error_code"], "io_error")
        self.assertEqual(body["error_message"], "write failed")

    def test_get_task_detail_delete_running(self) -> None:
        self._insert_task(
            task_id="task-delete",
            operation="delete",
            status="running",
            source="storage1/trash.txt",
            destination="",
            created_at="2026-03-10T10:00:00Z",
            started_at="2026-03-10T10:00:01Z",
            done_items=0,
            total_items=1,
            current_item="trash.txt",
        )

        response = self._get("/api/tasks/task-delete")

        self.assertEqual(response.status_code, 200)
        body = response.json()
        self.assertEqual(body["operation"], "delete")
        self.assertEqual(body["status"], "running")
        self.assertEqual(body["done_items"], 0)
        self.assertEqual(body["total_items"], 1)
        self.assertEqual(body["current_item"], "trash.txt")

    def test_cancel_running_delete_task_returns_cancelling(self) -> None:
        self._insert_task(
            task_id="task-delete",
            operation="delete",
            status="running",
            source="storage1/trash.txt",
            destination="",
            created_at="2026-03-10T10:00:00Z",
            started_at="2026-03-10T10:00:01Z",
            done_items=0,
            total_items=1,
            current_item="trash.txt",
        )

        response = self._post("/api/tasks/task-delete/cancel")

        self.assertEqual(response.status_code, 200)
        body = response.json()
        self.assertEqual(body["operation"], "delete")
        self.assertEqual(body["status"], "cancelling")
        self.assertEqual(body["current_item"], "trash.txt")

    def test_cancel_completed_task_rejected(self) -> None:
        self._insert_task(
            task_id="task-copy",
            operation="copy",
            status="completed",
            source="storage1/a.txt",
            destination="storage2/a.txt",
            created_at="2026-03-10T10:00:00Z",
            finished_at="2026-03-10T10:00:04Z",
        )

        response = self._post("/api/tasks/task-copy/cancel")

        self.assertEqual(response.status_code, 409)
        self.assertEqual(
            response.json(),
            {
                "error": {
                    "code": "task_not_cancellable",
                    "message": "Task cannot be cancelled",
                    "details": {"task_id": "task-copy", "status": "completed"},
                }
            },
        )

    def test_cancel_download_task_rejected(self) -> None:
        self._insert_task(
            task_id="task-download",
            operation="download",
            status="preparing",
            source="single_directory_zip",
            destination="docs.zip",
            created_at="2026-03-10T10:00:00Z",
            started_at="2026-03-10T10:00:01Z",
        )

        response = self._post("/api/tasks/task-download/cancel")

        self.assertEqual(response.status_code, 409)
        self.assertEqual(
            response.json(),
            {
                "error": {
                    "code": "task_not_cancellable",
                    "message": "Task cannot be cancelled",
                    "details": {"task_id": "task-download", "status": "preparing"},
                }
            },
        )

    def test_get_task_detail_ready_archive_download(self) -> None:
        self._insert_task(
            task_id="task-download-ready",
            operation="download",
            status="ready",
            source="storage1/docs",
            destination="docs.zip",
            created_at="2026-03-10T10:00:00Z",
            started_at="2026-03-10T10:00:01Z",
            finished_at="2026-03-10T10:00:05Z",
            done_items=1,
            total_items=1,
        )

        response = self._get("/api/tasks/task-download-ready")

        self.assertEqual(response.status_code, 200)
        body = response.json()
        self.assertEqual(body["operation"], "download")
        self.assertEqual(body["status"], "ready")
        self.assertEqual(body["destination"], "docs.zip")

    def test_get_task_detail_duplicate_completed(self) -> None:
        self._insert_task(
            task_id="task-duplicate",
            operation="duplicate",
            status="completed",
            source="storage1/report.txt",
            destination="storage1/report copy.txt",
            created_at="2026-03-10T10:00:00Z",
            started_at="2026-03-10T10:00:01Z",
            finished_at="2026-03-10T10:00:03Z",
            done_items=1,
            total_items=1,
            current_item="storage1/report.txt",
        )

        response = self._get("/api/tasks/task-duplicate")

        self.assertEqual(response.status_code, 200)
        body = response.json()
        self.assertEqual(body["operation"], "duplicate")
        self.assertEqual(body["status"], "completed")
        self.assertEqual(body["done_items"], 1)
        self.assertEqual(body["total_items"], 1)

    def test_get_task_detail_requested_archive_download(self) -> None:
        self._insert_task(
            task_id="task-download-requested",
            operation="download",
            status="requested",
            source="storage1/docs",
            destination="docs.zip",
            created_at="2026-03-10T10:00:00Z",
            done_items=0,
            total_items=1,
        )

        response = self._get("/api/tasks/task-download-requested")

        self.assertEqual(response.status_code, 200)
        body = response.json()
        self.assertEqual(body["operation"], "download")
        self.assertEqual(body["status"], "requested")
        self.assertEqual(body["done_items"], 0)
        self.assertEqual(body["total_items"], 1)

    def test_get_task_detail_preparing_archive_download_with_current_item(self) -> None:
        self._insert_task(
            task_id="task-download-preparing",
            operation="download",
            status="preparing",
            source="storage1/docs",
            destination="docs.zip",
            created_at="2026-03-10T10:00:00Z",
            started_at="2026-03-10T10:00:01Z",
            done_items=1,
            total_items=3,
            current_item="storage1/docs/b.txt",
        )

        response = self._get("/api/tasks/task-download-preparing")

        self.assertEqual(response.status_code, 200)
        body = response.json()
        self.assertEqual(body["operation"], "download")
        self.assertEqual(body["status"], "preparing")
        self.assertEqual(body["done_items"], 1)
        self.assertEqual(body["total_items"], 3)
        self.assertEqual(body["current_item"], "storage1/docs/b.txt")

    def test_get_task_detail_cancelled_archive_download(self) -> None:
        self._insert_task(
            task_id="task-download-cancelled",
            operation="download",
            status="cancelled",
            source="storage1/docs",
            destination="docs.zip",
            created_at="2026-03-10T10:00:00Z",
            started_at="2026-03-10T10:00:01Z",
            finished_at="2026-03-10T10:00:03Z",
            done_items=0,
            total_items=1,
        )

        response = self._get("/api/tasks/task-download-cancelled")

        self.assertEqual(response.status_code, 200)
        body = response.json()
        self.assertEqual(body["operation"], "download")
        self.assertEqual(body["status"], "cancelled")
        self.assertEqual(body["destination"], "docs.zip")

    def test_get_task_not_found(self) -> None:
        response = self._get("/api/tasks/task-missing")

        self.assertEqual(response.status_code, 404)
        self.assertEqual(
            response.json(),
            {
                "error": {
                    "code": "task_not_found",
                    "message": "Task was not found",
                    "details": {"task_id": "task-missing"},
                }
            },
        )


if __name__ == "__main__":
    unittest.main()
