from __future__ import annotations

import uuid
from datetime import datetime, timezone
from pathlib import Path

from backend.app.api.errors import AppError
from backend.app.api.schemas import TaskCreateResponse
from backend.app.db.history_repository import HistoryRepository
from backend.app.db.task_repository import TaskRepository
from backend.app.security.path_guard import PathGuard
from backend.app.tasks_runner import TaskRunner


class DeleteTaskService:
    def __init__(
        self,
        path_guard: PathGuard,
        repository: TaskRepository,
        runner: TaskRunner,
        history_repository: HistoryRepository | None = None,
    ):
        self._path_guard = path_guard
        self._repository = repository
        self._runner = runner
        self._history_repository = history_repository

    def create_delete_task(self, path: str | None, recursive: bool = False) -> TaskCreateResponse:
        if not path:
            raise AppError(
                code="invalid_request",
                message="Query parameter 'path' is required",
                status_code=400,
            )
        try:
            item = self._build_delete_item(path=path, recursive=recursive)

            task_id = str(uuid.uuid4())
            task = self._repository.create_task(
                operation="delete",
                source=item["relative_path"],
                destination="",
                task_id=task_id,
            )
            self._record_history(
                entry_id=task_id,
                operation="delete",
                status="queued",
                path=item["relative_path"],
            )
            self._runner.enqueue_delete_path(task_id=task["id"], item=item)
            return TaskCreateResponse(task_id=task["id"], status=task["status"])
        except AppError as exc:
            self._record_history(
                operation="delete",
                status="failed",
                path=path,
                error_code=exc.code,
                error_message=exc.message,
                finished_at=self._now_iso(),
            )
            raise
        except OSError as exc:
            error = AppError(
                code="io_error",
                message="Filesystem operation failed",
                status_code=500,
                details={"reason": str(exc)},
            )
            self._record_history(
                operation="delete",
                status="failed",
                path=path,
                error_code=error.code,
                error_message=error.message,
                finished_at=self._now_iso(),
            )
            raise error

    def create_batch_delete_task(self, paths: list[str] | None, recursive_paths: list[str] | None = None) -> TaskCreateResponse:
        if not paths or len(paths) < 2:
            raise AppError(
                code="invalid_request",
                message="Batch delete requires at least 2 paths",
                status_code=400,
            )

        recursive_paths_set = set(recursive_paths or [])
        invalid_recursive = sorted(path for path in recursive_paths_set if path not in paths)
        if invalid_recursive:
            raise AppError(
                code="invalid_request",
                message="Recursive delete paths must be included in the batch selection",
                status_code=400,
                details={"path": invalid_recursive[0]},
            )

        try:
            items = [
                self._build_delete_item(
                    path=path,
                    recursive=path in recursive_paths_set,
                    include_root_prefix=True,
                )
                for path in paths
            ]

            task_id = str(uuid.uuid4())
            task = self._repository.create_task(
                operation="delete",
                source=f"{len(items)} items",
                destination="",
                task_id=task_id,
            )
            self._record_history(
                entry_id=task_id,
                operation="delete",
                status="queued",
                path=f"{len(items)} items",
            )
            self._runner.enqueue_delete_batch(task_id=task["id"], items=items)
            return TaskCreateResponse(task_id=task["id"], status=task["status"])
        except AppError as exc:
            self._record_history(
                operation="delete",
                status="failed",
                path=f"{len(paths or [])} items",
                error_code=exc.code,
                error_message=exc.message,
                finished_at=self._now_iso(),
            )
            raise
        except OSError as exc:
            error = AppError(
                code="io_error",
                message="Filesystem operation failed",
                status_code=500,
                details={"reason": str(exc)},
            )
            self._record_history(
                operation="delete",
                status="failed",
                path=f"{len(paths or [])} items",
                error_code=error.code,
                error_message=error.message,
                finished_at=self._now_iso(),
            )
            raise error

    def _build_delete_item(self, path: str, recursive: bool, include_root_prefix: bool = False) -> dict:
        resolved_target = self._path_guard.resolve_existing_path(path)

        if resolved_target.absolute.is_file():
            label = resolved_target.absolute.name
            files = [{"path": str(resolved_target.absolute), "label": label}]
            directories: list[str] = []
            kind = "file"
        elif resolved_target.absolute.is_dir():
            kind = "directory"
            if not recursive and any(resolved_target.absolute.iterdir()):
                raise AppError(
                    code="directory_not_empty",
                    message="Directory is not empty",
                    status_code=409,
                    details={"path": resolved_target.relative},
                )
            if recursive:
                files, directories = self._build_recursive_delete_plan(
                    resolved_target.absolute,
                    include_root_prefix=include_root_prefix,
                )
            else:
                files = []
                directories = [str(resolved_target.absolute)]
        else:
            raise AppError(
                code="type_conflict",
                message="Unsupported path type for delete",
                status_code=409,
                details={"path": resolved_target.relative},
            )

        return {
            "target": str(resolved_target.absolute),
            "relative_path": resolved_target.relative,
            "kind": kind,
            "recursive": recursive,
            "files": files,
            "directories": directories,
            "progress_total_items": len(files),
            "progress_label": files[0]["label"] if files else None,
        }

    def _build_recursive_delete_plan(self, root: Path, include_root_prefix: bool = False) -> tuple[list[dict[str, str]], list[str]]:
        files: list[dict[str, str]] = []
        directories: list[str] = []
        start_prefix = Path(root.name) if include_root_prefix else Path()

        def walk(path: Path, relative_prefix: Path) -> None:
            for entry in sorted(path.iterdir(), key=lambda child: child.name.lower()):
                relative_path = relative_prefix / entry.name
                if entry.is_symlink():
                    files.append({"path": str(entry), "label": relative_path.as_posix()})
                    continue
                if entry.is_dir():
                    walk(entry, relative_path)
                    directories.append(str(entry))
                    continue
                files.append({"path": str(entry), "label": relative_path.as_posix()})

        walk(root, start_prefix)
        directories.append(str(root))
        return files, directories

    def _record_history(self, **kwargs) -> None:
        if self._history_repository:
            self._history_repository.create_entry(**kwargs)

    @staticmethod
    def _now_iso() -> str:
        return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
