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

import { config } from '../config';
import { buildUpstreamHeaders } from '../proxy/headers';
import { resolveCartSessionId } from '../intercept/cart';
import {
  commitDraft,
  computeTotals,
  createSession,
  getSessionView,
  loadCartBySessionId,
  payDraft,
  requireDraft,
  updateCourierAddress,
  updateInvoice,
  updatePaymentMethod,
  updatePickup,
  updateShipping,
} from '../checkout/draftService';

function checkoutCookie(reply: FastifyReply, checkoutSessionId: string): void {
  reply.setCookie(config.checkout.draftCookieName, checkoutSessionId, {
    path: '/',
    maxAge: config.checkout.draftTtlSec,
    httpOnly: true,
    sameSite: 'lax',
  });
}

export async function registerCheckoutRoutes(app: FastifyInstance): Promise<void> {
  await app.register(
    async (checkoutApp) => {
      checkoutApp.get('/cart', async (request, reply) => {
        const query = request.query as { checkoutSessionId?: string };
        let cartSessionId: string | undefined;
        if (query.checkoutSessionId) {
          const draft = await requireDraft(query.checkoutSessionId);
          cartSessionId = draft?.cartSessionId;
        }
        if (!cartSessionId) {
          cartSessionId = await resolveCartSessionId(request, reply);
        }
        const cart = await loadCartBySessionId(cartSessionId);
        reply.send(cart);
      });

      checkoutApp.post('/sessions', async (request, reply) => {
        const body = (request.body ?? {}) as { cartSessionId?: string };
        const cartSessionId = body.cartSessionId ?? (await resolveCartSessionId(request, reply));
        const result = await createSession(cartSessionId);
        checkoutCookie(reply, result.checkoutSessionId);
        reply.send({ ok: true, checkoutSessionId: result.checkoutSessionId, snapshot: result.snapshot });
      });

      checkoutApp.get<{ Params: { id: string } }>('/sessions/:id', async (request, reply) => {
        const view = await getSessionView(request.params.id);
        if (!view) {
          reply.status(404).send({ ok: false, error: 'Session not found' });
          return;
        }
        reply.send({ ok: true, snapshot: view });
      });

      checkoutApp.put<{ Params: { id: string } }>(
        '/sessions/:id/shipping',
        async (request, reply) => {
          const body = request.body as { shippingMethodId?: string };
          if (!body?.shippingMethodId) {
            reply.status(400).send({ ok: false, error: 'shippingMethodId required' });
            return;
          }
          const result = await updateShipping(request.params.id, body.shippingMethodId);
          if (!result.ok) {
            reply.status(400).send(result);
            return;
          }
          reply.send(result);
        },
      );

      checkoutApp.post<{ Params: { id: string } }>(
        '/sessions/:id/pickup/search',
        async (request, reply) => {
          const body = (request.body ?? {}) as Record<string, unknown>;
          const draft = await requireDraft(request.params.id);
          if (!draft) {
            reply.status(404).send({ ok: false, error: 'Session not found' });
            return;
          }
          const headers = buildUpstreamHeaders(
            request.headers as Record<string, string | string[] | undefined>,
            'www.sinsay.com',
          );
          headers['content-type'] = 'application/json';
          const upstream = await undiciRequest(
            `${config.upstream.main}/pl/pl/ajx/pickuppoint/searchWithPaging/`,
            {
              method: 'POST',
              headers,
              body: JSON.stringify(body),
              throwOnError: false,
            },
          );
          const chunks: Buffer[] = [];
          for await (const chunk of upstream.body) {
            chunks.push(chunk instanceof Buffer ? chunk : Buffer.from(chunk));
          }
          reply.status(upstream.statusCode).send(Buffer.concat(chunks));
        },
      );

      checkoutApp.put<{ Params: { id: string } }>(
        '/sessions/:id/pickup',
        async (request, reply) => {
          const body = request.body as {
            id?: string;
            name?: string;
            address?: string;
            city?: string;
            zip?: string;
          };
          const result = await updatePickup(request.params.id, {
            id: String(body.id ?? ''),
            name: String(body.name ?? ''),
            address: String(body.address ?? ''),
            city: body.city,
            zip: body.zip,
          });
          if (!result.ok) {
            reply.status(400).send(result);
            return;
          }
          reply.send(result);
        },
      );

      checkoutApp.put<{ Params: { id: string } }>(
        '/sessions/:id/courier-address',
        async (request, reply) => {
          const body = request.body as {
            street?: string;
            number?: string;
            zip?: string;
            city?: string;
          };
          const result = await updateCourierAddress(request.params.id, {
            street: String(body.street ?? ''),
            number: String(body.number ?? ''),
            zip: String(body.zip ?? ''),
            city: String(body.city ?? ''),
          });
          if (!result.ok) {
            reply.status(400).send(result);
            return;
          }
          reply.send(result);
        },
      );

      checkoutApp.put<{ Params: { id: string } }>(
        '/sessions/:id/payment-method',
        async (request, reply) => {
          const body = request.body as { paymentMethodId?: string };
          if (!body?.paymentMethodId) {
            reply.status(400).send({ ok: false, error: 'paymentMethodId required' });
            return;
          }
          const result = await updatePaymentMethod(request.params.id, body.paymentMethodId);
          if (!result.ok) {
            reply.status(400).send(result);
            return;
          }
          reply.send(result);
        },
      );

      checkoutApp.put<{ Params: { id: string } }>(
        '/sessions/:id/invoice',
        async (request, reply) => {
          const body = request.body as Record<string, unknown>;
          const result = await updateInvoice(request.params.id, {
            firstname: String(body.firstname ?? ''),
            lastname: String(body.lastname ?? ''),
            email: String(body.email ?? ''),
            phone: String(body.phone ?? ''),
            invoiceIsCompany: Boolean(body.invoiceIsCompany),
            companyName: body.companyName ? String(body.companyName) : undefined,
            vatin: body.vatin ? String(body.vatin) : undefined,
            regon: body.regon ? String(body.regon) : undefined,
          });
          if (!result.ok) {
            reply.status(400).send(result);
            return;
          }
          reply.send(result);
        },
      );

      checkoutApp.get<{ Params: { id: string } }>(
        '/sessions/:id/totals',
        async (request, reply) => {
          const draft = await requireDraft(request.params.id);
          if (!draft) {
            reply.status(404).send({ ok: false, error: 'Session not found' });
            return;
          }
          const cart = await loadCartBySessionId(draft.cartSessionId);
          reply.send({ ok: true, ...computeTotals(draft, cart) });
        },
      );

      checkoutApp.post<{ Params: { id: string } }>(
        '/sessions/:id/commit',
        async (request, reply) => {
          const visitorId = (request as FastifyRequest & { visitorId?: string }).visitorId;
          const result = await commitDraft(request.params.id, { visitorId });
          if (!result.ok) {
            reply.status(400).send(result);
            return;
          }
          reply.send(result);
        },
      );

      checkoutApp.post<{ Params: { id: string } }>(
        '/sessions/:id/pay',
        async (request, reply) => {
          const body = (request.body ?? {}) as {
            webhookUrl?: string;
            returnUrlSuccess?: string;
            returnUrlFailure?: string;
          };
          const result = await payDraft(request.params.id, body);
          if (!result.ok) {
            reply.status(400).send(result);
            return;
          }
          reply.send(result);
        },
      );
    },
    { prefix: '/api/checkout' },
  );
}
