package com.walkguide.service;

import com.walkguide.dto.request.AiConfigUpdateRequest;
import com.walkguide.dto.response.AiConfigResponse;
import com.walkguide.entity.AiConfig;
import com.walkguide.entity.PairingRelation;
import com.walkguide.entity.User;
import com.walkguide.enums.PairingStatus;
import com.walkguide.exception.PairingException;
import com.walkguide.repository.AiConfigRepository;
import com.walkguide.repository.PairingRelationRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Optional;

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

@ExtendWith(MockitoExtension.class)
@DisplayName("AiConfigService Unit Tests")
class AiConfigServiceTest {

    @Mock AiConfigRepository aiConfigRepository;
    @Mock PairingRelationRepository pairingRelationRepository;
    @Mock FcmService fcmService;

    @InjectMocks AiConfigService aiConfigService;

    private User guardian;
    private User user;
    private PairingRelation activePairing;
    private AiConfig existingConfig;

    @BeforeEach
    void setUp() {
        guardian = User.builder()
                .id(1L).email("guardian@test.com").role("ROLE_GUARDIAN")
                .displayName("Guardian").fcmToken("guardian-fcm").build();

        user = User.builder()
                .id(2L).email("user@test.com").role("ROLE_USER")
                .displayName("User").fcmToken("user-fcm").build();

        activePairing = PairingRelation.builder()
                .id(5L).guardian(guardian).user(user).status(PairingStatus.ACTIVE).build();

        existingConfig = AiConfig.builder()
                .id(10L).userId(2L).guardianId(1L)
                .confidenceThreshold(0.5)
                .alertDistanceClose(1.5)
                .alertDistanceMedium(3.0)
                .maxInferenceFps(5)
                .build();
    }

    // ===== GET CONFIG TESTS =====

    @Test
    @DisplayName("getConfig - konfigurasi ada harus return data dari DB")
    void getConfig_configExists_shouldReturnExistingConfig() {
        when(aiConfigRepository.findByUserId(2L)).thenReturn(Optional.of(existingConfig));

        AiConfigResponse result = aiConfigService.getConfig(2L);

        assertThat(result.getId()).isEqualTo(10L);
        assertThat(result.getConfidenceThreshold()).isEqualTo(0.5);
        assertThat(result.getAlertDistanceClose()).isEqualTo(1.5);
        verify(aiConfigRepository, never()).save(any());
    }

    @Test
    @DisplayName("getConfig - konfigurasi belum ada harus buat default dan simpan")
    void getConfig_configNotExists_shouldCreateDefaultAndSave() {
        AiConfig newConfig = AiConfig.builder().id(99L).userId(2L).build();

        when(aiConfigRepository.findByUserId(2L)).thenReturn(Optional.empty());
        when(aiConfigRepository.save(any(AiConfig.class))).thenReturn(newConfig);

        AiConfigResponse result = aiConfigService.getConfig(2L);

        assertThat(result).isNotNull();
        verify(aiConfigRepository).save(any(AiConfig.class));
    }

    // ===== UPDATE CONFIG BY GUARDIAN TESTS =====

    @Test
    @DisplayName("updateConfigByGuardian - update confidenceThreshold berhasil")
    void updateConfigByGuardian_updateThreshold_shouldSaveAndNotify() {
        AiConfigUpdateRequest req = new AiConfigUpdateRequest();
        req.setConfidenceThreshold(0.75);

        when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
                .thenReturn(Optional.of(activePairing));
        when(aiConfigRepository.findByUserId(2L)).thenReturn(Optional.of(existingConfig));
        when(aiConfigRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));

        AiConfigResponse result = aiConfigService.updateConfigByGuardian(1L, req);

        assertThat(result.getConfidenceThreshold()).isEqualTo(0.75);
        verify(aiConfigRepository).save(any(AiConfig.class));
        verify(fcmService).sendToToken(eq("user-fcm"), anyString(), anyString(), anyMap());
    }

    @Test
    @DisplayName("updateConfigByGuardian - update semua field sekaligus")
    void updateConfigByGuardian_updateAllFields_shouldUpdateAll() {
        AiConfigUpdateRequest req = new AiConfigUpdateRequest();
        req.setConfidenceThreshold(0.8);
        req.setAlertDistanceClose(1.0);
        req.setAlertDistanceMedium(2.5);
        req.setMaxInferenceFps(10);
        req.setEnabledLabels("person,car,motorcycle");

        when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
                .thenReturn(Optional.of(activePairing));
        when(aiConfigRepository.findByUserId(2L)).thenReturn(Optional.of(existingConfig));
        when(aiConfigRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));

        AiConfigResponse result = aiConfigService.updateConfigByGuardian(1L, req);

        assertThat(result.getConfidenceThreshold()).isEqualTo(0.8);
        assertThat(result.getAlertDistanceClose()).isEqualTo(1.0);
        assertThat(result.getAlertDistanceMedium()).isEqualTo(2.5);
        assertThat(result.getMaxInferenceFps()).isEqualTo(10);
        assertThat(result.getEnabledLabels()).isEqualTo("person,car,motorcycle");
    }

    @Test
    @DisplayName("updateConfigByGuardian - field null tidak boleh mengubah nilai existing")
    void updateConfigByGuardian_nullFields_shouldNotOverrideExisting() {
        // Request dengan semua field null — tidak ada yang berubah
        AiConfigUpdateRequest req = new AiConfigUpdateRequest();

        when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
                .thenReturn(Optional.of(activePairing));
        when(aiConfigRepository.findByUserId(2L)).thenReturn(Optional.of(existingConfig));
        when(aiConfigRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));

        AiConfigResponse result = aiConfigService.updateConfigByGuardian(1L, req);

        // Nilai lama tetap
        assertThat(result.getConfidenceThreshold()).isEqualTo(0.5);
        assertThat(result.getAlertDistanceClose()).isEqualTo(1.5);
    }

    @Test
    @DisplayName("updateConfigByGuardian - Guardian tanpa pairing aktif harus throw PairingException")
    void updateConfigByGuardian_noPairing_shouldThrow() {
        when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
                .thenReturn(Optional.empty());

        assertThatThrownBy(() -> aiConfigService.updateConfigByGuardian(1L, new AiConfigUpdateRequest()))
                .isInstanceOf(PairingException.class)
                .hasMessageContaining("Tidak ada user yang dipair");

        verify(aiConfigRepository, never()).save(any());
        verify(fcmService, never()).sendToToken(any(), any(), any(), any());
    }

    @Test
    @DisplayName("updateConfigByGuardian - konfigurasi belum ada harus buat baru lalu update")
    void updateConfigByGuardian_configNotExists_shouldCreateAndUpdate() {
        AiConfigUpdateRequest req = new AiConfigUpdateRequest();
        req.setConfidenceThreshold(0.6);

        AiConfig freshConfig = AiConfig.builder().userId(2L).guardianId(1L)
                .confidenceThreshold(0.5).build();

        when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
                .thenReturn(Optional.of(activePairing));
        // Pertama kali findByUserId kosong → save default → kembalikan freshConfig
        when(aiConfigRepository.findByUserId(2L)).thenReturn(Optional.empty());
        when(aiConfigRepository.save(any())).thenAnswer(inv -> {
            AiConfig c = inv.getArgument(0);
            c.setId(50L);
            return c;
        });

        AiConfigResponse result = aiConfigService.updateConfigByGuardian(1L, req);

        assertThat(result).isNotNull();
        // save dipanggil 2x: sekali buat default, sekali update
        verify(aiConfigRepository, times(2)).save(any(AiConfig.class));
    }

    @Test
    @DisplayName("updateConfigByGuardian - harus kirim FCM ke user setelah update")
    void updateConfigByGuardian_shouldSendFcmToUser() {
        AiConfigUpdateRequest req = new AiConfigUpdateRequest();
        req.setMaxInferenceFps(8);

        when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
                .thenReturn(Optional.of(activePairing));
        when(aiConfigRepository.findByUserId(2L)).thenReturn(Optional.of(existingConfig));
        when(aiConfigRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));

        aiConfigService.updateConfigByGuardian(1L, req);

        ArgumentCaptor<String> tokenCaptor = ArgumentCaptor.forClass(String.class);
        verify(fcmService).sendToToken(tokenCaptor.capture(), anyString(), anyString(), anyMap());
        assertThat(tokenCaptor.getValue()).isEqualTo("user-fcm");
    }
}