import { FastifyReply, FastifyRequest } from 'fastify';
import { Dispatcher, request as undiciRequest } from 'undici';

import { config } from '../config';
import { getRedis, REDIS_KEYS } from '../redis';
import { buildUpstreamHeaders, processResponseHeaders } from '../proxy/headers';
import { isRewritable, rewriteBody } from '../proxy/rewrite';
import {
  frontendIdFromSetCookie,
  rewriteCustomerAuthResponse,
} from './customerAjxRewrite';
import { maybePatchCheckoutcartJsonBody } from './checkoutcartPricePatch';
import { patchCheckoutAjaxResponse } from './checkoutAjaxPatch';
import { rewriteSinsayPaymentJsonBody } from './sinsayPaymentRewrite';

function bodyToBuffer(body: unknown, contentType = ''): Buffer | undefined {
  if (body == null) return undefined;
  if (Buffer.isBuffer(body)) return body.length ? body : undefined;
  if (typeof body === 'string') return body.length ? Buffer.from(body, 'utf8') : undefined;
  if (body instanceof Uint8Array) return Buffer.from(body);

  if (contentType.toLowerCase().includes('application/x-www-form-urlencoded')) {
    const params = new URLSearchParams();
    for (const [key, value] of Object.entries(body as Record<string, unknown>)) {
      if (Array.isArray(value)) {
        for (const item of value) params.append(key, String(item));
      } else if (value != null) {
        params.append(key, String(value));
      }
    }
    const encoded = params.toString();
    return encoded ? Buffer.from(encoded, 'utf8') : undefined;
  }

  try {
    return Buffer.from(JSON.stringify(body), 'utf8');
  } catch {
    return undefined;
  }
}

/**
 * AJAX paths proxied before the generic catch-all.
 * checkoutcart/* — add/update/get cart (axios on PDP, listing quick-add fallback).
 */
const ALLOWED_AJX_PREFIXES = [
  'pickuppoint/',
  'stores/',
  'customer/',
  'checkoutcart/',
  'checkout/',
  'product/',
];

function mirrorContextFromRequest(
  request: FastifyRequest,
): { mirrorOrigin: string; mirrorDomain: string; requestOrigin?: string } {
  const domain = String(request.headers.host ?? config.domain) || config.domain;
  const hostname = domain.replace(/:\d+$/, '');
  const isLocal = hostname === 'localhost' || hostname.startsWith('127.');
  const protoHeader = String(request.headers['x-forwarded-proto'] ?? '')
    .split(',')[0]
    .trim();
  const proto = isLocal
    ? 'http'
    : protoHeader === 'http' || protoHeader === 'https'
      ? protoHeader
      : 'https';
  const mirrorOrigin = `${proto}://${domain}`;
  const requestOrigin = String(request.headers.origin ?? '');
  return {
    mirrorOrigin,
    mirrorDomain: domain,
    requestOrigin: requestOrigin || undefined,
  };
}

export async function handleSinsayAjxProxy(
  request: FastifyRequest<{ Params: { '*': string } }>,
  reply: FastifyReply,
): Promise<void> {
  let subPath = request.params['*'] ?? '';
  if (!ALLOWED_AJX_PREFIXES.some((p) => subPath.startsWith(p))) {
    reply.status(404).send({ error: 'Not found' });
    return;
  }
  if (
    subPath.startsWith('checkoutcart/') &&
    !subPath.endsWith('/') &&
    !subPath.includes('?')
  ) {
    subPath = `${subPath}/`;
  }
  const query = request.url.includes('?') ? request.url.slice(request.url.indexOf('?')) : '';
  const upstreamUrl = `${config.upstream.main}/pl/pl/ajx/${subPath}${query}`;

  const contentType = String(request.headers['content-type'] ?? '');
  const headers = buildUpstreamHeaders(
    request.headers as Record<string, string | string[] | undefined>,
    'www.sinsay.com',
    {
      preserveClientCookies:
        subPath.startsWith('checkoutcart/') ||
        subPath.startsWith('checkout/') ||
        subPath.startsWith('pickuppoint/') ||
        subPath.startsWith('stores/') ||
        subPath.startsWith('customer/'),
    },
  );
  if (contentType) headers['content-type'] = contentType;

  const upstream = await undiciRequest(upstreamUrl, {
    method: request.method as Dispatcher.HttpMethod,
    headers,
    body: bodyToBuffer(request.body, contentType),
    throwOnError: false,
  });

  const chunks: Buffer[] = [];
  for await (const chunk of upstream.body) {
    chunks.push(chunk instanceof Buffer ? chunk : Buffer.from(chunk));
  }

  const mirror = mirrorContextFromRequest(request);
  const responseContentType =
    (upstream.headers['content-type'] as string | undefined) ?? '';
  let responseBody: Buffer = Buffer.concat(chunks);
  if (isRewritable(responseContentType)) {
    responseBody = rewriteBody(responseBody, {
      origin: mirror.mirrorOrigin,
      domain: mirror.mirrorDomain,
    });
    responseBody = rewriteCustomerAuthResponse(responseBody, subPath, mirror.mirrorOrigin);
    responseBody = maybePatchCheckoutcartJsonBody(subPath, responseBody);
    responseBody = rewriteSinsayPaymentJsonBody(
      responseBody,
      subPath,
      mirror.mirrorOrigin,
    );
    responseBody = patchCheckoutAjaxResponse(
      responseBody,
      subPath,
      mirror.mirrorOrigin,
    );
  }

  const reqSid = (request.cookies as Record<string, string>)[config.session.cookieName];
  if (reqSid && /\/customer\/(login|register)\b/.test(subPath)) {
    reply.setCookie(config.session.cookieName, reqSid, {
      path: '/',
      maxAge: 60 * 60 * 24 * 30,
      httpOnly: true,
      sameSite: 'lax',
    });
    const frontendId = frontendIdFromSetCookie(
      upstream.headers as Record<string, string | string[] | undefined>,
    );
    if (frontendId) {
      const redis = getRedis();
      await redis.setex(
        REDIS_KEYS.cartFrontendBind(frontendId),
        60 * 60 * 24 * 7,
        reqSid,
      );
    }
  }

  const responseHeaders = processResponseHeaders(
    upstream.headers as Record<string, string | string[]>,
    mirror,
  );
  for (const [key, value] of Object.entries(responseHeaders)) {
    reply.header(key, value as string);
  }

  reply.status(upstream.statusCode).send(responseBody);
}
