import { NextFunction, Request, Response } from "express";
import { ApiError } from "../errors";
import { readIdempotency, writeIdempotency } from "../idempotency/idempotencyStore";
import { env } from "../../config/env";
import { successResponse } from "./errorMiddleware";

interface IdempotencyOptions {
  scope: string;
  ttlMs?: number;
  required?: boolean;
}

export function idempotency(options: IdempotencyOptions) {
  return function idempotencyMiddleware(req: Request, _res: Response, next: NextFunction) {
    const idempotencyKey = req.header("idempotency-key");
    if (!idempotencyKey) {
      if (options.required === false) {
        return next();
      }

      return next(new ApiError("DUPLICATE_REQUEST", "Missing Idempotency-Key", 400));
    }

    const cached = readIdempotency(options.scope, idempotencyKey);
    if (cached) {
      const cachedPayload = (cached as { response?: unknown; statusCode?: number; payload?: unknown }).response ?? cached;
      const cachedStatus = (cached as { statusCode?: number }).statusCode || 200;
      const payload = (() => {
        if (
          cachedPayload &&
          typeof cachedPayload === "object" &&
          "data" in cachedPayload &&
          "request_id" in cachedPayload &&
          "timestamp" in cachedPayload
        ) {
          const typed = cachedPayload as Record<string, unknown> & { request_id?: string; timestamp?: string };
          typed.request_id = req.requestId;
          typed.timestamp = new Date().toISOString();
          return typed;
        }
        return cachedPayload;
      })();
      return _res.status(cachedStatus).json(payload);
    }

    req.body = { ...(req.body || {}), __idempotencyKey: idempotencyKey } as Request["body"];

    const originalJson = _res.json.bind(_res);
    const originalStatus = _res.status.bind(_res);
    let statusCode = 200;
    _res.status = function statusWithStore(code: number) {
      statusCode = code;
      return originalStatus(code);
    };
    _res.json = function jsonWithStore(payload: unknown) {
      const responsePayload = payload &&
        typeof payload === "object" &&
        "data" in payload &&
        "request_id" in payload &&
        "timestamp" in payload
        ? successResponse(req, (payload as { data: unknown }).data)
        : payload;
      writeIdempotency(
        options.scope,
        idempotencyKey,
        {
          response: responsePayload,
          statusCode,
          at: Date.now()
        },
        options.ttlMs || env.IDEMPOTENCY_TTL_MS
      );
      return originalJson(responsePayload);
    };

    next();
  };
}
