import { NextRequest, NextResponse } from 'next/server';
import { getSession } from '@/lib/auth';
import { dbHelpers } from '@/lib/db';
import {
  SOURCE_EXTENSIONS,
  analyzeSourceFile,
  parseManifestDeps,
  findManifestPaths,
  buildAnalysisResponse,
  prioritizeSourceFiles,
  MAX_ANALYZE_FILES,
} from '@/lib/analyze-source';

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<any> {
  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 (res.status === 403 || res.status === 429) throw new Error('rate_limit');
    if (res.status === 404 && required) throw new Error('not_found');
    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();
}

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const repoUrl = searchParams.get('repoUrl');
  const branch = searchParams.get('branch') || 'main';
  const sourceLanguage = (searchParams.get('sourceLanguage') ?? '').trim();
  const additionalSourceLanguages = (searchParams.get('additionalSourceLanguages') ?? '')
    .split(',')
    .map(s => s.trim().toLowerCase())
    .filter(Boolean);

  if (!repoUrl) return NextResponse.json({ error: 'repoUrl is required' }, { status: 400 });
  if (!sourceLanguage) return NextResponse.json({ error: 'sourceLanguage is required' }, { status: 400 });

  const parsed = parseGitHubUrl(repoUrl);
  if (!parsed) return NextResponse.json({ error: 'Invalid GitHub URL' }, { status: 400 });

  const { owner, repo } = parsed;

  const session = await getSession(request.cookies.get('scriba.access_token')?.value);
  const ghAccount = session ? await dbHelpers.getGithubToken(session.user.id) : null;
  const token = ghAccount?.accessToken || process.env.GITHUB_TOKEN;

  try {
    const treeData = await ghFetch(`/repos/${owner}/${repo}/git/trees/${branch}?recursive=1`, token, true);
    if (!treeData?.tree) {
      return NextResponse.json(
        { error: `Branch "${branch}" not found or repository is empty. Check the branch name in your project settings.` },
        { status: 404 }
      );
    }

    const languagesData = await ghFetch(`/repos/${owner}/${repo}/languages`, token) || {};

    const allSourceExts = [
      ...(SOURCE_EXTENSIONS[sourceLanguage.toLowerCase()] ?? []),
      ...additionalSourceLanguages.flatMap(lang => SOURCE_EXTENSIONS[lang] ?? []),
    ];
    const sourceExts = [...new Set(allSourceExts)];
    const allFiles: any[] = treeData.tree.filter((f: any) => f.type === 'blob');
    const allDirs: any[] = treeData.tree.filter((f: any) => f.type === 'tree');
    const sourceFiles = allFiles.filter((f: any) =>
      sourceExts.some(ext => f.path.toLowerCase().endsWith(ext))
    );

    // Fetch + analyze source files. Prioritise program files (they carry the COPY/CALL/EXEC
    // dependencies) and scan up to MAX_ANALYZE_FILES, in small batches to respect rate limits.
    const toAnalyze = prioritizeSourceFiles(sourceFiles).slice(0, MAX_ANALYZE_FILES);
    const fileContents: { path: string; size: number; content: string | null }[] = [];
    const BATCH = 15;
    for (let i = 0; i < toAnalyze.length; i += BATCH) {
      const batch = await Promise.all(
        toAnalyze.slice(i, i + BATCH).map(async (f: any) => {
          const content = await ghFetchRaw(`/repos/${owner}/${repo}/contents/${f.path}?ref=${branch}`, token);
          return { path: f.path, size: f.size, content };
        }),
      );
      fileContents.push(...batch);
    }
    const analyzedFiles = fileContents
      .filter(f => f.content !== null)
      .map(f => analyzeSourceFile(f.path, f.content!, sourceLanguage));

    // Manifest file scanning
    const allFilePaths = allFiles.map((f: any) => f.path);
    const manifestPaths = findManifestPaths(allFilePaths, sourceLanguage);
    const manifestContents = await Promise.all(
      manifestPaths.map(async (p: string) => {
        const content = await ghFetchRaw(`/repos/${owner}/${repo}/contents/${p}?ref=${branch}`, token);
        return { path: p, content };
      })
    );
    const manifestDeps = manifestContents
      .filter(f => f.content !== null)
      .flatMap(f => parseManifestDeps(f.path, f.content!));

    const { lineMetrics, complexity, dependencies, risk } = buildAnalysisResponse(
      analyzedFiles, manifestDeps, sourceFiles.length
    );

    // LOC completeness: source files beyond the deep-analysis cap (or whose fetch failed) still
    // count toward the repo's line total, so the price reflects the whole codebase, not the sample.
    const BYTES_PER_LINE = 45;
    const analyzedPaths = new Set(analyzedFiles.map(f => f.path));
    const uncountedLines = sourceFiles
      .filter((f: any) => !analyzedPaths.has(f.path))
      .reduce((sum: number, f: any) => sum + Math.max(1, Math.round((f.size ?? 0) / BYTES_PER_LINE)), 0);
    // effectiveLoc (code lines, the pricing basis) extended to unanalyzed files using the
    // code-line ratio observed in the analyzed sample.
    const codeRatio = lineMetrics.totalLines > 0 ? lineMetrics.totalCodeLines / lineMetrics.totalLines : 0.66;
    const completeLineMetrics = {
      ...lineMetrics,
      totalLines: lineMetrics.totalLines + uncountedLines,
      effectiveLoc: Math.round(lineMetrics.totalCodeLines + uncountedLines * codeRatio),
    };

    // Language breakdown
    const totalBytes = Object.values(languagesData).reduce((a: number, b: any) => a + (b as number), 0);
    const extMap: Record<string, string[]> = {
      COBOL: ['.cbl', '.cob', '.cpy'], Java: ['.java'], Python: ['.py'],
      JavaScript: ['.js'], TypeScript: ['.ts', '.tsx'], JCL: ['.jcl'],
      Shell: ['.sh'], SQL: ['.sql'], PHP: ['.php'], Ruby: ['.rb'],
      Go: ['.go'], Rust: ['.rs'], 'C#': ['.cs'], 'C++': ['.cpp', '.cxx', '.cc'],
      C: ['.c'], Swift: ['.swift'], Kotlin: ['.kt'], Scala: ['.scala'], Dart: ['.dart'],
    };
    const languages = Object.entries(languagesData).map(([name, bytes]) => ({
      name,
      bytes: bytes as number,
      files: allFiles.filter((f: any) => (extMap[name] || []).some(ext => f.path.toLowerCase().endsWith(ext))).length,
      percentage: totalBytes > 0 ? Math.round(((bytes as number) / totalBytes) * 1000) / 10 : 0,
    })).sort((a, b) => b.bytes - a.bytes);

    const totalFiles = allFiles.length;
    const extCounts: Record<string, number> = {};
    for (const f of allFiles) {
      const ext = f.path.includes('.') ? '.' + f.path.split('.').pop().toLowerCase() : '(none)';
      extCounts[ext] = (extCounts[ext] || 0) + 1;
    }
    const extensions = Object.entries(extCounts)
      .map(([ext, count]) => ({ ext, count, percentage: Math.round((count / totalFiles) * 1000) / 10 }))
      .sort((a, b) => b.count - a.count);

    return NextResponse.json({
      scanResults: {
        totalFiles,
        totalSourceFiles: sourceFiles.length,
        totalDirectories: allDirs.length,
        totalSize: allFiles.reduce((sum: number, f: any) => sum + (f.size || 0), 0),
        ...completeLineMetrics,
        languages,
        extensions,
      },
      complexity,
      dependencies,
      risk,
      analyzedFileCount: analyzedFiles.length,
    });
  } catch (error: any) {
    if (error?.message === 'rate_limit') {
      return NextResponse.json(
        { error: 'GitHub API rate limit exceeded. Add a GITHUB_TOKEN to .env or wait a few minutes and try again.' },
        { status: 429 }
      );
    }
    if (error?.message === 'not_found') {
      return NextResponse.json({ error: 'Repository or branch not found.' }, { status: 404 });
    }
    console.error('Analysis error:', error);
    return NextResponse.json({ error: 'Failed to analyze repository' }, { status: 500 });
  }
}
