"""Detect and migrate the known legacy presign-only form upload shape."""
from __future__ import annotations

import re
from pathlib import Path
from typing import NamedTuple


class LegacyUploadCandidate(NamedTuple):
    route_path: str
    client_path: str
    bucket: str
    confirm_path: str


_PRESIGN_RE = re.compile(r"/presign(?:['\"`]|\b)")
_PUT_RE = re.compile(r"fetch\(\s*presign\.upload_url\s*,")
_BUCKET_RE = re.compile(r"(?:const|let)\s+BUCKET\s*=\s*['\"]([^'\"]+)['\"]")
_UPLOAD_FUNCTION_RE = re.compile(
    r"(?P<prefix>\s*)async function uploadFile\(file: File, documentType: string\): Promise<UploadedDocument> \{.*?"
    r"\n(?P=prefix)\}\n\n(?P=prefix)async function submit",
    re.S,
)


def _route_url(path: Path, root: Path) -> str:
    relative = path.relative_to(root / "server" / "api").as_posix()
    if relative.endswith(".post.ts"):
        relative = relative[:-len(".post.ts")]
    elif relative.endswith(".ts"):
        relative = relative[:-len(".ts")]
    return "/api/" + relative


def _client_files(root: Path) -> list[Path]:
    paths: list[Path] = []
    for base in (root / "composables", root / "components", root / "pages"):
        if base.is_dir():
            paths.extend(
                path
                for path in base.rglob("*")
                if path.is_file() and path.suffix in {".ts", ".tsx", ".vue", ".js", ".jsx"}
            )
    return paths


def find_legacy_upload_candidates(repo_path: str) -> list[LegacyUploadCandidate]:
    """Find public Nuxt upload routes that send presigned PUTs from the browser."""
    root = Path(repo_path)
    server_api = root / "server" / "api"
    if not server_api.is_dir():
        return []
    clients = _client_files(root)
    candidates: list[LegacyUploadCandidate] = []
    for route in server_api.rglob("*.post.ts"):
        try:
            source = route.read_text(encoding="utf-8")
        except OSError:
            continue
        bucket = _BUCKET_RE.search(source)
        if not bucket or not _PRESIGN_RE.search(source):
            continue
        route_url = _route_url(route, root)
        client = next(
            (
                path
                for path in clients
                if route_url in path.read_text(encoding="utf-8", errors="ignore")
                and _PUT_RE.search(path.read_text(encoding="utf-8", errors="ignore"))
            ),
            None,
        )
        if client is None:
            continue
        candidates.append(
            LegacyUploadCandidate(
                route_path=route.relative_to(root).as_posix(),
                client_path=client.relative_to(root).as_posix(),
                bucket=bucket.group(1),
                confirm_path=f"{route_url}/confirm",
            )
        )
    return candidates


def migrate_legacy_upload_candidate(repo_path: str, candidate: LegacyUploadCandidate) -> list[str]:
    """Move the recognized flow to a same-origin, server-side application-file upload."""
    root = Path(repo_path)
    route = root / candidate.route_path
    client = root / candidate.client_path
    route_source = route.read_text(encoding="utf-8")
    client_source = client.read_text(encoding="utf-8")
    changed: list[str] = []

    if "content_base64" not in route_source:
        route.write_text(
            """import { cloudFetch } from '~/server/utils/p2hs'

const ALLOWED_TYPES = ['application/pdf', 'image/jpeg', 'image/jpg', 'image/png']

export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  if (!body?.file_name || !body?.content_type || typeof body.content_base64 !== 'string') {
    throw createError({ statusCode: 400, message: 'file_name, content_type, and content_base64 are required.' })
  }
  if (!ALLOWED_TYPES.includes(body.content_type)) {
    throw createError({ statusCode: 400, message: 'File type not allowed. Use PDF, JPG, JPEG, or PNG.' })
  }
  const safeName = String(body.file_name).replace(/[^a-zA-Z0-9._-]/g, '_')
  const uploaded = await cloudFetch<{ file_id?: string; filename?: string }>(event, 'application-files', {
    method: 'POST',
    body: {
      filename: safeName,
      content_type: body.content_type,
      content_base64: body.content_base64,
    },
  })
  return {
    document_type: body.document_type || 'Other',
    file_id: uploaded.file_id,
    file_name: uploaded.filename || safeName,
    status: 'uploaded',
  }
})
""",
            encoding="utf-8",
            newline="\n",
        )
        changed.append(candidate.route_path)

    match = _UPLOAD_FUNCTION_RE.search(client_source)
    if match:
        prefix = match.group("prefix")
        replacement = f"""{prefix}async function uploadFile(file: File, documentType: string): Promise<UploadedDocument> {{
{prefix}  const bytes = new Uint8Array(await file.arrayBuffer())
{prefix}  let binary = ''
{prefix}  const chunkSize = 0x8000
{prefix}  for (let offset = 0; offset < bytes.length; offset += chunkSize) {{
{prefix}    binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize))
{prefix}  }}
{prefix}  return await $fetch<UploadedDocument>('/api/registration/upload', {{
{prefix}    method: 'POST',
{prefix}    body: {{
{prefix}      file_name: file.name,
{prefix}      content_type: file.type,
{prefix}      document_type: documentType,
{prefix}      content_base64: btoa(binary),
{prefix}    }},
{prefix}  }})
{prefix}}}

{prefix}async function submit"""
        client.write_text(
            client_source[:match.start()] + replacement + client_source[match.end():],
            encoding="utf-8",
            newline="\n",
        )
        changed.append(candidate.client_path)
    elif "content_base64" not in client_source:
        raise ValueError(f"legacy upload caller shape changed: {candidate.client_path}")
    return changed
