# retoor <retoor@molodetz.nl>

import asyncio
import logging
import shutil

from devplacepy.config import FORK_STAGING_DIR
from devplacepy import project_files
from devplacepy.content import create_content_item
from devplacepy.database import get_table, record_fork, delete_fork_relations
from devplacepy.services.jobs.base import JobService
from devplacepy.utils import XP_PROJECT

logger = logging.getLogger(__name__)


class ForkService(JobService):
    kind = "fork"
    title = "Fork"
    description = (
        "Copies a project into a brand-new project owned by the forking user off the request "
        "path: it creates the destination project, duplicates the whole virtual filesystem, and "
        "records the source-to-fork relation. The resulting project is permanent; only the job "
        "tracking row is swept on retention."
    )

    def __init__(self):
        super().__init__(name="fork", interval_seconds=2)

    async def process(self, job: dict) -> dict:
        payload = job["payload"]
        source_uid = payload.get("source_project_uid", "")
        forked_by_uid = payload.get("forked_by_uid", "")
        title = (payload.get("title") or "").strip()

        source = get_table("projects").find_one(uid=source_uid)
        if not source:
            raise ValueError(f"source project not found: {source_uid}")
        user = get_table("users").find_one(uid=forked_by_uid)
        if not user:
            raise ValueError(f"forking user not found: {forked_by_uid}")
        if not title:
            raise ValueError("a destination title is required")

        new_uid, new_slug = create_content_item(
            "projects",
            "project",
            user,
            {
                "title": title,
                "description": source.get("description") or "",
                "release_date": source.get("release_date") or None,
                "demo_date": source.get("demo_date") or None,
                "project_type": source.get("project_type") or "software",
                "platforms": source.get("platforms") or "",
                "status": source.get("status") or "In Development",
                "is_private": 1 if source.get("is_private") else 0,
                "read_only": 0,
            },
            title,
            XP_PROJECT,
            "First Project",
            source.get("description") or "",
            None,
        )

        from devplacepy.services.audit import record as audit

        try:
            item_count = await asyncio.to_thread(
                self._copy_files, source_uid, new_uid, user, job["uid"]
            )
            record_fork(source_uid, new_uid, forked_by_uid)
        except Exception:
            self._rollback(new_uid)
            audit.record_system(
                "project.fork.failed",
                actor_kind="user",
                actor_uid=forked_by_uid,
                actor_username=user.get("username"),
                result="failure",
                target_type="project",
                target_uid=source_uid,
                target_label=source.get("title"),
                summary=f"fork of project {source.get('title')} by {user.get('username')} failed",
                links=[audit.source("project", source_uid, source.get("title")), audit.job(job["uid"])],
            )
            raise

        audit.record_system(
            "project.fork.complete",
            actor_kind="user",
            actor_uid=forked_by_uid,
            actor_username=user.get("username"),
            target_type="project",
            target_uid=new_uid,
            target_label=title,
            metadata={"item_count": item_count},
            summary=f"fork of project {source.get('title')} by {user.get('username')} completed as {title}",
            links=[
                audit.source("project", source_uid, source.get("title")),
                audit.destination("project", new_uid, title),
                audit.job(job["uid"]),
            ],
        )
        return {
            "project_uid": new_uid,
            "project_url": f"/projects/{new_slug}",
            "source_project_uid": source_uid,
            "item_count": item_count,
        }

    def _copy_files(
        self, source_uid: str, new_uid: str, user: dict, job_uid: str
    ) -> int:
        staging = FORK_STAGING_DIR / job_uid
        try:
            project_files.export_to_dir(source_uid, "", staging)
            return project_files.import_from_dir(
                new_uid, staging, user, skip_names=set()
            )
        finally:
            shutil.rmtree(staging, ignore_errors=True)

    def _rollback(self, new_uid: str) -> None:
        try:
            project_files.delete_all_project_files(new_uid)
            delete_fork_relations(new_uid)
            get_table("projects").delete(uid=new_uid)
        except Exception:
            logger.exception("fork rollback failed for %s", new_uid)

    def cleanup(self, job: dict) -> None:
        shutil.rmtree(FORK_STAGING_DIR / job["uid"], ignore_errors=True)
