package cloud.nstart.terravault.ordinis.restapi.idempotency;

import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.springframework.util.StreamUtils;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

/**
 * Кэширует тело запроса в памяти, чтобы его можно было прочитать несколько раз
 * (для idempotency hash + handler-аргумента). В отличие от
 * {@link org.springframework.web.util.ContentCachingRequestWrapper}, отдает
 * свежий InputStream при каждом вызове {@link #getInputStream()}.
 */
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {

  private final byte[] cachedBody;

  public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
    super(request);
    InputStream is = request.getInputStream();
    this.cachedBody = StreamUtils.copyToByteArray(is);
  }

  public byte[] getCachedBody() {
    return cachedBody;
  }

  @Override
  public ServletInputStream getInputStream() {
    return new CachedServletInputStream(cachedBody);
  }

  @Override
  public BufferedReader getReader() {
    return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
  }

  private static class CachedServletInputStream extends ServletInputStream {
    private final ByteArrayInputStream buf;

    CachedServletInputStream(byte[] data) {
      this.buf = new ByteArrayInputStream(data);
    }

    @Override public boolean isFinished() { return buf.available() == 0; }
    @Override public boolean isReady() { return true; }
    @Override public void setReadListener(ReadListener listener) { /* no-op */ }
    @Override public int read() { return buf.read(); }
    @Override public int read(byte[] b, int off, int len) { return buf.read(b, off, len); }
  }
}
