"""Migrate existing generated apps from presign-only form uploads.

This is intentionally narrower than a general codegen rerun.  It updates only
the known generated Nuxt shape whose browser PUT never confirms the Cloud file.
Ambiguous/custom upload implementations are reported and left untouched.
"""
from __future__ import annotations

import structlog
from sqlalchemy import and_, select

logger = structlog.get_logger()

_MAX_PER_RUN = 100
_COMMIT_MSG = "[p2hs] migrate legacy form uploads to confirmed files"


async def migrate_legacy_form_uploads(ctx: dict, project_id: str | None = None) -> dict:
    # The task is also invoked directly by maintenance tooling and tests, where
    # the normal worker entrypoint has not populated SQLAlchemy's model registry.
    # Keep the task self-contained so preview enqueueing cannot fail after a
    # successful repository push with NoReferencedTableError.
    from src.core.orm_import import import_all_orm_models

    import_all_orm_models()

    from src.config import get_settings
    from src.core.database import async_session_factory
    from src.domain.codegen.legacy_form_uploads import (
        find_legacy_upload_candidates,
        migrate_legacy_upload_candidate,
    )
    from src.domain.projects.models import Project, ProjectRepo
    from src.integrations.github_client import GitHubAppClient
    from src.integrations.github_installation import resolve_installation_id_for_workspace
    from src.workers.repo_ops import get_project_repo_path, prepare_working_tree, push_repo
    from src.workers.tasks.migrate_runtime_primitives import (
        _commit_all,
        _enqueue_preview_for_sha,
        _head_sha,
    )

    db_factory = ctx.get("db_factory") or async_session_factory
    settings = get_settings()
    results = {"checked": 0, "updated": 0, "skipped": 0, "ambiguous": 0, "errors": 0}

    async with db_factory() as db:
        stmt = (
            select(Project, ProjectRepo)
            .join(ProjectRepo, Project.id == ProjectRepo.project_id)
            .where(
                and_(
                    Project.status.in_(["ready", "creating"]),
                    Project.deleted_at.is_(None),
                    ProjectRepo.repo_full_name.isnot(None),
                )
            )
        )
        if project_id:
            from uuid import UUID
            stmt = stmt.where(Project.id == UUID(project_id))
        rows = list((await db.execute(stmt.limit(_MAX_PER_RUN))).all())

    for project, repo in rows:
        results["checked"] += 1
        pid = project.id
        try:
            installation_id = await resolve_installation_id_for_workspace(
                db_factory, project.workspace_id
            )
            github = GitHubAppClient(installation_id=installation_id)
            clone_url = await github.get_authenticated_clone_url(repo.repo_full_name)
            repo_path = get_project_repo_path(settings.repo_workspace, project.workspace_id, pid)
            await prepare_working_tree(
                repo_path,
                clone_url,
                branch=getattr(repo, "preview_branch", None) or "preview",
                heal_missing_remote_from=getattr(repo, "default_branch", None) or "main",
            )

            candidates = find_legacy_upload_candidates(repo_path)
            if not candidates:
                results["skipped"] += 1
                continue

            changed: list[str] = []
            for candidate in candidates:
                try:
                    changed.extend(migrate_legacy_upload_candidate(repo_path, candidate))
                except ValueError:
                    results["ambiguous"] += 1
                    logger.warning(
                        "legacy_form_upload_migration_ambiguous",
                        project_id=str(pid),
                        client_path=candidate.client_path,
                    )

            if not changed:
                results["skipped"] += 1
                continue
            committed = await _commit_all(
                repo_path,
                _COMMIT_MSG,
                git_name=settings.git_committer_name,
                git_email=settings.git_committer_email,
            )
            sha = await _head_sha(repo_path)
            if committed:
                await push_repo(
                    repo_path,
                    getattr(repo, "preview_branch", None) or "preview",
                    force_with_lease=True,
                )
                if sha:
                    await _enqueue_preview_for_sha(db_factory, pid, sha)
            results["updated"] += 1
            logger.info(
                "legacy_form_upload_migrated",
                project_id=str(pid),
                changed_files=sorted(set(changed)),
                commit_sha=(sha or "")[:12],
            )
        except Exception as exc:
            results["errors"] += 1
            logger.error(
                "legacy_form_upload_migration_error",
                project_id=str(pid),
                error=str(exc),
                exc_info=True,
            )
    return results
