import { NextRequest, NextResponse } from 'next/server';

const API = process.env.NEXT_PUBLIC_API_URL ?? 'http://127.0.0.1:3000';

const PROXY_TIMEOUT_MS = 25_000;
const REQUEST_ID_HEADER = 'x-request-id';

interface ProxyInit {
  method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
  /** Raw body, or `null` when there is no body to forward. */
  body: string | null;
  headers: Record<string, string>;
  requestId: string;
}

interface ProxyResult {
  status: number;
  headers: Headers;
  body: ReadableStream | null;
}

async function proxyToBackend(
  request: NextRequest,
  init: ProxyInit,
): Promise<ProxyResult> {
  const path = request.nextUrl.pathname.replace('/api/', '');
  const search = request.nextUrl.search;
  const url = `${API}/${path}${search}`;

  // Forward only the cookies the proxy knows are safe to relay. We deliberately
  // do NOT pass `content-length` (Node fetch sets it from `body`), and we keep
  // `content-type` only when we actually have a body to send — otherwise some
  // upstreams reject the request or return 502 on bodyless PATCH/POST calls.
  const headers: Record<string, string> = {
    Cookie: init.headers.Cookie ?? '',
    [REQUEST_ID_HEADER]: init.requestId,
  };
  if (init.body !== null) {
    headers['Content-Type'] = 'application/json';
  }

  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), PROXY_TIMEOUT_MS);

  try {
    const backendRes = await fetch(url, {
      method: init.method,
      headers,
      body: init.body,
      signal: controller.signal,
    });
    const responseHeaders = new Headers();
    const contentType = backendRes.headers.get('content-type');
    if (contentType) responseHeaders.set('Content-Type', contentType);
    const setCookie = backendRes.headers.get('set-cookie');
    if (setCookie) {
      responseHeaders.set(
        'Set-Cookie',
        setCookie.replace(/;\s*Secure/gi, '').replace(/;\s*SameSite=Lax/gi, '').trim(),
      );
    }
    return {
      status: backendRes.status,
      headers: responseHeaders,
      body: backendRes.body,
    };
  } finally {
    clearTimeout(timeout);
  }
}

/**
 * Reads the incoming request body safely. Returns `null` for bodyless methods
 * (GET/DELETE) and an empty string when the body is empty.
 */
async function safeReadBody(request: NextRequest): Promise<string | null> {
  if (request.method === 'GET' || request.method === 'DELETE' || request.method === 'HEAD') {
    return null;
  }
  try {
    return await request.text();
  } catch (error) {
    console.warn('[proxy] failed to read body', { message: (error as Error)?.message });
    return null;
  }
}

function getRequestId(request: NextRequest): string {
  return (
    request.headers.get(REQUEST_ID_HEADER) ??
    request.headers.get('x-vercel-id') ??
    `adm-${Math.random().toString(36).slice(2, 10)}`
  );
}

function logProxyFailure(stage: string, request: NextRequest, requestId: string, error: unknown) {
  console.warn('[proxy] failure', {
    stage,
    requestId,
    method: request.method,
    target: request.nextUrl.pathname,
    message: error instanceof Error ? error.message : String(error),
    name: error instanceof Error ? error.name : 'unknown',
  });
}

export async function GET(request: NextRequest) {
  const requestId = getRequestId(request);
  try {
    const result = await proxyToBackend(request, {
      method: 'GET',
      body: null,
      headers: { Cookie: request.headers.get('cookie') ?? '' },
      requestId,
    });
    if (request.nextUrl.pathname === 'admin/logs/stream') {
      result.headers.set('Content-Type', result.headers.get('content-type') ?? 'text/event-stream');
      result.headers.set('Cache-Control', 'no-cache, no-store, must-revalidate');
      result.headers.set('X-Accel-Buffering', 'no');
    }
    return new Response(result.body, { status: result.status, headers: result.headers });
  } catch (error) {
    logProxyFailure('get', request, requestId, error);
    return NextResponse.json(
      { error: { code: 'PROXY_ERROR', message: 'Proxy error', requestId } },
      { status: 502 },
    );
  }
}

export async function POST(request: NextRequest) {
  const requestId = getRequestId(request);
  try {
    const body = await safeReadBody(request);
    const result = await proxyToBackend(request, {
      method: 'POST',
      body,
      headers: { Cookie: request.headers.get('cookie') ?? '' },
      requestId,
    });
    return new Response(result.body, { status: result.status, headers: result.headers });
  } catch (error) {
    logProxyFailure('post', request, requestId, error);
    return NextResponse.json(
      { error: { code: 'PROXY_ERROR', message: 'Proxy error', requestId } },
      { status: 502 },
    );
  }
}

export async function PATCH(request: NextRequest) {
  const requestId = getRequestId(request);
  try {
    const body = await safeReadBody(request);
    const result = await proxyToBackend(request, {
      method: 'PATCH',
      body,
      headers: { Cookie: request.headers.get('cookie') ?? '' },
      requestId,
    });
    return new Response(result.body, { status: result.status, headers: result.headers });
  } catch (error) {
    logProxyFailure('patch', request, requestId, error);
    return NextResponse.json(
      { error: { code: 'PROXY_ERROR', message: 'Proxy error', requestId } },
      { status: 502 },
    );
  }
}

export async function PUT(request: NextRequest) {
  const requestId = getRequestId(request);
  try {
    const body = await safeReadBody(request);
    const result = await proxyToBackend(request, {
      method: 'PUT',
      body,
      headers: { Cookie: request.headers.get('cookie') ?? '' },
      requestId,
    });
    return new Response(result.body, { status: result.status, headers: result.headers });
  } catch (error) {
    logProxyFailure('put', request, requestId, error);
    return NextResponse.json(
      { error: { code: 'PROXY_ERROR', message: 'Proxy error', requestId } },
      { status: 502 },
    );
  }
}

export async function DELETE(request: NextRequest) {
  const requestId = getRequestId(request);
  try {
    const result = await proxyToBackend(request, {
      method: 'DELETE',
      body: null,
      headers: { Cookie: request.headers.get('cookie') ?? '' },
      requestId,
    });
    return new Response(result.body, { status: result.status, headers: result.headers });
  } catch (error) {
    logProxyFailure('delete', request, requestId, error);
    return NextResponse.json(
      { error: { code: 'PROXY_ERROR', message: 'Proxy error', requestId } },
      { status: 502 },
    );
  }
}
