package org.raddatz.familienarchiv.config;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;

import java.io.PrintWriter;
import java.io.StringWriter;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;

class RateLimitInterceptorTest {

    private RateLimitInterceptor interceptor;
    private HttpServletRequest request;
    private HttpServletResponse response;
    private PrintWriter writer;

    @BeforeEach
    void setUp() throws Exception {
        interceptor = new RateLimitInterceptor();
        request = mock(HttpServletRequest.class);
        response = mock(HttpServletResponse.class);
        writer = new PrintWriter(new StringWriter());
        when(response.getWriter()).thenReturn(writer);
        when(request.getRemoteAddr()).thenReturn("1.2.3.4");
    }

    @Test
    void allows_requests_below_limit() throws Exception {
        for (int i = 0; i < 10; i++) {
            assertThat(interceptor.preHandle(request, response, null)).isTrue();
        }
    }

    @Test
    void blocks_request_when_limit_exceeded() throws Exception {
        for (int i = 0; i < 10; i++) {
            interceptor.preHandle(request, response, null);
        }
        assertThat(interceptor.preHandle(request, response, null)).isFalse();
        verify(response).setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
    }

    @Test
    void blocked_response_includes_retry_after_header() throws Exception {
        for (int i = 0; i < 10; i++) {
            interceptor.preHandle(request, response, null);
        }
        interceptor.preHandle(request, response, null);
        verify(response).setHeader("Retry-After", "60");
    }

    @Test
    void different_ips_have_independent_limits() throws Exception {
        HttpServletRequest other = mock(HttpServletRequest.class);
        when(other.getRemoteAddr()).thenReturn("9.9.9.9");

        for (int i = 0; i < 10; i++) {
            interceptor.preHandle(request, response, null);
        }
        // first IP is at limit — second IP is not
        assertThat(interceptor.preHandle(other, response, null)).isTrue();
    }

    @Test
    void ignores_x_forwarded_for_from_untrusted_remote_addr() throws Exception {
        when(request.getRemoteAddr()).thenReturn("5.5.5.5"); // not a trusted proxy
        when(request.getHeader("X-Forwarded-For")).thenReturn("99.99.99.99");

        // exhaust the limit for the real remote IP (5.5.5.5), not the spoofed one
        for (int i = 0; i < 10; i++) {
            interceptor.preHandle(request, response, null);
        }
        assertThat(interceptor.preHandle(request, response, null)).isFalse();

        // spoofed IP (99.99.99.99) should not have been rate-limited
        HttpServletRequest clean = mock(HttpServletRequest.class);
        when(clean.getRemoteAddr()).thenReturn("99.99.99.99");
        assertThat(interceptor.preHandle(clean, response, null)).isTrue();
    }

    @Test
    void trusts_x_forwarded_for_from_localhost() throws Exception {
        when(request.getRemoteAddr()).thenReturn("127.0.0.1"); // trusted proxy
        when(request.getHeader("X-Forwarded-For")).thenReturn("20.20.20.20");

        for (int i = 0; i < 10; i++) {
            interceptor.preHandle(request, response, null);
        }
        // the rate-limited IP should be 20.20.20.20 (from header), not 127.0.0.1
        assertThat(interceptor.preHandle(request, response, null)).isFalse();
    }
}
