package com.magistr.app.controller;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.mock.web.MockHttpServletRequest;

import java.sql.SQLException;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

@DisplayName("Безопасное преобразование нарушений целостности БД")
class GlobalExceptionHandlerDataIntegrityTest {

    private final GlobalExceptionHandler handler = new GlobalExceptionHandler();

    @Test
    @DisplayName("известный CHECK возвращает русский 400 без JDBC details")
    void knownCheckConstraintReturnsSafeBadRequest() {
        String technicalMessage = "ERROR: new row violates check constraint "
                + "\"chk_time_slots_time_range\" Detail: failing row contains secret-value";

        var response = handler.handleDataIntegrityViolation(
                violation(technicalMessage, "23514"),
                request("POST", "/api/admin/time-slots")
        );

        assertThat(response.getStatusCode().value()).isEqualTo(400);
        assertThat(response.getBody())
                .containsEntry("error", "Некорректный запрос")
                .containsEntry("message", "Время начала пары должно быть раньше времени окончания");
        assertNoTechnicalDetails(response.getBody());
    }

    @Test
    @DisplayName("известный UNIQUE возвращает русский 409 без имени ограничения")
    void knownUniqueConstraintReturnsSafeConflict() {
        String technicalMessage = "duplicate key value violates unique constraint "
                + "\"uq_subjects_name_ci\" Detail: Key (lower(name))=(secret-value) already exists";

        var response = handler.handleDataIntegrityViolation(
                violation(technicalMessage, "23505"),
                request("POST", "/api/subjects")
        );

        assertThat(response.getStatusCode().value()).isEqualTo(409);
        assertThat(response.getBody())
                .containsEntry("error", "Конфликт")
                .containsEntry("message", "Дисциплина с таким названием уже существует");
        assertNoTechnicalDetails(response.getBody());
    }

    @Test
    @DisplayName("неизвестный constraint получает общий 409 без сырого исключения")
    void unknownConstraintReturnsGenericSafeConflict() {
        String technicalMessage = "duplicate key violates constraint \"tenant_private_constraint\" "
                + "jdbc:postgresql://db/private?password=secret-value";

        var response = handler.handleDataIntegrityViolation(
                violation(technicalMessage, "23505"),
                request("PUT", "/api/private")
        );

        assertThat(response.getStatusCode().value()).isEqualTo(409);
        assertThat(response.getBody())
                .containsEntry("error", "Конфликт")
                .containsEntry("message", "Такая запись уже существует");
        assertNoTechnicalDetails(response.getBody());
    }

    @Test
    @DisplayName("неизвестная integrity-ошибка не превращается в 500 и не раскрывает cause")
    void unknownIntegrityFailureReturnsGenericConflict() {
        var exception = new DataIntegrityViolationException(
                "could not execute statement; jdbc:postgresql://db/private",
                new RuntimeException("password=secret-value")
        );

        var response = handler.handleDataIntegrityViolation(
                exception,
                request("DELETE", "/api/private/1")
        );

        assertThat(response.getStatusCode().value()).isEqualTo(409);
        assertThat(response.getBody())
                .containsEntry("message", "Операция нарушает ограничения целостности данных");
        assertNoTechnicalDetails(response.getBody());
    }

    private DataIntegrityViolationException violation(String message, String sqlState) {
        return new DataIntegrityViolationException(message, new SQLException(message, sqlState));
    }

    private MockHttpServletRequest request(String method, String path) {
        MockHttpServletRequest request = new MockHttpServletRequest(method, path);
        request.setRequestURI(path);
        return request;
    }

    private void assertNoTechnicalDetails(Map<String, Object> body) {
        assertThat(body).isNotNull();
        String rendered = body.toString().toLowerCase();
        assertThat(rendered)
                .doesNotContain("constraint")
                .doesNotContain("jdbc:")
                .doesNotContain("password")
                .doesNotContain("secret-value")
                .doesNotContain("sqlstate");
    }
}
