import { NextRequest, NextResponse } from 'next/server';
import { spawn } from 'child_process';
import { mkdir, writeFile, rm } from 'fs/promises';
import path from 'path';
import { dbHelpers } from '@/lib/db';
import { getSession } from '@/lib/auth';

async function authorize(request: NextRequest, projectId: string) {
  const accessToken = request.cookies.get('scriba.access_token')?.value;
  const session = await getSession(accessToken);
  if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  const project = await dbHelpers.getProject(projectId);
  if (!project) return NextResponse.json({ error: 'Conversion not found' }, { status: 404 });
  if (project.userId !== session.user.id) return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
  return project;
}

interface SandboxConfig {
  image: string;
  installCmd: string | null;
  buildCmd: string;
}

function getSandboxConfig(targetLanguage: string): SandboxConfig {
  const lang = targetLanguage.toLowerCase().trim();

  if (lang.includes('javascript') || lang.includes('typescript') || lang.includes('node') || lang === 'js' || lang === 'ts') {
    return {
      image: 'node:20-slim',
      // Prefer offline cache; fall back to full install. Ignore missing optional deps.
      installCmd: 'npm install --prefer-offline --no-audit --no-fund 2>&1 || npm install --no-audit --no-fund 2>&1',
      // If no build script exists, treat as success (many migrated projects won't have one initially)
      buildCmd: 'npm run build 2>&1; EXIT=$?; if [ $EXIT -eq 1 ] && grep -q "missing script" /tmp/.npm-err 2>/dev/null; then echo "No build script — skipping"; exit 0; fi; exit $EXIT',
    };
  }

  if (lang.includes('python') || lang === 'py') {
    return {
      image: 'python:3.12-slim',
      installCmd: '[ -f requirements.txt ] && pip install -r requirements.txt --quiet 2>&1 || echo "[sandbox] No requirements.txt — skipping install"',
      buildCmd: 'find . -name "*.py" ! -path "./.venv/*" -exec python -m py_compile {} + && echo "All Python files compiled OK"',
    };
  }

  if (lang.includes('kotlin') || (lang.includes('java') && lang.includes('gradle'))) {
    return {
      image: 'gradle:8.5-jdk21',
      installCmd: null,
      buildCmd: '[ -f gradlew ] && chmod +x gradlew && ./gradlew build --no-daemon -q 2>&1 || gradle build --no-daemon -q 2>&1',
    };
  }

  if (lang.includes('java')) {
    return {
      image: 'maven:3.9-eclipse-temurin-21',
      installCmd: null,
      buildCmd: '[ -f pom.xml ] && mvn compile -q 2>&1 || echo "[sandbox] No pom.xml found — skipping Maven build"',
    };
  }

  if (lang.includes('go') || lang === 'golang') {
    return {
      image: 'golang:1.23-alpine',
      installCmd: '[ -f go.mod ] && go mod download 2>&1 || (go mod init sandbox 2>&1 && go mod tidy 2>&1)',
      buildCmd: 'go build ./... 2>&1',
    };
  }

  if (lang.includes('ruby') || lang === 'rb') {
    return {
      image: 'ruby:3.3-slim',
      installCmd: '[ -f Gemfile ] && bundle install --quiet 2>&1 || echo "[sandbox] No Gemfile — skipping install"',
      buildCmd: 'find . -name "*.rb" ! -path "./vendor/*" -exec ruby -c {} + 2>&1 && echo "Ruby syntax OK"',
    };
  }

  if (lang.includes('php')) {
    return {
      image: 'php:8.3-cli-alpine',
      installCmd: '[ -f composer.json ] && composer install --no-interaction --quiet 2>&1 || echo "[sandbox] No composer.json — skipping install"',
      buildCmd: 'find . -name "*.php" ! -path "./vendor/*" -exec php -l {} + 2>&1 | grep -v "No syntax errors" | grep -v "^$" || echo "PHP syntax OK"',
    };
  }

  if (lang.includes('csharp') || lang.includes('c#') || lang.includes('dotnet') || lang.includes('.net')) {
    return {
      image: 'mcr.microsoft.com/dotnet/sdk:8.0',
      installCmd: 'PROJ=$(find . -name "*.sln" -o -name "*.csproj" | head -1); [ -n "$PROJ" ] && dotnet restore "$PROJ" 2>&1 || echo "[sandbox] No .sln/.csproj found"',
      buildCmd: 'PROJ=$(find . -name "*.sln" -o -name "*.csproj" | head -1); [ -n "$PROJ" ] && dotnet build "$PROJ" 2>&1 || echo "[sandbox] No build target found"',
    };
  }

  if (lang.includes('rust') || lang === 'rs') {
    return {
      image: 'rust:1.75-slim',
      installCmd: null,
      buildCmd: '[ -f Cargo.toml ] && cargo build 2>&1 || echo "[sandbox] No Cargo.toml found"',
    };
  }

  if (lang.includes('swift')) {
    return {
      image: 'swift:5.9-jammy',
      installCmd: '[ -f Package.swift ] && swift package resolve 2>&1 || echo "[sandbox] No Package.swift found"',
      buildCmd: '[ -f Package.swift ] && swift build 2>&1 || echo "[sandbox] No Swift package to build"',
    };
  }

  return {
    image: 'alpine:3.19',
    installCmd: null,
    buildCmd: `echo "[sandbox] Language '${targetLanguage}' — no build step configured. Files written successfully."`,
  };
}

function sseChunk(event: string, data: unknown): string {
  return `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
}

export async function POST(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const authResult = await authorize(request, id);
  if (authResult instanceof NextResponse) return authResult;
  const project = authResult;

  const targetLanguage = project.targetLanguage ?? 'unknown';
  const cfg = getSandboxConfig(targetLanguage);

  // Collect all files to write: translated code files + tooling files (package.json, go.mod, etc.)
  const projConfig = (project.config ?? {}) as Record<string, unknown>;
  const analysisResults = (projConfig.analysisResults ?? {}) as Record<string, unknown>;
  const translation = (analysisResults.translation ?? {}) as Record<string, unknown>;
  const codeFiles = (translation.files ?? []) as Array<{ targetFile?: string; targetCode?: string }>;
  const artifacts = (translation.artifacts ?? {}) as Record<string, unknown>;

  // Tooling files include package.json, go.mod, Dockerfile, etc. generated by the scaffold step
  type ArtifactFile = { path?: string; code?: string; name?: string };
  const toolingFiles = (artifacts.toolingFiles ?? []) as ArtifactFile[];
  const projectFiles = (artifacts.projectFiles ?? []) as ArtifactFile[];

  const sandboxDir = `/tmp/scriba-sandbox-${id}`;

  const stream = new ReadableStream({
    async start(controller) {
      let closed = false;
      const enc = (chunk: string) => {
        if (!closed) controller.enqueue(new TextEncoder().encode(chunk));
      };
      const startMs = Date.now();

      const elapsed = () => `+${((Date.now() - startMs) / 1000).toFixed(1)}s`;
      const log = (line: string, stream: 'stdout' | 'stderr' = 'stdout', level = 'info') =>
        enc(sseChunk('log', { line, stream, level, elapsed: elapsed() }));

      try {
        log(`[sandbox] Preparing workspace for ${targetLanguage} project...`);

        await rm(sandboxDir, { recursive: true, force: true });
        await mkdir(sandboxDir, { recursive: true });

        // Write translated code files
        if (codeFiles.length === 0) {
          log('[sandbox] No migrated code files found — writing placeholder', 'stderr', 'warn');
          await writeFile(path.join(sandboxDir, 'README.md'), `# Sandbox\nNo translated files found for project ${id}.\n`);
        } else {
          for (const f of codeFiles) {
            const relPath = (f.targetFile ?? 'unknown.txt').replace(/^\/+/, '');
            const absPath = path.join(sandboxDir, relPath);
            await mkdir(path.dirname(absPath), { recursive: true });
            await writeFile(absPath, f.targetCode ?? '');
          }
          log(`[sandbox] Wrote ${codeFiles.length} source file(s) to ${sandboxDir}`);
        }

        // Write tooling/project manifest files (package.json, go.mod, pom.xml, etc.)
        const manifestNames: string[] = [];
        for (const f of [...toolingFiles, ...projectFiles]) {
          const filePath = f.path ?? f.name;
          if (!filePath || !f.code) continue;
          const relPath = filePath.replace(/^\/+/, '');
          const absPath = path.join(sandboxDir, relPath);
          await mkdir(path.dirname(absPath), { recursive: true });
          await writeFile(absPath, f.code);
          manifestNames.push(relPath);
        }
        if (manifestNames.length > 0) {
          log(`[sandbox] Wrote ${manifestNames.length} manifest file(s): ${manifestNames.join(', ')}`);
        }

        const shellCmd = [cfg.installCmd, cfg.buildCmd].filter(Boolean).join(' && ');

        log(`[sandbox] Image:    ${cfg.image}`);
        log(`[sandbox] Language: ${targetLanguage}`);
        log(`[sandbox] Limits:   memory=1g  cpus=1.5  nofile=1024`);
        if (cfg.installCmd) log(`[sandbox] Install:  ${cfg.installCmd}`);
        log(`[sandbox] Build:    ${cfg.buildCmd}`);
        log(`[sandbox] Pulling image and starting container...`);

        // Mount read-write so installers can write node_modules, target/, build/, etc.
        // Resource-limit with memory + CPU caps; network is required for package managers.
        const dockerArgs = [
          'run', '--rm',
          '--memory', '1g',
          '--cpus', '1.5',
          '--ulimit', 'nofile=1024:1024',
          '-v', `${sandboxDir}:/app`,
          '-w', '/app',
          cfg.image,
          'sh', '-c', shellCmd,
        ];

        const containerStartMs = Date.now();

        await new Promise<void>((resolve) => {
          const proc = spawn('docker', dockerArgs, { stdio: ['ignore', 'pipe', 'pipe'] });

          // Separate buffers to avoid stdout/stderr interleaving corruption
          let stdoutBuf = '';
          let stderrBuf = '';
          let containerReady = false;

          const flushLines = (buf: string, newChunk: string, streamName: 'stdout' | 'stderr'): string => {
            if (!containerReady) {
              containerReady = true;
              log(`[sandbox] Container ready in ${((Date.now() - containerStartMs) / 1000).toFixed(1)}s — running commands...`);
            }
            buf += newChunk;
            const lines = buf.split('\n');
            const remaining = lines.pop() ?? '';
            for (const line of lines) {
              if (!line.trim()) continue;
              const lower = line.toLowerCase();
              const level =
                lower.includes('error') || lower.includes('exception') || lower.includes('fatal') || lower.includes('failed')
                  ? 'error'
                  : lower.includes('warn')
                    ? 'warn'
                    : 'info';
              enc(sseChunk('log', { line, stream: streamName, level, elapsed: elapsed() }));
            }
            return remaining;
          };

          proc.stdout.on('data', (d: Buffer) => { stdoutBuf = flushLines(stdoutBuf, d.toString(), 'stdout'); });
          proc.stderr.on('data', (d: Buffer) => { stderrBuf = flushLines(stderrBuf, d.toString(), 'stderr'); });

          proc.on('error', (err: Error) => {
            enc(sseChunk('error', { message: `Failed to start Docker: ${err.message}. Ensure Docker is running on the server.` }));
            resolve();
          });

          proc.on('close', async (code: number | null) => {
            // Flush remaining partial lines
            if (stdoutBuf.trim()) enc(sseChunk('log', { line: stdoutBuf, stream: 'stdout', level: 'info', elapsed: elapsed() }));
            if (stderrBuf.trim()) enc(sseChunk('log', { line: stderrBuf, stream: 'stderr', level: 'warn', elapsed: elapsed() }));

            const success = code === 0;
            const duration = Date.now() - startMs;
            log(`[sandbox] Container exited — code=${code ?? -1}  total=${(duration / 1000).toFixed(1)}s`, 'stdout', success ? 'info' : 'error');
            enc(sseChunk('done', { success, exitCode: code ?? -1, duration }));

            // Persist result to project config
            try {
              await dbHelpers.updateProjectProgress(id, {
                config: {
                  ...projConfig,
                  sandboxRun: {
                    ranAt: new Date().toISOString(),
                    targetLanguage,
                    dockerImage: cfg.image,
                    exitCode: code ?? -1,
                    success,
                    duration,
                    fileCount: codeFiles.length,
                  },
                } as Record<string, unknown>,
              });
            } catch { /* non-critical */ }

            rm(sandboxDir, { recursive: true, force: true }).catch(() => {});
            resolve();
          });
        });
      } catch (err) {
        enc(sseChunk('error', { message: err instanceof Error ? err.message : 'Sandbox failed' }));
      } finally {
        closed = true;
        controller.close();
      }
    },
  });

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache, no-transform',
      'X-Accel-Buffering': 'no',
    },
  });
}

export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const authResult = await authorize(request, id);
  if (authResult instanceof NextResponse) return authResult;
  const project = authResult;

  const projConfig = (project.config ?? {}) as Record<string, unknown>;
  return NextResponse.json({ sandboxRun: projConfig.sandboxRun ?? null });
}
