package com.walkguide.service;

import com.walkguide.dto.request.GeofenceConfigRequest;
import com.walkguide.dto.response.GeofenceResponse;
import com.walkguide.entity.GeofenceConfig;
import com.walkguide.entity.PairingRelation;
import com.walkguide.entity.User;
import com.walkguide.enums.PairingStatus;
import com.walkguide.exception.PairingException;
import com.walkguide.repository.GeofenceConfigRepository;
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("GeofenceService Unit Tests")
class GeofenceServiceTest {

    @Mock GeofenceConfigRepository geofenceConfigRepository;
    @Mock PairingRelationRepository pairingRelationRepository;

    @InjectMocks GeofenceService geofenceService;

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

    @BeforeEach
    void setUp() {
        guardian = User.builder().id(1L).email("guardian@test.com").displayName("Guardian Test").build();
        user = User.builder().id(2L).email("user@test.com").displayName("User Test").build();

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

        existingConfig = GeofenceConfig.builder()
                .id(10L)
                .userId(2L)
                .guardianId(1L)
                .centerLat(-7.257)
                .centerLng(112.752)
                .radiusMeters(300.0)
                .enabled(true)
                .build();
    }

    // ===== getConfig TESTS =====

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

        GeofenceResponse result = geofenceService.getConfig(2L);

        assertThat(result).isNotNull();
        assertThat(result.getId()).isEqualTo(10L);
        assertThat(result.getCenterLat()).isEqualTo(-7.257);
        assertThat(result.getCenterLng()).isEqualTo(112.752);
        assertThat(result.getRadiusMeters()).isEqualTo(300.0);
        assertThat(result.getEnabled()).isTrue();
    }

    @Test
    @DisplayName("getConfig - konfigurasi tidak ada: harus return default (disabled, radius 500m)")
    void getConfig_configNotFound_shouldReturnDefaultResponse() {
        when(geofenceConfigRepository.findByUserId(99L)).thenReturn(Optional.empty());

        GeofenceResponse result = geofenceService.getConfig(99L);

        assertThat(result).isNotNull();
        assertThat(result.getEnabled()).isFalse();
        assertThat(result.getRadiusMeters()).isEqualTo(500.0);
        assertThat(result.getId()).isNull();
    }

    // ===== updateConfig TESTS =====

    @Test
    @DisplayName("updateConfig - pairing aktif, config sudah ada: harus update field yang diberikan saja")
    void updateConfig_activePairingAndConfigExists_shouldUpdateFields() {
        GeofenceConfigRequest req = new GeofenceConfigRequest();
        req.setCenterLat(-7.300);
        req.setCenterLng(112.800);
        req.setRadiusMeters(400.0);
        req.setEnabled(true);

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

        GeofenceResponse result = geofenceService.updateConfig(1L, req);

        assertThat(result).isNotNull();
        assertThat(result.getCenterLat()).isEqualTo(-7.300);
        assertThat(result.getCenterLng()).isEqualTo(112.800);
        assertThat(result.getRadiusMeters()).isEqualTo(400.0);
        assertThat(result.getEnabled()).isTrue();
        verify(geofenceConfigRepository).save(any(GeofenceConfig.class));
    }

    @Test
    @DisplayName("updateConfig - config belum ada: harus buat config baru dengan userId dari pairing")
    void updateConfig_configNotExists_shouldCreateNewConfig() {
        GeofenceConfigRequest req = new GeofenceConfigRequest();
        req.setCenterLat(-7.100);
        req.setCenterLng(112.600);
        req.setRadiusMeters(200.0);
        req.setEnabled(false);

        when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
                .thenReturn(Optional.of(activePairing));
        when(geofenceConfigRepository.findByUserId(2L)).thenReturn(Optional.empty());
        when(geofenceConfigRepository.save(any(GeofenceConfig.class))).thenAnswer(inv -> {
            GeofenceConfig saved = inv.getArgument(0);
            saved.setId(20L);
            return saved;
        });

        GeofenceResponse result = geofenceService.updateConfig(1L, req);

        ArgumentCaptor<GeofenceConfig> captor = ArgumentCaptor.forClass(GeofenceConfig.class);
        verify(geofenceConfigRepository).save(captor.capture());
        GeofenceConfig saved = captor.getValue();
        assertThat(saved.getUserId()).isEqualTo(2L);
        assertThat(saved.getGuardianId()).isEqualTo(1L);
        assertThat(result.getCenterLat()).isEqualTo(-7.100);
        assertThat(result.getEnabled()).isFalse();
    }

    @Test
    @DisplayName("updateConfig - request field null: harus tidak menimpa nilai yang sudah ada")
    void updateConfig_partialRequest_shouldNotOverwriteNullFields() {
        GeofenceConfigRequest req = new GeofenceConfigRequest();
        req.setEnabled(false); // hanya ubah enabled, field lain null

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

        GeofenceResponse result = geofenceService.updateConfig(1L, req);

        // centerLat, centerLng, radiusMeters tidak berubah
        assertThat(result.getCenterLat()).isEqualTo(-7.257);
        assertThat(result.getCenterLng()).isEqualTo(112.752);
        assertThat(result.getRadiusMeters()).isEqualTo(300.0);
        assertThat(result.getEnabled()).isFalse();
    }

    @Test
    @DisplayName("updateConfig - tidak ada pairing aktif: harus throw PairingException")
    void updateConfig_noActivePairing_shouldThrowPairingException() {
        GeofenceConfigRequest req = new GeofenceConfigRequest();
        req.setEnabled(true);

        when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
                .thenReturn(Optional.empty());

        assertThatThrownBy(() -> geofenceService.updateConfig(1L, req))
                .isInstanceOf(PairingException.class)
                .hasMessageContaining("Tidak ada user yang dipair");

        verify(geofenceConfigRepository, never()).save(any());
    }
}