package com.walkguide.service;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.Map;

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

@DisplayName("MockDataService Unit Tests")
class MockDataServiceTest {

    private MockDataService mockDataService;

    @BeforeEach
    void setUp() {
        mockDataService = new MockDataService();
    }

    // ===== getUserStatus TESTS =====

    @Test
    @DisplayName("getUserStatus - harus return data status user yang sudah diinisialisasi")
    void getUserStatus_shouldReturnInitializedUserStatus() {
        Map<String, Object> status = mockDataService.getUserStatus();

        assertThat(status).isNotNull();
        assertThat(status).containsKey("status");
        assertThat(status).containsKey("location");
        assertThat(status).containsKey("batteryLevel");
        assertThat(status).containsKey("lastSeen");
    }

    @Test
    @DisplayName("getUserStatus - nilai default status harus 'Sedang Berjalan'")
    void getUserStatus_defaultStatus_shouldBeSedangBerjalan() {
        Map<String, Object> status = mockDataService.getUserStatus();

        assertThat(status.get("status")).isEqualTo("Sedang Berjalan");
    }

    @Test
    @DisplayName("getUserStatus - nilai default location harus 'Jl. Kenangan, SBY'")
    void getUserStatus_defaultLocation_shouldBeJlKenangan() {
        Map<String, Object> status = mockDataService.getUserStatus();

        assertThat(status.get("location")).isEqualTo("Jl. Kenangan, SBY");
    }

    @Test
    @DisplayName("getUserStatus - nilai default batteryLevel harus 85")
    void getUserStatus_defaultBatteryLevel_shouldBe85() {
        Map<String, Object> status = mockDataService.getUserStatus();

        assertThat(status.get("batteryLevel")).isEqualTo(85);
    }

    @Test
    @DisplayName("getUserStatus - nilai default lastSeen harus berupa timestamp ISO 8601")
    void getUserStatus_defaultLastSeen_shouldBeIsoTimestamp() {
        Map<String, Object> status = mockDataService.getUserStatus();

        assertThat(status.get("lastSeen")).isEqualTo("2026-04-23T08:40:00Z");
    }

    @Test
    @DisplayName("getUserStatus - harus return referensi map yang sama (stateful)")
    void getUserStatus_shouldReturnSameMapReference() {
        Map<String, Object> first = mockDataService.getUserStatus();
        Map<String, Object> second = mockDataService.getUserStatus();

        assertThat(first).isSameAs(second);
    }

    // ===== updateShortcuts TESTS =====

    @Test
    @DisplayName("updateShortcuts - harus menambahkan entry baru ke hardwareShortcuts")
    void updateShortcuts_newEntry_shouldBeAdded() {
        Map<String, Object> newShortcuts = new HashMap<>();
        newShortcuts.put("volumeUpAction", "start_navigation");
        newShortcuts.put("powerAction", "send_sos");

        Map<String, Object> result = mockDataService.updateShortcuts(newShortcuts);

        assertThat(result.get("volumeUpAction")).isEqualTo("start_navigation");
        assertThat(result.get("powerAction")).isEqualTo("send_sos");
    }

    @Test
    @DisplayName("updateShortcuts - nilai lama harus ter-override dengan nilai baru")
    void updateShortcuts_existingKey_shouldBeOverridden() {
        Map<String, Object> update = new HashMap<>();
        update.put("volumeUpAction", "call_guardian"); // override dari "accept_call"

        Map<String, Object> result = mockDataService.updateShortcuts(update);

        assertThat(result.get("volumeUpAction")).isEqualTo("call_guardian");
    }

    @Test
    @DisplayName("updateShortcuts - key yang tidak diupdate harus tetap ada")
    void updateShortcuts_unrelatedKeys_shouldBePreserved() {
        Map<String, Object> update = new HashMap<>();
        update.put("volumeUpAction", "call_guardian");
        // volumeDownAction tidak diubah

        Map<String, Object> result = mockDataService.updateShortcuts(update);

        assertThat(result.get("volumeDownAction")).isEqualTo("emergency_ping");
    }

    @Test
    @DisplayName("updateShortcuts - harus return map yang sama (bukan salinan baru)")
    void updateShortcuts_shouldReturnSameMapInstance() {
        Map<String, Object> before = mockDataService.updateShortcuts(new HashMap<>());
        Map<String, Object> after = mockDataService.updateShortcuts(new HashMap<>());

        assertThat(before).isSameAs(after);
    }

    @Test
    @DisplayName("updateShortcuts - default awal volumeUpAction harus 'accept_call'")
    void updateShortcuts_defaultVolumeUpAction_shouldBeAcceptCall() {
        Map<String, Object> result = mockDataService.updateShortcuts(new HashMap<>());

        assertThat(result.get("volumeUpAction")).isEqualTo("accept_call");
    }

    @Test
    @DisplayName("updateShortcuts - default awal volumeDownAction harus 'emergency_ping'")
    void updateShortcuts_defaultVolumeDownAction_shouldBeEmergencyPing() {
        Map<String, Object> result = mockDataService.updateShortcuts(new HashMap<>());

        assertThat(result.get("volumeDownAction")).isEqualTo("emergency_ping");
    }

    @Test
    @DisplayName("updateShortcuts - update dengan map kosong tidak boleh mengubah nilai yang ada")
    void updateShortcuts_emptyMap_shouldNotModifyExistingValues() {
        Map<String, Object> result = mockDataService.updateShortcuts(new HashMap<>());

        assertThat(result.get("volumeUpAction")).isEqualTo("accept_call");
        assertThat(result.get("volumeDownAction")).isEqualTo("emergency_ping");
    }

    @Test
    @DisplayName("updateShortcuts - update berulang harus accumulate semua perubahan")
    void updateShortcuts_multipleUpdates_shouldAccumulateChanges() {
        mockDataService.updateShortcuts(Map.of("cameraAction", "take_photo"));
        mockDataService.updateShortcuts(Map.of("homeAction", "open_walkguide"));

        Map<String, Object> result = mockDataService.updateShortcuts(new HashMap<>());

        assertThat(result.get("cameraAction")).isEqualTo("take_photo");
        assertThat(result.get("homeAction")).isEqualTo("open_walkguide");
    }

    // ===== updateAiSettings TESTS =====

    @Test
    @DisplayName("updateAiSettings - harus menambahkan entry baru ke aiSettings")
    void updateAiSettings_newEntry_shouldBeAdded() {
        Map<String, Object> newSettings = new HashMap<>();
        newSettings.put("speechRate", 1.5);
        newSettings.put("language", "id-ID");

        Map<String, Object> result = mockDataService.updateAiSettings(newSettings);

        assertThat(result.get("speechRate")).isEqualTo(1.5);
        assertThat(result.get("language")).isEqualTo("id-ID");
    }

    @Test
    @DisplayName("updateAiSettings - nilai lama harus ter-override dengan nilai baru")
    void updateAiSettings_existingKey_shouldBeOverridden() {
        Map<String, Object> update = new HashMap<>();
        update.put("alertDistanceMeters", 5.0); // override dari 2.5

        Map<String, Object> result = mockDataService.updateAiSettings(update);

        assertThat(result.get("alertDistanceMeters")).isEqualTo(5.0);
    }

    @Test
    @DisplayName("updateAiSettings - key yang tidak diupdate harus tetap ada")
    void updateAiSettings_unrelatedKeys_shouldBePreserved() {
        Map<String, Object> update = new HashMap<>();
        update.put("alertDistanceMeters", 3.0);
        // hapticFeedback tidak diubah

        Map<String, Object> result = mockDataService.updateAiSettings(update);

        assertThat(result.get("hapticFeedback")).isEqualTo(true);
    }

    @Test
    @DisplayName("updateAiSettings - default alertDistanceMeters harus 2.5")
    void updateAiSettings_defaultAlertDistance_shouldBe2point5() {
        Map<String, Object> result = mockDataService.updateAiSettings(new HashMap<>());

        assertThat(result.get("alertDistanceMeters")).isEqualTo(2.5);
    }

    @Test
    @DisplayName("updateAiSettings - default hapticFeedback harus true")
    void updateAiSettings_defaultHapticFeedback_shouldBeTrue() {
        Map<String, Object> result = mockDataService.updateAiSettings(new HashMap<>());

        assertThat(result.get("hapticFeedback")).isEqualTo(true);
    }

    @Test
    @DisplayName("updateAiSettings - harus return map yang sama (bukan salinan baru)")
    void updateAiSettings_shouldReturnSameMapInstance() {
        Map<String, Object> before = mockDataService.updateAiSettings(new HashMap<>());
        Map<String, Object> after = mockDataService.updateAiSettings(new HashMap<>());

        assertThat(before).isSameAs(after);
    }

    @Test
    @DisplayName("updateAiSettings - update dengan map kosong tidak boleh mengubah nilai yang ada")
    void updateAiSettings_emptyMap_shouldNotModifyExistingValues() {
        Map<String, Object> result = mockDataService.updateAiSettings(new HashMap<>());

        assertThat(result.get("alertDistanceMeters")).isEqualTo(2.5);
        assertThat(result.get("hapticFeedback")).isEqualTo(true);
    }

    @Test
    @DisplayName("updateAiSettings - update boolean ke false harus tersimpan dengan benar")
    void updateAiSettings_disableHapticFeedback_shouldBeFalse() {
        Map<String, Object> update = new HashMap<>();
        update.put("hapticFeedback", false);

        Map<String, Object> result = mockDataService.updateAiSettings(update);

        assertThat(result.get("hapticFeedback")).isEqualTo(false);
    }

    @Test
    @DisplayName("updateAiSettings - update berulang harus accumulate semua perubahan")
    void updateAiSettings_multipleUpdates_shouldAccumulateChanges() {
        mockDataService.updateAiSettings(Map.of("confidenceThreshold", 0.85));
        mockDataService.updateAiSettings(Map.of("maxObstacleRange", 10));

        Map<String, Object> result = mockDataService.updateAiSettings(new HashMap<>());

        assertThat(result.get("confidenceThreshold")).isEqualTo(0.85);
        assertThat(result.get("maxObstacleRange")).isEqualTo(10);
    }

    // ===== Isolation Tests =====

    @Test
    @DisplayName("instance baru harus selalu punya data default yang segar")
    void newInstance_shouldAlwaysHaveFreshDefaultData() {
        MockDataService instance1 = new MockDataService();
        MockDataService instance2 = new MockDataService();

        instance1.updateShortcuts(Map.of("volumeUpAction", "changed_action"));

        // instance2 tidak terpengaruh perubahan instance1
        assertThat(instance2.updateShortcuts(new HashMap<>()).get("volumeUpAction"))
                .isEqualTo("accept_call");
    }
}
