import { NextRequest, NextResponse } from 'next/server';
import { dbHelpers, db, schema } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { eq, and } from 'drizzle-orm';
import {
  buildScribaDeployWorkflow,
  buildGitLabCiWorkflow,
  buildBitbucketPipelinesWorkflow,
  buildAzurePipelinesWorkflow,
  normalizeProjectLang,
} from '@/lib/cicd-workflow';
import {
  readToolingFilesFromAnalysis,
  readDocsAndTestArtifactsFromAnalysis,
  normalizeEngineOutputZipPath,
  applyMarkersToFiles,
  buildMarkerMetadata,
} from '@/lib/export-bundle';

type ConversionResultShape = {
  files?: Array<{ outputPath: string; content: string }>;
  metadata?: Record<string, unknown>;
};

function readConversionResult(config: Record<string, unknown>): ConversionResultShape | undefined {
  const raw = config.conversionResult;
  if (raw && typeof raw === 'object') return raw as ConversionResultShape;
  return undefined;
}

function strVal(data: unknown, key: string): string | undefined {
  if (!data || typeof data !== 'object') return undefined;
  const v = (data as Record<string, unknown>)[key];
  return typeof v === 'string' ? v : undefined;
}

function slug(s: string | null | undefined) {
  return (s ?? '').trim().toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9._-]/g, '') || 'unspecified';
}

type Provider = 'github' | 'gitlab' | 'bitbucket' | 'azure';

function detectProvider(repoUrl: string): Provider | null {
  if (repoUrl.includes('github.com')) return 'github';
  if (repoUrl.includes('gitlab.com')) return 'gitlab';
  if (repoUrl.includes('bitbucket.org')) return 'bitbucket';
  if (repoUrl.includes('dev.azure.com') || repoUrl.includes('visualstudio.com')) return 'azure';
  return null;
}

// ─── GitHub ──────────────────────────────────────────────────────────────────

function parseGitHubUrl(url: string): { owner: string; repo: string } | null {
  const match = url.match(/github\.com\/([^/]+)\/([^/.\s]+)/);
  if (!match) return null;
  return { owner: match[1], repo: match[2].replace(/\.git$/, '') };
}

async function ghApi(path: string, token: string, method = 'GET', body?: unknown) {
  const res = await fetch(`https://api.github.com${path}`, {
    method,
    headers: {
      Authorization: `Bearer ${token}`,
      Accept: 'application/vnd.github.v3+json',
      'Content-Type': 'application/json',
      'User-Agent': 'Scriba-AI',
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  const data: unknown = await res.json().catch(() => ({}));
  return { ok: res.ok, status: res.status, data, scopes: res.headers.get('x-oauth-scopes') ?? undefined };
}

async function pushToGitHub(
  token: string,
  repoUrl: string,
  files: Map<string, string>,
  branchName: string,
  commitMessage: string,
  prTitle: string,
  prBody: string,
): Promise<{ prUrl: string; branch: string }> {
  const parsed = parseGitHubUrl(repoUrl);
  if (!parsed) throw Object.assign(new Error('Invalid GitHub URL'), { status: 400 });
  const { owner, repo } = parsed;

  const repoInfo = await ghApi(`/repos/${owner}/${repo}`, token);
  if (!repoInfo.ok) throw Object.assign(new Error(`Cannot access repository: ${strVal(repoInfo.data, 'message') ?? repoInfo.status}`), { status: 400 });
  if ((repoInfo.data as Record<string, unknown>).archived === true)
    throw Object.assign(new Error(`Repository ${owner}/${repo} is archived on GitHub.`), { status: 400 });

  const tokenScopes = (repoInfo.scopes ?? '').split(',').map((s: string) => s.trim()).filter(Boolean);
  const hasWriteScope = tokenScopes.length === 0 || tokenScopes.includes('repo') || tokenScopes.includes('public_repo');
  if (!hasWriteScope) throw Object.assign(new Error('Your GitHub token does not have write access. Reconnect GitHub in Settings.'), { status: 403 });

  const repoPerms = (repoInfo.data as Record<string, unknown>).permissions as Record<string, unknown> | null;
  if (repoPerms && repoPerms.push !== true && repoPerms.admin !== true)
    throw Object.assign(new Error(`Your account does not have push access to ${owner}/${repo}.`), { status: 403 });

  const defaultBranch: string = strVal(repoInfo.data, 'default_branch') ?? 'main';

  const refInfo = await ghApi(`/repos/${owner}/${repo}/git/ref/heads/${defaultBranch}`, token);
  if (!refInfo.ok) throw Object.assign(new Error(`Cannot get branch ref: ${strVal(refInfo.data, 'message') ?? refInfo.status}`), { status: 400 });
  const baseSha = (() => {
    const obj = (refInfo.data as Record<string, unknown>).object;
    return obj && typeof obj === 'object' ? String((obj as Record<string, unknown>).sha ?? '') : '';
  })();
  if (!baseSha) throw new Error('Cannot read default branch SHA');

  const commitInfo = await ghApi(`/repos/${owner}/${repo}/git/commits/${baseSha}`, token);
  if (!commitInfo.ok) throw new Error(`Cannot get commit info: ${strVal(commitInfo.data, 'message') ?? commitInfo.status}`);
  const baseTreeSha = (() => {
    const tree = (commitInfo.data as Record<string, unknown>).tree;
    return tree && typeof tree === 'object' ? String((tree as Record<string, unknown>).sha ?? '') : '';
  })();
  if (!baseTreeSha) throw new Error('Cannot read base tree SHA');

  const probeRes = await ghApi(`/repos/${owner}/${repo}/git/blobs`, token, 'POST', { content: 'scriba-probe', encoding: 'utf-8' });
  if (!probeRes.ok) throw Object.assign(new Error(
    probeRes.status === 404
      ? `Cannot write to ${owner}/${repo}. The GitHub App may not be approved for the "${owner}" org.`
      : `Write access check failed: ${strVal(probeRes.data, 'message') ?? probeRes.status}`
  ), { status: 403 });

  const treeItems = [...files.entries()]
    .filter(([p, c]) => p.length > 0 && typeof c === 'string')
    .map(([p, c]) => ({ path: p, mode: '100644', type: 'blob', content: c }));

  const treeRes = await ghApi(`/repos/${owner}/${repo}/git/trees`, token, 'POST', { base_tree: baseTreeSha, tree: treeItems });
  if (!treeRes.ok) throw new Error(`Failed to create tree: ${strVal(treeRes.data, 'message') ?? treeRes.status}`);
  const treeSha = strVal(treeRes.data, 'sha');
  if (!treeSha) throw new Error('GitHub tree response missing sha');

  const commitRes = await ghApi(`/repos/${owner}/${repo}/git/commits`, token, 'POST', { message: commitMessage, tree: treeSha, parents: [baseSha] });
  if (!commitRes.ok) throw new Error(`Failed to create commit: ${strVal(commitRes.data, 'message') ?? commitRes.status}`);
  const commitSha = strVal(commitRes.data, 'sha');
  if (!commitSha) throw new Error('Commit response missing sha');

  const existingBranch = await ghApi(`/repos/${owner}/${repo}/git/ref/heads/${branchName}`, token);
  if (existingBranch.ok) {
    await ghApi(`/repos/${owner}/${repo}/git/refs/heads/${branchName}`, token, 'PATCH', { sha: commitSha, force: true });
  } else {
    const createRef = await ghApi(`/repos/${owner}/${repo}/git/refs`, token, 'POST', { ref: `refs/heads/${branchName}`, sha: commitSha });
    if (!createRef.ok) throw new Error(`Failed to create branch: ${strVal(createRef.data, 'message') ?? createRef.status}`);
  }

  const existingPrs = await ghApi(`/repos/${owner}/${repo}/pulls?head=${owner}:${branchName}&state=open`, token);
  if (existingPrs.ok && Array.isArray(existingPrs.data) && existingPrs.data.length > 0) {
    const url = strVal(existingPrs.data[0], 'html_url');
    if (url) return { prUrl: url, branch: branchName };
  }

  const prRes = await ghApi(`/repos/${owner}/${repo}/pulls`, token, 'POST', { title: prTitle, body: prBody, head: branchName, base: defaultBranch });
  if (!prRes.ok) throw new Error(`Failed to create PR: ${strVal(prRes.data, 'message') ?? prRes.status}`);
  const prUrl = strVal(prRes.data, 'html_url');
  if (!prUrl) throw new Error('PR created but response missing html_url');
  return { prUrl, branch: branchName };
}

// ─── GitLab ───────────────────────────────────────────────────────────────────

function parseGitLabUrl(url: string): string | null {
  const match = url.match(/gitlab\.com\/([^?#]+)/);
  if (!match) return null;
  return match[1].replace(/\.git$/, '').replace(/\/$/, '');
}

async function glApi(path: string, token: string, method = 'GET', body?: unknown) {
  const res = await fetch(`https://gitlab.com/api/v4${path}`, {
    method,
    headers: { 'PRIVATE-TOKEN': token, 'Content-Type': 'application/json' },
    body: body ? JSON.stringify(body) : undefined,
  });
  const data: unknown = await res.json().catch(() => ({}));
  return { ok: res.ok, status: res.status, data };
}

async function pushToGitLab(
  token: string,
  repoUrl: string,
  files: Map<string, string>,
  branchName: string,
  commitMessage: string,
  mrTitle: string,
  mrBody: string,
): Promise<{ prUrl: string; branch: string }> {
  const projectPath = parseGitLabUrl(repoUrl);
  if (!projectPath) throw Object.assign(new Error('Invalid GitLab URL'), { status: 400 });
  const enc = encodeURIComponent(projectPath);

  // Get project info (default branch)
  const projInfo = await glApi(`/projects/${enc}`, token);
  if (!projInfo.ok) throw Object.assign(new Error(`Cannot access GitLab repository: ${strVal(projInfo.data, 'message') ?? projInfo.status}`), { status: 400 });
  const defaultBranch: string = strVal(projInfo.data, 'default_branch') ?? 'main';

  // Delete branch if it already exists — ensures a clean re-push
  const existingBranch = await glApi(`/projects/${enc}/repository/branches/${encodeURIComponent(branchName)}`, token);
  if (existingBranch.ok) {
    await glApi(`/projects/${enc}/repository/branches/${encodeURIComponent(branchName)}`, token, 'DELETE');
  }

  // Fetch existing file paths on the default branch so we can use 'update' vs 'create'
  // (GitLab rejects 'create' if the file already exists on the start_branch)
  const treeRes = await glApi(`/projects/${enc}/repository/tree?recursive=true&per_page=500&ref=${encodeURIComponent(defaultBranch)}`, token);
  const existingPaths = new Set<string>();
  if (treeRes.ok && Array.isArray(treeRes.data)) {
    for (const item of treeRes.data as Array<{ type: string; path: string }>) {
      if (item.type === 'blob') existingPaths.add(item.path);
    }
  }

  // Build actions array — 'update' for files that already exist, 'create' for new ones
  const actions = [...files.entries()].map(([filePath, content]) => {
    const normalized = filePath.startsWith('/') ? filePath.slice(1) : filePath;
    return {
      action: existingPaths.has(normalized) ? 'update' : 'create',
      file_path: `/${normalized}`,
      content,
      encoding: 'text',
    };
  });

  // Create branch + commit in one call (start_branch creates the branch from default)
  const commitRes = await glApi(`/projects/${enc}/repository/commits`, token, 'POST', {
    branch: branchName,
    start_branch: defaultBranch,
    commit_message: commitMessage,
    actions,
  });
  if (!commitRes.ok) {
    const msg = strVal(commitRes.data, 'message') ?? commitRes.status;
    throw new Error(`Failed to push to GitLab: ${msg}`);
  }

  // Check for existing open MR
  const existingMrs = await glApi(`/projects/${enc}/merge_requests?state=opened&source_branch=${encodeURIComponent(branchName)}`, token);
  if (existingMrs.ok && Array.isArray(existingMrs.data) && existingMrs.data.length > 0) {
    const url = strVal(existingMrs.data[0], 'web_url');
    if (url) return { prUrl: url, branch: branchName };
  }

  const mrRes = await glApi(`/projects/${enc}/merge_requests`, token, 'POST', {
    source_branch: branchName,
    target_branch: defaultBranch,
    title: mrTitle,
    description: mrBody,
    remove_source_branch: false,
  });
  if (!mrRes.ok) throw new Error(`Failed to create MR: ${strVal(mrRes.data, 'message') ?? mrRes.status}`);
  const mrUrl = strVal(mrRes.data, 'web_url');
  if (!mrUrl) throw new Error('MR created but response missing web_url');
  return { prUrl: mrUrl, branch: branchName };
}

// ─── Bitbucket ───────────────────────────────────────────────────────────────

function parseBitbucketUrl(url: string): { workspace: string; repo: string } | null {
  const match = url.match(/bitbucket\.org\/([^/]+)\/([^/?#\s]+)/);
  if (!match) return null;
  return { workspace: match[1], repo: match[2].replace(/\.git$/, '') };
}

function bbAuthHeader(token: string): string {
  return token.includes(':') ? `Basic ${Buffer.from(token).toString('base64')}` : `Bearer ${token}`;
}

async function bbApi(url: string, token: string, method = 'GET', body?: unknown) {
  const isForm = method !== 'GET' && body instanceof FormData;
  const res = await fetch(url, {
    method,
    headers: isForm ? { Authorization: bbAuthHeader(token) } : { Authorization: bbAuthHeader(token), 'Content-Type': 'application/json' },
    body: body ? (isForm ? (body as FormData) : JSON.stringify(body)) : undefined,
  });
  const data: unknown = await res.json().catch(() => ({}));
  return { ok: res.ok, status: res.status, data };
}

async function pushToBitbucket(
  token: string,
  repoUrl: string,
  files: Map<string, string>,
  branchName: string,
  commitMessage: string,
  prTitle: string,
  prBody: string,
): Promise<{ prUrl: string; branch: string }> {
  const parsed = parseBitbucketUrl(repoUrl);
  if (!parsed) throw Object.assign(new Error('Invalid Bitbucket URL'), { status: 400 });
  const { workspace, repo } = parsed;
  const base = `https://api.bitbucket.org/2.0/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repo)}`;

  // Get default branch
  const repoInfo = await bbApi(base, token);
  if (!repoInfo.ok) throw Object.assign(new Error(`Cannot access Bitbucket repository: ${strVal(repoInfo.data, 'error') ?? repoInfo.status}`), { status: 400 });
  const mainBranch: string = ((repoInfo.data as Record<string, unknown>).mainbranch as Record<string, unknown>)?.name as string ?? 'main';

  // Use Bitbucket src API to commit files (multipart form)
  const form = new FormData();
  form.set('branch', branchName);
  form.set('message', commitMessage);
  form.set('parents', mainBranch);
  for (const [filePath, content] of files) {
    form.set(filePath, content);
  }

  const srcRes = await fetch(`${base}/src`, {
    method: 'POST',
    headers: { Authorization: bbAuthHeader(token) },
    body: form,
  });
  if (!srcRes.ok) {
    const errText = await srcRes.text().catch(() => String(srcRes.status));
    throw new Error(`Failed to push to Bitbucket: ${errText.slice(0, 200)}`);
  }

  // Check for existing open PR
  const existingPrs = await bbApi(`${base}/pullrequests?state=OPEN&q=source.branch.name="${encodeURIComponent(branchName)}"`, token);
  if (existingPrs.ok) {
    const values = (existingPrs.data as Record<string, unknown>).values;
    if (Array.isArray(values) && values.length > 0) {
      const url = (values[0] as Record<string, unknown>).links as Record<string, unknown>;
      const htmlLink = url?.html as Record<string, unknown>;
      const href = htmlLink?.href;
      if (typeof href === 'string') return { prUrl: href, branch: branchName };
    }
  }

  const prRes = await bbApi(`${base}/pullrequests`, token, 'POST', {
    title: prTitle,
    description: prBody,
    source: { branch: { name: branchName } },
    destination: { branch: { name: mainBranch } },
    close_source_branch: false,
  });
  if (!prRes.ok) throw new Error(`Failed to create PR: ${strVal(prRes.data, 'error') ?? prRes.status}`);
  const prLinks = (prRes.data as Record<string, unknown>).links as Record<string, unknown>;
  const prUrl = (prLinks?.html as Record<string, unknown>)?.href;
  if (typeof prUrl !== 'string') throw new Error('PR created but response missing href');
  return { prUrl, branch: branchName };
}

// ─── Azure DevOps ─────────────────────────────────────────────────────────────

function parseAzureUrl(url: string): { org: string; project: string; repo: string } | null {
  const match = url.match(/dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/([^/?#\s]+)/);
  if (!match) return null;
  return { org: match[1], project: match[2], repo: match[3] };
}

async function azApi(url: string, token: string, method = 'GET', body?: unknown) {
  const basicAuth = Buffer.from(`:${token}`).toString('base64');
  const res = await fetch(url, {
    method,
    headers: { Authorization: `Basic ${basicAuth}`, 'Content-Type': 'application/json' },
    body: body ? JSON.stringify(body) : undefined,
  });
  const data: unknown = await res.json().catch(() => ({}));
  return { ok: res.ok, status: res.status, data };
}

async function pushToAzure(
  token: string,
  repoUrl: string,
  files: Map<string, string>,
  branchName: string,
  commitMessage: string,
  prTitle: string,
  prBody: string,
): Promise<{ prUrl: string; branch: string }> {
  const parsed = parseAzureUrl(repoUrl);
  if (!parsed) throw Object.assign(new Error('Invalid Azure DevOps URL'), { status: 400 });
  const { org, project, repo } = parsed;
  const base = `https://dev.azure.com/${encodeURIComponent(org)}/${encodeURIComponent(project)}/_apis/git/repositories/${encodeURIComponent(repo)}`;

  // Get default branch and its tip SHA
  const repoInfo = await azApi(`${base}?api-version=7.0`, token);
  if (!repoInfo.ok) throw Object.assign(new Error(`Cannot access Azure DevOps repository: ${repoInfo.status}`), { status: 400 });
  const defaultBranch: string = (strVal(repoInfo.data, 'defaultBranch') ?? 'refs/heads/main').replace('refs/heads/', '');

  const refsRes = await azApi(`${base}/refs?filter=heads/${encodeURIComponent(defaultBranch)}&api-version=7.0`, token);
  if (!refsRes.ok) throw new Error(`Cannot get branch ref: ${refsRes.status}`);
  const refValues = (refsRes.data as Record<string, unknown>).value;
  const baseSha: string = Array.isArray(refValues) && refValues.length > 0
    ? String((refValues[0] as Record<string, unknown>).objectId ?? '')
    : '';
  if (!baseSha) throw new Error('Cannot read default branch SHA');

  // Build changes array
  const changes = [...files.entries()].map(([filePath, content]) => ({
    changeType: 'add',
    item: { path: filePath.startsWith('/') ? filePath : `/${filePath}` },
    newContent: { content, contentType: 'rawtext' },
  }));

  // Create push (creates branch + commit in one call)
  const pushRes = await azApi(`${base}/pushes?api-version=7.0`, token, 'POST', {
    refUpdates: [{ name: `refs/heads/${branchName}`, oldObjectId: '0000000000000000000000000000000000000000' }],
    commits: [{ comment: commitMessage, changes }],
  });

  if (!pushRes.ok) {
    const msg = strVal(pushRes.data, 'message') ?? (pushRes.data as Record<string, unknown>)?.typeKey ?? pushRes.status;
    // If branch already exists, update it
    if (String(msg).includes('TF401179') || pushRes.status === 400) {
      // Branch exists — create update push instead
      const updatePush = await azApi(`${base}/pushes?api-version=7.0`, token, 'POST', {
        refUpdates: [{ name: `refs/heads/${branchName}`, oldObjectId: baseSha }],
        commits: [{ comment: commitMessage, changes }],
      });
      if (!updatePush.ok) throw new Error(`Failed to push to Azure DevOps: ${strVal(updatePush.data, 'message') ?? updatePush.status}`);
    } else {
      throw new Error(`Failed to push to Azure DevOps: ${msg}`);
    }
  }

  // Check for existing open PR
  const existingPrs = await azApi(`${base}/pullrequests?searchCriteria.sourceRefName=refs/heads/${encodeURIComponent(branchName)}&searchCriteria.status=active&api-version=7.0`, token);
  if (existingPrs.ok) {
    const values = (existingPrs.data as Record<string, unknown>).value;
    if (Array.isArray(values) && values.length > 0) {
      const pr = values[0] as Record<string, unknown>;
      const links = pr._links as Record<string, unknown>;
      const webLink = links?.web as Record<string, unknown>;
      if (typeof webLink?.href === 'string') return { prUrl: webLink.href, branch: branchName };
    }
  }

  const prRes = await azApi(`${base}/pullrequests?api-version=7.0`, token, 'POST', {
    title: prTitle,
    description: prBody,
    sourceRefName: `refs/heads/${branchName}`,
    targetRefName: `refs/heads/${defaultBranch}`,
  });
  if (!prRes.ok) throw new Error(`Failed to create PR: ${strVal(prRes.data, 'message') ?? prRes.status}`);
  const prLinks = (prRes.data as Record<string, unknown>)._links as Record<string, unknown>;
  const prUrl = (prLinks?.web as Record<string, unknown>)?.href;
  if (typeof prUrl !== 'string') throw new Error('PR created but response missing href');
  return { prUrl, branch: branchName };
}

// ─── Main handler ─────────────────────────────────────────────────────────────

export async function POST(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const withCicd = request.nextUrl.searchParams.get('withCicd') === 'true';

  const session = await getSession(request.cookies.get('scriba.access_token')?.value);
  if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });

  const project = await dbHelpers.getProject(id);
  if (!project) return NextResponse.json({ error: 'Project not found' }, { status: 404 });
  if (project.userId !== session.user.id) return NextResponse.json({ error: 'Forbidden' }, { status: 403 });

  const config = (project.config ?? {}) as Record<string, unknown>;
  const conversionResult = readConversionResult(config);
  const engineFiles: Array<{ outputPath: string; content: string }> = conversionResult?.files ?? [];

  if (engineFiles.length === 0) {
    return NextResponse.json({ error: 'No converted files available. Complete the migration first.' }, { status: 400 });
  }

  const repoUrl = project.repoUrl ?? '';
  const provider = detectProvider(repoUrl);
  if (!provider) {
    return NextResponse.json({ error: 'Invalid or missing repository URL on this project.' }, { status: 400 });
  }

  // Fetch access token for the detected provider
  const account = await db.query.accounts.findFirst({
    where: and(eq(schema.accounts.userId, session.user.id), eq(schema.accounts.providerId, provider)),
  });
  if (!account?.accessToken) {
    const providerName = provider === 'github' ? 'GitHub' : provider === 'gitlab' ? 'GitLab' : provider === 'bitbucket' ? 'Bitbucket' : 'Azure DevOps';
    return NextResponse.json({ error: `No ${providerName} account connected. Connect it in Settings first.` }, { status: 400 });
  }
  const token = account.accessToken;

  const srcLang = slug(project.sourceLanguage);
  const tgtLang = slug(project.targetLanguage);
  const branchName = `scriba/migrate-${srcLang}-to-${tgtLang}`;

  // Build file map
  const pathToContent = new Map<string, string>();
  for (const f of engineFiles) {
    pathToContent.set(normalizeEngineOutputZipPath(f.outputPath), f.content ?? '');
  }
  const toolingFiles = readToolingFilesFromAnalysis(config);
  let toolingHasCi = false;
  for (const tf of toolingFiles) {
    const norm = tf.path.replace(/\\/g, '/');
    if ((norm.includes('.github/workflows/') || norm.includes('.gitlab-ci') || norm.includes('bitbucket-pipelines') || norm.includes('azure-pipelines')) && norm.includes('scriba')) toolingHasCi = true;
    pathToContent.set(tf.path.startsWith('/') ? tf.path.slice(1) : tf.path, tf.content);
  }
  for (const row of readDocsAndTestArtifactsFromAnalysis(config)) {
    pathToContent.set(row.path.startsWith('/') ? row.path.slice(1) : row.path, row.content);
  }

  const addDocker = config.addDocker !== false;
  const cicdInput = {
    projectName: project.name ?? 'project',
    sourceLanguage: project.sourceLanguage,
    targetLanguage: project.targetLanguage,
    config,
    includeOptionalDocker: addDocker,
  };

  if (withCicd && !toolingHasCi) {
    if (provider === 'github') {
      pathToContent.set('.github/workflows/scriba-deploy.yml', buildScribaDeployWorkflow(cicdInput));
    } else if (provider === 'gitlab') {
      pathToContent.set('.gitlab-ci.yml', buildGitLabCiWorkflow(cicdInput));
    } else if (provider === 'bitbucket') {
      pathToContent.set('bitbucket-pipelines.yml', buildBitbucketPipelinesWorkflow(cicdInput));
    } else if (provider === 'azure') {
      pathToContent.set('azure-pipelines.yml', buildAzurePipelinesWorkflow(cicdInput));
    }
  }

  const markerMeta = buildMarkerMetadata(project);
  const { files: markedFiles, manifestContent } = applyMarkersToFiles(
    [...pathToContent.entries()].map(([path, content]) => ({ path, content })),
    markerMeta
  );
  pathToContent.clear();
  for (const file of markedFiles) {
    pathToContent.set(file.path, file.content);
  }
  pathToContent.set('SCRIBA_MANIFEST.json', manifestContent);

  if (pathToContent.size === 0) {
    return NextResponse.json({ error: 'No valid files to push after processing converted output.' }, { status: 400 });
  }

  const meta = conversionResult?.metadata ?? {};
  let accuracy = project.accuracy ?? 0;
  const rawAcc = meta.accuracy;
  if (typeof rawAcc === 'number' && Number.isFinite(rawAcc)) accuracy = rawAcc;
  else if (typeof rawAcc === 'string') { const p = parseFloat(rawAcc); if (Number.isFinite(p)) accuracy = p; }

  const commitMessage = `feat: migrate ${srcLang} to ${tgtLang} (${engineFiles.length} files, ${accuracy}% parity)\n\nMigrated by Scriba Engine. See SCRIBA-MIGRATION.md for details.`;
  const prTitle = `feat: migrate ${srcLang} → ${tgtLang} (${engineFiles.length} files, ${accuracy}% parity)`;
  const prBody =
    `## Scriba Migration: ${project.sourceLanguage} → ${project.targetLanguage}\n\n` +
    `Automatically migrated by **Scriba Engine**.\n\n` +
    `| Metric | Value |\n|---|---|\n` +
    `| Source files | ${engineFiles.length} |\n` +
    `| Accuracy | ${accuracy}% |\n` +
    `| Test coverage | ${project.testCoverage ?? 0}% |\n` +
    (withCicd ? `\nCI/CD pipeline included — merge to trigger staging deployment.\n` : '');

  try {
    let result: { prUrl: string; branch: string };
    if (provider === 'github') {
      result = await pushToGitHub(token, repoUrl, pathToContent, branchName, commitMessage, prTitle, prBody);
    } else if (provider === 'gitlab') {
      result = await pushToGitLab(token, repoUrl, pathToContent, branchName, commitMessage, prTitle, prBody);
    } else if (provider === 'bitbucket') {
      result = await pushToBitbucket(token, repoUrl, pathToContent, branchName, commitMessage, prTitle, prBody);
    } else {
      result = await pushToAzure(token, repoUrl, pathToContent, branchName, commitMessage, prTitle, prBody);
    }
    return NextResponse.json({ prUrl: result.prUrl, branch: result.branch, withCicd });
  } catch (err: unknown) {
    const msg = err instanceof Error ? err.message : 'Push failed';
    const status = (err as Record<string, unknown>)?.status;
    return NextResponse.json({ error: msg }, { status: typeof status === 'number' ? status : 500 });
  }
}
