"""One-time (and re-runnable) task: cut template starter repos onto current primitives.

Templates are seed repos, not live projects (no Core pack-install/DB tracking to run
against — see migrate_runtime_primitives.py for that, which targets `projects`). This
only refreshes each template's `starter_branch` (the branch project_setup_pipeline.py
actually materializes into a new project, see its ``source.get("starter_branch")``
usage) with the current port-adapter catalog + legacy-path rewrites, then pushes only
if something actually changed. A fleet dry-run (2026-09-11) already confirmed every
active/inactive template passes validate_backend_repo before AND after this refresh,
so a normal run here is expected to be a no-op push-wise -- this exists so template
drift gets caught automatically the same way project drift does.

Usage:
    python scripts/migrate_template_primitives.py
    python scripts/migrate_template_primitives.py --template-id <uuid>
    python scripts/migrate_template_primitives.py --dry-run
"""
from __future__ import annotations

import argparse
import asyncio
import os
import sys
from pathlib import Path
from uuid import UUID

sys.path.insert(0, str(Path(__file__).parent.parent))

import structlog

logger = structlog.get_logger()

_COMMIT_MSG = "[p2hs] refresh template onto current platform primitives"


async def migrate_template_primitives(template_id: str | None = None, dry_run: bool = False) -> dict:
    from sqlalchemy import select

    from src.config import get_settings
    from src.core.database import async_session_factory
    from src.domain.codegen.backend_guard import migrate_legacy_app_paths, validate_backend_repo
    from src.domain.codegen.port_adapter import install_catalog_adapters
    from src.domain.templates.models import Template
    from src.integrations.github_client import GitHubAppClient
    from src.workers.repo_ops import prepare_working_tree, push_repo

    settings = get_settings()
    results = {"checked": 0, "updated": 0, "already_current": 0, "skipped": 0, "errors": 0}

    async with async_session_factory() as db:
        # No deleted_at/is_active filter: soft-deleted templates are often WIP work
        # the team is still actively developing, not abandoned -- see the fleet audit
        # (2026-09-11) that explicitly covered all 30 rows, not just the 25 active ones.
        stmt = select(Template)
        if template_id:
            stmt = stmt.where(Template.id == UUID(template_id))
        templates = list((await db.execute(stmt)).scalars().all())

    logger.info("migrate_template_primitives_start", total=len(templates))

    for tmpl in templates:
        results["checked"] += 1
        repo_full_name = tmpl.github_repo
        branch = tmpl.starter_branch or "starter"
        if not repo_full_name or not tmpl.github_installation_id:
            results["skipped"] += 1
            logger.info("migrate_template_primitives_no_repo", template_id=str(tmpl.id), name=tmpl.name)
            continue
        try:
            github = GitHubAppClient(installation_id=tmpl.github_installation_id)
            clone_url = await github.get_authenticated_clone_url(repo_full_name)
            repo_path = os.path.join(settings.repo_workspace, "templates", str(tmpl.id))
            await prepare_working_tree(repo_path, clone_url, branch=branch)

            install_catalog_adapters(
                repo_path, tmpl.framework, {"legacy_contact_submit": False}
            )
            migrate_legacy_app_paths(repo_path)

            add = await asyncio.create_subprocess_exec(
                "git", "add", "-A", cwd=repo_path,
                stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE,
            )
            await add.communicate()
            status = await asyncio.create_subprocess_exec(
                "git", "status", "--porcelain", cwd=repo_path,
                stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE,
            )
            out, _ = await status.communicate()
            if not out.strip():
                results["already_current"] += 1
                logger.info("migrate_template_primitives_current", template_id=str(tmpl.id), repo=repo_full_name)
                continue

            if dry_run:
                results["updated"] += 1
                logger.info(
                    "migrate_template_primitives_would_update",
                    template_id=str(tmpl.id), repo=repo_full_name,
                    changed_files=out.decode(errors="replace").strip().splitlines(),
                )
                continue

            env = {
                **os.environ,
                "GIT_AUTHOR_NAME": settings.git_committer_name,
                "GIT_AUTHOR_EMAIL": settings.git_committer_email,
                "GIT_COMMITTER_NAME": settings.git_committer_name,
                "GIT_COMMITTER_EMAIL": settings.git_committer_email,
            }
            commit = await asyncio.create_subprocess_exec(
                "git", "commit", "-m", _COMMIT_MSG, cwd=repo_path, env=env,
                stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE,
            )
            await commit.communicate()
            await push_repo(repo_path, branch, force_with_lease=True)

            validate_backend_repo(repo_path)  # fail loudly if the refresh itself broke something

            results["updated"] += 1
            logger.info("migrate_template_primitives_updated", template_id=str(tmpl.id), repo=repo_full_name, branch=branch)
        except Exception as exc:
            results["errors"] += 1
            logger.error(
                "migrate_template_primitives_error",
                template_id=str(tmpl.id), repo=repo_full_name, error=str(exc), exc_info=True,
            )

    logger.info("migrate_template_primitives_done", **results)
    return results


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--template-id", help="Only process this template's UUID")
    parser.add_argument("--dry-run", action="store_true", help="Report changes without committing/pushing")
    args = parser.parse_args()
    result = asyncio.run(migrate_template_primitives(template_id=args.template_id, dry_run=args.dry_run))
    print(result)


if __name__ == "__main__":
    main()
