/**
 * Proxy route: forwards all /api/engine/* requests to scriba-engine on port 3100.
 * This avoids CORS issues in the browser and keeps the engine URL internal.
 */
import { NextRequest, NextResponse } from 'next/server';
import { getSession } from '@/lib/auth';
import { db, schema } from '@/lib/db';
import { eq, and } from 'drizzle-orm';
import http from 'node:http';
import { buildEngineUpstreamHeaders } from '@/lib/engine-upstream-headers';

export const dynamic = 'force-dynamic';

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

async function requireSession(request: NextRequest) {
  const session = await getSession(request.cookies.get('scriba.access_token')?.value);
  return session ?? null;
}

export async function GET(request: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
  if (!await requireSession(request)) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }
  const { path } = await params;
  const url = `${ENGINE}/scriba/${path.join('/')}${request.nextUrl.search}`;

  // Detect SSE requests (stream endpoints or Accept: text/event-stream)
  const lastSeg = path[path.length - 1];
  const isSSE = lastSeg === 'stream' || lastSeg === 'events' || request.headers.get('accept')?.includes('text/event-stream');

  if (isSSE) {
    // Use Node.js http module to avoid Next.js fetch buffering
    const parsed = new URL(url);
    let clientReq: http.ClientRequest | undefined;

    const stream = new ReadableStream<Uint8Array>({
      start(controller) {
        let finished = false;
        const finish = () => {
          if (finished) return;
          finished = true;
          try {
            controller.close();
          } catch {
            /* consumer already gone; controller may already be closed */
          }
        };

        if (request.signal.aborted) {
          finish();
          return;
        }

        const sseHeaders = { Accept: 'text/event-stream', ...buildEngineUpstreamHeaders('GET') };
        clientReq = http.get(
          {
            hostname: parsed.hostname,
            port: parsed.port || undefined,
            path: parsed.pathname + parsed.search,
            headers: sseHeaders,
          },
          (res) => {
            res.on('data', (chunk: Buffer) => {
              if (finished) return;
              try {
                controller.enqueue(new Uint8Array(chunk));
              } catch {
                finish();
              }
            });
            res.on('end', finish);
            res.on('error', finish);
          },
        );
        clientReq.on('error', finish);
        request.signal.addEventListener('abort', () => {
          clientReq?.destroy();
          finish();
        });
      },
      cancel() {
        clientReq?.destroy();
      },
    });

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

  const upGet = buildEngineUpstreamHeaders('GET');
  const res = await fetch(url, {
    headers: {
      ...upGet,
      accept: request.headers.get('accept') ?? 'application/json',
    },
    cache: 'no-store',
  });
  const contentType = res.headers.get('content-type') ?? '';
  const retryAfter = res.headers.get('Retry-After');
  const baseHeaders: Record<string, string> = retryAfter ? { 'Retry-After': retryAfter } : {};

  // Pass HTML through with /scriba/ paths rewritten to /api/engine/ so that
  // relative references (e.g. Scalar fetching /scriba/openapi.json) resolve correctly
  // when the page is served from the Next.js origin instead of the engine origin.
  if (contentType.includes('text/html')) {
    let html = await res.text();
    html = html.replaceAll('/scriba/', '/api/engine/');
    return new NextResponse(html, {
      status: res.status,
      headers: { ...baseHeaders, 'content-type': contentType },
    });
  }

  // Pass other non-JSON responses through verbatim (CSS, plain text, etc.)
  if (!contentType.includes('application/json') && !contentType.includes('application/javascript')) {
    const body = await res.arrayBuffer();
    return new NextResponse(body, {
      status: res.status,
      headers: { ...baseHeaders, 'content-type': contentType || 'text/plain' },
    });
  }

  const data = await res.json().catch(() => ({}));
  return NextResponse.json(data, {
    status: res.status,
    ...(Object.keys(baseHeaders).length ? { headers: baseHeaders } : {}),
  });
}

export async function POST(request: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
  const session = await requireSession(request);
  if (!session) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }
  const { path } = await params;
  const url = `${ENGINE}/scriba/${path.join('/')}`;
  const contentType = request.headers.get('content-type') ?? '';
  const isJson = !contentType || contentType.includes('application/json');
  let body: BodyInit;
  let rawBodyForSigning: string | undefined;

  // For /convert requests that include a repoUrl, inject the user's VCS access token
  // so the engine can perform an authenticated git clone without exposing tokens to the browser.
  if (isJson) {
    let textBody = await request.text();
    if (path[path.length - 1] === 'convert') {
      try {
        const parsed = JSON.parse(textBody);
        if (parsed.repoUrl && !parsed.accessToken) {
          const repoUrl: string = parsed.repoUrl;
          const providerId =
            repoUrl.includes('github.com') ? 'github'
            : repoUrl.includes('gitlab.com') ? 'gitlab'
            : repoUrl.includes('bitbucket.org') ? 'bitbucket'
            : repoUrl.includes('dev.azure.com') || repoUrl.includes('visualstudio.com') ? 'azure'
            : null;
          if (providerId) {
            const account = await db.query.accounts.findFirst({
              where: and(eq(schema.accounts.userId, session.user.id), eq(schema.accounts.providerId, providerId)),
            });
            if (account?.accessToken) {
              parsed.accessToken = account.accessToken;
              textBody = JSON.stringify(parsed);
            }
          }
        }
      } catch {
        // Non-parseable body — pass through unchanged
      }
    }
    body = textBody;
    rawBodyForSigning = textBody;
  } else {
    const bodyBuffer = await request.arrayBuffer();
    body = bodyBuffer;
    rawBodyForSigning = new TextDecoder().decode(bodyBuffer);
  }

  const upstream = buildEngineUpstreamHeaders('POST', { rawBody: rawBodyForSigning });
  const res = await fetch(url, {
    method: 'POST',
    headers: {
      ...upstream,
      ...(contentType ? { 'Content-Type': contentType } : { 'Content-Type': 'application/json' }),
    },
    body,
    cache: 'no-store',
  });
  const data = await res.json().catch(() => ({}));
  const retryAfter = res.headers.get('Retry-After');
  const traceparent = res.headers.get('traceparent');
  const responseHeaders: Record<string, string> = {};
  if (retryAfter) responseHeaders['Retry-After'] = retryAfter;
  if (traceparent) responseHeaders['traceparent'] = traceparent;
  return NextResponse.json(data, {
    status: res.status,
    ...(Object.keys(responseHeaders).length ? { headers: responseHeaders } : {}),
  });
}

export async function DELETE(request: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
  if (!await requireSession(request)) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }
  const { path } = await params;
  const url = `${ENGINE}/scriba/${path.join('/')}`;
  const upstream = buildEngineUpstreamHeaders('DELETE');
  const res = await fetch(url, {
    method: 'DELETE',
    headers: { ...upstream },
    cache: 'no-store',
  });
  const data = await res.json().catch(() => ({}));
  return NextResponse.json(data, { status: res.status });
}
