package com.walkguide.service;

import com.walkguide.dto.response.PairingStatusResponse;
import com.walkguide.entity.PairingRelation;
import com.walkguide.entity.User;
import com.walkguide.enums.PairingStatus;
import com.walkguide.exception.PairingException;
import com.walkguide.exception.ResourceNotFoundException;
import com.walkguide.repository.*;
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.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("PairingService Unit Tests")
class PairingServiceTest {

    @Mock PairingRelationRepository pairingRelationRepository;
    @Mock UserRepository userRepository;
    @Mock VoiceCommandConfigRepository voiceCommandConfigRepository;
    @Mock HardwareShortcutRepository hardwareShortcutRepository;
    @Mock AiConfigRepository aiConfigRepository;
    @Mock ActivityLogService activityLogService;
    @Mock FcmService fcmService;

    @InjectMocks PairingService pairingService;

    private User guardian;
    private User user;

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

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

    // ===== INVITE USER TESTS =====

    @Test
    @DisplayName("inviteUser - berhasil mengirim invite ke user yang valid")
    void inviteUser_success_shouldSavePairingAndSendFcm() {
        when(pairingRelationRepository.existsByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE)).thenReturn(false);
        when(pairingRelationRepository.existsByGuardian_IdAndStatus(1L, PairingStatus.PENDING)).thenReturn(false);
        when(userRepository.findById(1L)).thenReturn(Optional.of(guardian));
        when(userRepository.findByUniqueUserId("ABC123DEF456")).thenReturn(Optional.of(user));
        when(pairingRelationRepository.existsByUser_IdAndStatus(2L, PairingStatus.ACTIVE)).thenReturn(false);
        when(pairingRelationRepository.save(any(PairingRelation.class))).thenAnswer(inv -> {
            PairingRelation p = inv.getArgument(0);
            p.setId(10L);
            return p;
        });

        PairingStatusResponse result = pairingService.inviteUser(1L, "ABC123DEF456");

        assertThat(result).isNotNull();
        verify(pairingRelationRepository).save(any(PairingRelation.class));
        verify(fcmService).sendToToken(eq("user-fcm-token"), anyString(), anyString(), anyMap());
        verify(activityLogService).createLog(eq(guardian), any(), anyString(), any());
    }

    @Test
    @DisplayName("inviteUser - Guardian sudah ACTIVE pairing harus throw PairingException")
    void inviteUser_alreadyActivePairing_shouldThrow() {
        when(pairingRelationRepository.existsByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE)).thenReturn(true);

        assertThatThrownBy(() -> pairingService.inviteUser(1L, "ABC123DEF456"))
                .isInstanceOf(PairingException.class)
                .hasMessageContaining("sudah memiliki user yang dipair");

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

    @Test
    @DisplayName("inviteUser - Guardian sudah PENDING invite harus throw PairingException")
    void inviteUser_alreadyPendingInvite_shouldThrow() {
        when(pairingRelationRepository.existsByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE)).thenReturn(false);
        when(pairingRelationRepository.existsByGuardian_IdAndStatus(1L, PairingStatus.PENDING)).thenReturn(true);

        assertThatThrownBy(() -> pairingService.inviteUser(1L, "ABC123DEF456"))
                .isInstanceOf(PairingException.class)
                .hasMessageContaining("sudah punya invite yang menunggu");

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

    @Test
    @DisplayName("inviteUser - uniqueUserId tidak ditemukan harus throw ResourceNotFoundException")
    void inviteUser_userNotFound_shouldThrow() {
        when(pairingRelationRepository.existsByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE)).thenReturn(false);
        when(pairingRelationRepository.existsByGuardian_IdAndStatus(1L, PairingStatus.PENDING)).thenReturn(false);
        when(userRepository.findById(1L)).thenReturn(Optional.of(guardian));
        when(userRepository.findByUniqueUserId("INVALID")).thenReturn(Optional.empty());

        assertThatThrownBy(() -> pairingService.inviteUser(1L, "INVALID"))
                .isInstanceOf(ResourceNotFoundException.class)
                .hasMessageContaining("tidak ditemukan");
    }

    @Test
    @DisplayName("inviteUser - target bukan ROLE_USER harus throw PairingException")
    void inviteUser_targetNotUser_shouldThrow() {
        User anotherGuardian = User.builder()
                .id(3L).role("ROLE_GUARDIAN").uniqueUserId("GRD000000001").build();

        when(pairingRelationRepository.existsByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE)).thenReturn(false);
        when(pairingRelationRepository.existsByGuardian_IdAndStatus(1L, PairingStatus.PENDING)).thenReturn(false);
        when(userRepository.findById(1L)).thenReturn(Optional.of(guardian));
        when(userRepository.findByUniqueUserId("GRD000000001")).thenReturn(Optional.of(anotherGuardian));

        assertThatThrownBy(() -> pairingService.inviteUser(1L, "GRD000000001"))
                .isInstanceOf(PairingException.class)
                .hasMessageContaining("bukan milik User");
    }

    @Test
    @DisplayName("inviteUser - User sudah ACTIVE dengan Guardian lain harus throw PairingException")
    void inviteUser_userAlreadyPaired_shouldThrow() {
        when(pairingRelationRepository.existsByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE)).thenReturn(false);
        when(pairingRelationRepository.existsByGuardian_IdAndStatus(1L, PairingStatus.PENDING)).thenReturn(false);
        when(userRepository.findById(1L)).thenReturn(Optional.of(guardian));
        when(userRepository.findByUniqueUserId("ABC123DEF456")).thenReturn(Optional.of(user));
        when(pairingRelationRepository.existsByUser_IdAndStatus(2L, PairingStatus.ACTIVE)).thenReturn(true);

        assertThatThrownBy(() -> pairingService.inviteUser(1L, "ABC123DEF456"))
                .isInstanceOf(PairingException.class)
                .hasMessageContaining("sudah dipair dengan Guardian lain");
    }

    // ===== RESPOND TO PAIRING TESTS =====

    @Test
    @DisplayName("respondToPairing - accept: status harus jadi ACTIVE")
    void respondToPairing_accept_shouldSetStatusActive() {
        PairingRelation pairing = PairingRelation.builder()
                .id(10L).guardian(guardian).user(user).status(PairingStatus.PENDING).build();

        when(pairingRelationRepository.findById(10L)).thenReturn(Optional.of(pairing));
        when(pairingRelationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
        // Stub untuk pembuatan config saat accept
        when(aiConfigRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));

        PairingStatusResponse result = pairingService.respondToPairing(2L, 10L, true);

        assertThat(result).isNotNull();
        assertThat(pairing.getStatus()).isEqualTo(PairingStatus.ACTIVE);
        verify(pairingRelationRepository).save(pairing);
    }

    @Test
    @DisplayName("respondToPairing - reject: status harus jadi REJECTED")
    void respondToPairing_reject_shouldSetStatusRejected() {
        PairingRelation pairing = PairingRelation.builder()
                .id(10L).guardian(guardian).user(user).status(PairingStatus.PENDING).build();

        when(pairingRelationRepository.findById(10L)).thenReturn(Optional.of(pairing));
        when(pairingRelationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));

        PairingStatusResponse result = pairingService.respondToPairing(2L, 10L, false);

        assertThat(result).isNotNull();
        assertThat(pairing.getStatus()).isEqualTo(PairingStatus.REJECTED);
    }

    @Test
    @DisplayName("respondToPairing - user yang salah harus throw PairingException")
    void respondToPairing_wrongUser_shouldThrow() {
        PairingRelation pairing = PairingRelation.builder()
                .id(10L).guardian(guardian).user(user).status(PairingStatus.PENDING).build();

        when(pairingRelationRepository.findById(10L)).thenReturn(Optional.of(pairing));

        // userId=99 bukan pemilik pairing ini (user.id=2)
        assertThatThrownBy(() -> pairingService.respondToPairing(99L, 10L, true))
                .isInstanceOf(PairingException.class)
                .hasMessageContaining("tidak berhak");
    }

    @Test
    @DisplayName("respondToPairing - pairing yang sudah direspons harus throw PairingException")
    void respondToPairing_alreadyResponded_shouldThrow() {
        PairingRelation pairing = PairingRelation.builder()
                .id(10L).guardian(guardian).user(user).status(PairingStatus.ACTIVE).build();

        when(pairingRelationRepository.findById(10L)).thenReturn(Optional.of(pairing));

        assertThatThrownBy(() -> pairingService.respondToPairing(2L, 10L, true))
                .isInstanceOf(PairingException.class)
                .hasMessageContaining("sudah direspons");
    }
}