/**
 * Fetch manifest files from a remote Git repository (GitHub-first; other hosts when token available).
 */
import { findManifestPaths, MANIFEST_FILES } from '@/lib/analyze-source';
import { buildEngineUpstreamHeaders } from '@/lib/engine-upstream-headers';

export type RepoManifest = { path: string; content: string };

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] };
}

async function ghFetch(path: string, token?: string, required = false): Promise<unknown> {
  const headers: Record<string, string> = {
    Accept: 'application/vnd.github.v3+json',
    'User-Agent': 'Scriba-AI',
  };
  if (token) headers.Authorization = `Bearer ${token}`;
  const res = await fetch(`https://api.github.com${path}`, { headers, cache: 'no-store' });
  if (!res.ok) {
    if (required) throw new Error(res.status === 404 ? 'not_found' : 'github_error');
    return null;
  }
  return res.json();
}

async function ghFetchRaw(path: string, token?: string): Promise<string | null> {
  const headers: Record<string, string> = {
    Accept: 'application/vnd.github.v3.raw',
    'User-Agent': 'Scriba-AI',
  };
  if (token) headers.Authorization = `Bearer ${token}`;
  const res = await fetch(`https://api.github.com${path}`, { headers, cache: 'no-store' });
  if (!res.ok) return null;
  return res.text();
}

/** Collect manifest paths + a capped file path list for stack heuristics. */
export async function fetchGitHubManifests(
  repoUrl: string,
  branch: string,
  sourceLanguage: string,
  token?: string,
): Promise<{ manifests: RepoManifest[]; filePaths: string[] }> {
  const parsed = parseGitHubUrl(repoUrl);
  if (!parsed) throw new Error('invalid_github_url');
  const { owner, repo } = parsed;

  const treeData = (await ghFetch(
    `/repos/${owner}/${repo}/git/trees/${encodeURIComponent(branch)}?recursive=1`,
    token,
    true,
  )) as { tree?: { type: string; path: string }[] };

  const tree = treeData?.tree ?? [];
  const allFilePaths = tree.filter((f) => f.type === 'blob').map((f) => f.path);
  const manifestPaths = findManifestPaths(allFilePaths, sourceLanguage);

  const manifests: RepoManifest[] = [];
  for (const p of manifestPaths) {
    const content = await ghFetchRaw(
      `/repos/${owner}/${repo}/contents/${encodeURIComponent(p)}?ref=${encodeURIComponent(branch)}`,
      token,
    );
    if (content) manifests.push({ path: p, content });
  }

  return { manifests, filePaths: allFilePaths.slice(0, 5000) };
}

const ENGINE = process.env['SCRIBA_ENGINE_URL'] ?? 'http://localhost:3100';

export type StackDetectResponse = {
  framework: string | null;
  version: string | null;
  signals: string[];
};

export async function detectStackViaEngine(
  sourceLanguage: string,
  manifests: RepoManifest[],
  filePaths: string[],
): Promise<StackDetectResponse | null> {
  const body = JSON.stringify({ sourceLanguage, manifests, filePaths });
  const headers = {
    'Content-Type': 'application/json',
    ...buildEngineUpstreamHeaders('POST', { rawBody: body }),
  };
  const res = await fetch(`${ENGINE}/scriba/detect-stack`, {
    method: 'POST',
    headers,
    body,
    cache: 'no-store',
  });
  if (!res.ok) return null;
  return res.json() as Promise<StackDetectResponse>;
}

/** Extra manifest patterns when language id does not have a dedicated MANIFEST_FILES entry. */
export function manifestPatternsForLanguage(lang: string): string[] {
  return MANIFEST_FILES[lang.toLowerCase()] ?? ['package.json', 'composer.json', 'pom.xml', 'pyproject.toml'];
}
