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

function parseAzureUrl(url: string): { org: string; project: string; repo: string } | null {
  // https://dev.azure.com/{org}/{project}/_git/{repo}
  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 azFetch(url: string, token: string): Promise<any> {
  const basicAuth = Buffer.from(`:${token}`).toString('base64');
  const res = await fetch(url, { headers: { Authorization: `Basic ${basicAuth}` }, cache: 'no-store' });
  if (!res.ok) {
    if (res.status === 429) throw new Error('rate_limit');
    if (res.status === 404) throw new Error('not_found');
    return null;
  }
  return res.json();
}

async function azFetchRaw(url: string, token: string): Promise<string | null> {
  const basicAuth = Buffer.from(`:${token}`).toString('base64');
  const res = await fetch(url, { headers: { Authorization: `Basic ${basicAuth}` }, 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 = parseAzureUrl(repoUrl);
  if (!parsed) return NextResponse.json({ error: 'Invalid Azure DevOps URL' }, { status: 400 });

  const { org, project, repo } = parsed;

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

  const account = await db.query.accounts.findFirst({
    where: and(eq(schema.accounts.userId, session.user.id), eq(schema.accounts.providerId, 'azure')),
  });
  if (!account?.accessToken) return NextResponse.json({ error: 'Azure DevOps not connected' }, { status: 401 });

  const token = account.accessToken;
  const baseUrl = `https://dev.azure.com/${encodeURIComponent(org)}/${encodeURIComponent(project)}/_apis/git/repositories/${encodeURIComponent(repo)}`;
  const versionParam = `&versionDescriptor.version=${encodeURIComponent(branch)}&versionDescriptor.versionType=branch`;

  try {
    const treeData = await azFetch(
      `${baseUrl}/items?scopePath=/&recursionLevel=full&api-version=7.0${versionParam}`,
      token
    );
    if (!treeData?.value) {
      return NextResponse.json(
        { error: `Branch "${branch}" not found or repository is empty. Check the branch name in your project settings.` },
        { status: 404 }
      );
    }

    const allItems: any[] = treeData.value ?? [];
    const allFiles = allItems.filter((f: any) => f.gitObjectType === 'blob');
    const allDirs = allItems.filter((f: any) => f.gitObjectType === 'tree');

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

    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 azFetchRaw(
            `${baseUrl}/items?path=${encodeURIComponent(f.path)}&download=true&api-version=7.0${versionParam}`,
            token,
          );
          return { path: f.path, size: f.size ?? 0, 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 azFetchRaw(
          `${baseUrl}/items?path=${encodeURIComponent(p)}&download=true&api-version=7.0${versionParam}`,
          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: estimate lines for source files beyond the deep-analysis cap.
    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);
    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),
    };

    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: totalFiles > 0 ? Math.round((count / totalFiles) * 1000) / 10 : 0 }))
      .sort((a, b) => b.count - a.count);

    return NextResponse.json({
      scanResults: {
        totalFiles,
        totalSourceFiles: sourceFiles.length,
        totalDirectories: allDirs.length,
        totalSize: allFiles.reduce((s: number, f: any) => s + (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: 'Azure DevOps API rate limit exceeded. Please 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('Azure DevOps analysis error:', error);
    return NextResponse.json({ error: 'Failed to analyze repository' }, { status: 500 });
  }
}
