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 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 getAuthHeader(token: string): string {
  return token.includes(':')
    ? `Basic ${Buffer.from(token).toString('base64')}`
    : `Bearer ${token}`;
}

async function bbFetch(url: string, token?: string): Promise<any> {
  const headers: Record<string, string> = {};
  if (token) headers['Authorization'] = getAuthHeader(token);
  const res = await fetch(url, { headers, 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 bbFetchRaw(url: string, token?: string): Promise<string | null> {
  const headers: Record<string, string> = {};
  if (token) headers['Authorization'] = getAuthHeader(token);
  const res = await fetch(url, { 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 = parseBitbucketUrl(repoUrl);
  if (!parsed) return NextResponse.json({ error: 'Invalid Bitbucket URL' }, { status: 400 });

  const { workspace, repo } = parsed;

  const session = await getSession(request.cookies.get('scriba.access_token')?.value);
  let token: string | undefined;
  if (session) {
    const account = await db.query.accounts.findFirst({
      where: and(eq(schema.accounts.userId, session.user.id), eq(schema.accounts.providerId, 'bitbucket')),
    });
    token = account?.accessToken ?? undefined;
  }

  const baseUrl = `https://api.bitbucket.org/2.0/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repo)}`;
  const srcBase = `${baseUrl}/src/${encodeURIComponent(branch)}`;

  try {
    const srcData = await bbFetch(`${srcBase}/?recursive=true&pagelen=500`, token);
    if (!srcData) {
      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[] = srcData.values ?? [];
    const allFiles = allItems.filter((f: any) => f.type === 'commit_file');
    const allDirs = allItems.filter((f: any) => f.type === 'commit_directory');

    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 bbFetchRaw(`${srcBase}/${f.path}`, 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 bbFetchRaw(`${srcBase}/${p}`, 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: count lines for source files beyond the deep-analysis cap so the
    // price/effLOC reflect the whole codebase (estimated for the un-analyzed remainder).
    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: 'Bitbucket 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('Bitbucket analysis error:', error);
    return NextResponse.json({ error: 'Failed to analyze repository' }, { status: 500 });
  }
}
