package de.effigenix.infrastructure.masterdata.web;

import de.effigenix.domain.usermanagement.RoleName;
import de.effigenix.infrastructure.AbstractIntegrationTest;
import de.effigenix.infrastructure.masterdata.web.dto.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult;

import java.time.LocalDate;
import java.util.Set;
import java.util.UUID;

import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

/**
 * Integrationstests für SupplierController.
 *
 * Abgedeckte Testfälle: TC-SUP-01 bis TC-SUP-12, TC-AUTH-01/02
 */
@DisplayName("Supplier Controller Integration Tests")
class SupplierControllerIntegrationTest extends AbstractIntegrationTest {

    private String adminToken;
    private String viewerToken;

    @BeforeEach
    void setUp() {
        String adminRoleId = createRole(RoleName.ADMIN, "Admin");
        String viewerRoleId = createRole(RoleName.PRODUCTION_WORKER, "Viewer");

        String adminId = createUser("sup.admin", "sup.admin@test.com", Set.of(adminRoleId), "BRANCH-01");
        String viewerId = createUser("sup.viewer", "sup.viewer@test.com", Set.of(viewerRoleId), "BRANCH-01");

        adminToken = generateToken(adminId, "sup.admin", "MASTERDATA_WRITE");
        viewerToken = generateToken(viewerId, "sup.viewer", "USER_READ");
    }

    // ==================== TC-SUP-01: Lieferant erstellen – Pflichtfelder ====================

    @Test
    @DisplayName("TC-SUP-01: Lieferant mit Pflichtfeldern erstellen → 201, Status ACTIVE")
    void createSupplier_withRequiredFields_returns201() throws Exception {
        var request = new CreateSupplierRequest(
                "Frisch AG", "+49 30 12345",
                null, null, null, null, null, null, null, null, null);

        mockMvc.perform(post("/api/suppliers")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id").isNotEmpty())
                .andExpect(jsonPath("$.name").value("Frisch AG"))
                .andExpect(jsonPath("$.status").value("ACTIVE"));
    }

    // ==================== TC-SUP-02: Lieferant erstellen – alle Felder ====================

    @Test
    @DisplayName("TC-SUP-02: Lieferant mit allen Feldern erstellen → alle Daten korrekt")
    void createSupplier_withAllFields_returnsCompleteData() throws Exception {
        var request = new CreateSupplierRequest(
                "Bio GmbH", "+49 89 999",
                "bio@example.com", "Max Muster",
                "Gartenstraße 5", "12", "80333", "München", "DE",
                30, "30 Tage netto");

        mockMvc.perform(post("/api/suppliers")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.name").value("Bio GmbH"))
                .andExpect(jsonPath("$.contactInfo.email").value("bio@example.com"))
                .andExpect(jsonPath("$.contactInfo.contactPerson").value("Max Muster"))
                .andExpect(jsonPath("$.address.city").value("München"))
                .andExpect(jsonPath("$.paymentTerms.paymentDueDays").value(30));
    }

    // ==================== TC-SUP-03: Pflichtfelder fehlen ====================

    @Test
    @DisplayName("TC-SUP-03: Lieferant ohne Namen → 400")
    void createSupplier_withBlankName_returns400() throws Exception {
        var request = new CreateSupplierRequest(
                "", "+49 30 12345",
                null, null, null, null, null, null, null, null, null);

        mockMvc.perform(post("/api/suppliers")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isBadRequest());
    }

    @Test
    @DisplayName("TC-SUP-03: Lieferant ohne Telefon → 400")
    void createSupplier_withBlankPhone_returns400() throws Exception {
        var request = new CreateSupplierRequest(
                "Kein Telefon AG", "",
                null, null, null, null, null, null, null, null, null);

        mockMvc.perform(post("/api/suppliers")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isBadRequest());
    }

    // ==================== TC-SUP-04: Doppelter Name ====================

    @Test
    @DisplayName("TC-SUP-04: Lieferant mit doppeltem Namen → 409 Conflict")
    void createSupplier_withDuplicateName_returns409() throws Exception {
        createSupplier("Frisch AG");

        var duplicate = new CreateSupplierRequest(
                "Frisch AG", "+49 30 99999",
                null, null, null, null, null, null, null, null, null);

        mockMvc.perform(post("/api/suppliers")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(duplicate)))
                .andExpect(status().isConflict());
    }

    // ==================== TC-SUP-05: Deaktivieren und Aktivieren ====================

    @Test
    @DisplayName("TC-SUP-05: Lieferant deaktivieren → Status INACTIVE")
    void deactivateSupplier_activeSupplier_returnsInactive() throws Exception {
        String supplierId = createSupplier("Frisch AG");

        mockMvc.perform(post("/api/suppliers/{id}/deactivate", supplierId)
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.status").value("INACTIVE"));
    }

    @Test
    @DisplayName("TC-SUP-05: Lieferant aktivieren → Status ACTIVE")
    void activateSupplier_inactiveSupplier_returnsActive() throws Exception {
        String supplierId = createSupplier("Frisch AG");

        mockMvc.perform(post("/api/suppliers/{id}/deactivate", supplierId)
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk());

        mockMvc.perform(post("/api/suppliers/{id}/activate", supplierId)
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.status").value("ACTIVE"));
    }

    // ==================== TC-SUP-06: Filtern ====================

    @Test
    @DisplayName("TC-SUP-06: Nur aktive Lieferanten filtern → nur ACTIVE in Ergebnis")
    void listSuppliers_filterByActive_returnsOnlyActive() throws Exception {
        String supplierId = createSupplier("Wird inaktiv AG");
        mockMvc.perform(post("/api/suppliers/{id}/deactivate", supplierId)
                .header("Authorization", "Bearer " + adminToken)).andReturn();

        createSupplier("Bleibt aktiv GmbH");

        mockMvc.perform(get("/api/suppliers")
                        .param("status", "ACTIVE")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[*].status", everyItem(is("ACTIVE"))));
    }

    @Test
    @DisplayName("TC-SUP-06: Nur inaktive Lieferanten filtern → nur INACTIVE in Ergebnis")
    void listSuppliers_filterByInactive_returnsOnlyInactive() throws Exception {
        String supplierId = createSupplier("Inaktiv AG");
        mockMvc.perform(post("/api/suppliers/{id}/deactivate", supplierId)
                .header("Authorization", "Bearer " + adminToken)).andReturn();

        mockMvc.perform(get("/api/suppliers")
                        .param("status", "INACTIVE")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[*].status", everyItem(is("INACTIVE"))));
    }

    // ==================== TC-SUP-07: Lieferant bewerten ====================

    @Test
    @DisplayName("TC-SUP-07: Lieferant bewerten → Bewertung und Durchschnitt korrekt")
    void rateSupplier_validScores_returnsBewertungWithAverage() throws Exception {
        String supplierId = createSupplier("Frisch AG");

        var rating = new RateSupplierRequest(4, 3, 5);

        mockMvc.perform(post("/api/suppliers/{id}/rating", supplierId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(rating)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.rating.qualityScore").value(4))
                .andExpect(jsonPath("$.rating.deliveryScore").value(3))
                .andExpect(jsonPath("$.rating.priceScore").value(5));
    }

    // ==================== TC-SUP-08: Bewertungsgrenzen ====================

    @Test
    @DisplayName("TC-SUP-08: Bewertung mit Score 0 → 400 (unter Minimum)")
    void rateSupplier_scoreBelowMin_returns400() throws Exception {
        String supplierId = createSupplier("Frisch AG");

        var rating = new RateSupplierRequest(0, 3, 3);

        mockMvc.perform(post("/api/suppliers/{id}/rating", supplierId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(rating)))
                .andExpect(status().isBadRequest());
    }

    @Test
    @DisplayName("TC-SUP-08: Bewertung mit Score 6 → 400 (über Maximum)")
    void rateSupplier_scoreAboveMax_returns400() throws Exception {
        String supplierId = createSupplier("Frisch AG");

        var rating = new RateSupplierRequest(6, 3, 3);

        mockMvc.perform(post("/api/suppliers/{id}/rating", supplierId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(rating)))
                .andExpect(status().isBadRequest());
    }

    @Test
    @DisplayName("TC-SUP-08: Bewertung mit Grenzwerten 1 und 5 → erfolgreich")
    void rateSupplier_boundaryScores_succeeds() throws Exception {
        String supplierId = createSupplier("Frisch AG");

        var rating = new RateSupplierRequest(1, 5, 1);

        mockMvc.perform(post("/api/suppliers/{id}/rating", supplierId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(rating)))
                .andExpect(status().isOk());
    }

    // ==================== TC-SUP-09: Zertifikat hinzufügen (gültig) ====================

    @Test
    @DisplayName("TC-SUP-09: Gültiges Zertifikat hinzufügen → erscheint in Detailansicht")
    void addCertificate_validCert_appearsInDetail() throws Exception {
        String supplierId = createSupplier("Frisch AG");

        var cert = new AddCertificateRequest(
                "ISO9001", "TÜV",
                LocalDate.of(2024, 1, 1), LocalDate.of(2027, 1, 1));

        mockMvc.perform(post("/api/suppliers/{id}/certificates", supplierId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(cert)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.certificates", hasSize(1)))
                .andExpect(jsonPath("$.certificates[0].certificateType").value("ISO9001"))
                .andExpect(jsonPath("$.certificates[0].issuer").value("TÜV"));
    }

    // ==================== TC-SUP-10: Abgelaufenes Zertifikat ====================

    @Test
    @DisplayName("TC-SUP-10: Abgelaufenes Zertifikat hinzufügen → wird angelegt (kein Blockieren)")
    void addCertificate_expiredCert_isAccepted() throws Exception {
        String supplierId = createSupplier("Frisch AG");

        var cert = new AddCertificateRequest(
                "ISO14001", "TÜV",
                LocalDate.of(2020, 1, 1), LocalDate.of(2023, 12, 31));

        mockMvc.perform(post("/api/suppliers/{id}/certificates", supplierId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(cert)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.certificates", hasSize(1)));
    }

    // ==================== TC-SUP-11: Doppeltes Zertifikat ====================

    @Test
    @DisplayName("TC-SUP-11: Doppeltes Zertifikat wird idempotent ignoriert (kein Duplikat in Liste)")
    void addCertificate_duplicate_isIdempotent() throws Exception {
        String supplierId = createSupplier("Frisch AG");

        var cert = new AddCertificateRequest(
                "ISO9001", "TÜV",
                LocalDate.of(2024, 1, 1), LocalDate.of(2027, 1, 1));

        mockMvc.perform(post("/api/suppliers/{id}/certificates", supplierId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(cert)))
                .andExpect(status().isCreated());

        // Zweiter Aufruf mit identischen Daten: Domain ignoriert Duplikate still
        mockMvc.perform(post("/api/suppliers/{id}/certificates", supplierId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(cert)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.certificates", hasSize(1)));
    }

    // ==================== TC-SUP-12: Zertifikat entfernen ====================

    @Test
    @DisplayName("TC-SUP-12: Zertifikat entfernen → verschwindet aus Detailansicht")
    void removeCertificate_existingCert_removedFromDetail() throws Exception {
        String supplierId = createSupplier("Frisch AG");

        var cert = new AddCertificateRequest(
                "ISO9001", "TÜV",
                LocalDate.of(2024, 1, 1), LocalDate.of(2027, 1, 1));
        mockMvc.perform(post("/api/suppliers/{id}/certificates", supplierId)
                .header("Authorization", "Bearer " + adminToken)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(cert))).andReturn();

        var removeRequest = new RemoveCertificateRequest("ISO9001", "TÜV", LocalDate.of(2024, 1, 1));

        mockMvc.perform(delete("/api/suppliers/{id}/certificates", supplierId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(removeRequest)))
                .andExpect(status().isNoContent());

        mockMvc.perform(get("/api/suppliers/{id}", supplierId)
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.certificates", hasSize(0)));
    }

    // ==================== Allgemein ====================

    @Test
    @DisplayName("Nicht existierenden Lieferanten abfragen → 404")
    void getSupplier_nonExistent_returns404() throws Exception {
        mockMvc.perform(get("/api/suppliers/{id}", UUID.randomUUID())
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isNotFound());
    }

    @Test
    @DisplayName("TC-AUTH-02: Lieferant erstellen ohne MASTERDATA_WRITE → 403")
    void createSupplier_withViewerToken_returns403() throws Exception {
        var request = new CreateSupplierRequest(
                "Kein Zugriff AG", "+49 30 0",
                null, null, null, null, null, null, null, null, null);

        mockMvc.perform(post("/api/suppliers")
                        .header("Authorization", "Bearer " + viewerToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isForbidden());
    }

    @Test
    @DisplayName("TC-AUTH-01: Lieferantenliste ohne MASTERDATA_WRITE → 200")
    void listSuppliers_withViewerToken_returns200() throws Exception {
        mockMvc.perform(get("/api/suppliers")
                        .header("Authorization", "Bearer " + viewerToken))
                .andExpect(status().isOk());
    }

    // ==================== Hilfsmethoden ====================

    private String createSupplier(String name) throws Exception {
        var request = new CreateSupplierRequest(
                name, "+49 30 12345",
                null, null, null, null, null, null, null, null, null);
        MvcResult result = mockMvc.perform(post("/api/suppliers")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isCreated())
                .andReturn();
        return objectMapper.readTree(result.getResponse().getContentAsString()).get("id").asText();
    }
}
