package com.zcloud.personnel.positioning.command.query;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zcloud.personnel.positioning.integration.finds.FindsOpenApiClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;

import java.time.LocalDateTime;
import java.util.Collections;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class FindsStaffLocationStatusSyncServiceTest {
    private final ObjectMapper objectMapper = new ObjectMapper();
    private FindsOpenApiClient findsOpenApiClient;
    private FindsStaffMappingSyncService mappingSyncService;
    private JdbcTemplate jdbcTemplate;

    @BeforeEach
    void setUp() {
        findsOpenApiClient = mock(FindsOpenApiClient.class);
        mappingSyncService = mock(FindsStaffMappingSyncService.class);
        jdbcTemplate = mock(JdbcTemplate.class);
    }

    @Test
    void unchangedStaffCountSkipsFullSyncAndRefreshesStatus() throws Exception {
        stubCount(2L);
        stubTerminalStatusPages();

        FindsStaffLocationStatusSyncService service = new FindsStaffLocationStatusSyncService(
                findsOpenApiClient, mappingSyncService, jdbcTemplate);
        FindsStaffLocationStatusSyncService.SyncSummary summary = service.synchronize();

        assertFalse(summary.isFullSync());
        verify(mappingSyncService, never()).syncAll();
        verify(jdbcTemplate).update(anyString(), any(), eq("FINDS"), any());
    }

    @Test
    void changedStaffCountRunsFullSyncBeforeStatusRefresh() throws Exception {
        stubCount(2L);
        when(jdbcTemplate.queryForObject(anyString(), eq(Long.class), eq("FINDS"))).thenReturn(1L);
        stubTerminalStatusPages();
        when(mappingSyncService.syncAll()).thenReturn(2);

        FindsStaffLocationStatusSyncService service = new FindsStaffLocationStatusSyncService(
                findsOpenApiClient, mappingSyncService, jdbcTemplate);
        FindsStaffLocationStatusSyncService.SyncSummary summary = service.synchronize();

        assertTrue(summary.isFullSync());
        verify(mappingSyncService).syncAll();
    }

    @Test
    void failedOnlineStatusPageDoesNotMarkExistingRowsOffline() throws Exception {
        stubCount(2L);
        when(findsOpenApiClient.postFailFast(eq("finds.terminal.list"), anyMap()))
                .thenReturn(json("{\"code\":500,\"data\":{\"total\":0,\"data\":[]}}"));

        FindsStaffLocationStatusSyncService service = new FindsStaffLocationStatusSyncService(
                findsOpenApiClient, mappingSyncService, jdbcTemplate);

        try {
            service.synchronize();
        } catch (RuntimeException expected) {
            // The status cycle must fail before any database status update.
        }
        verify(jdbcTemplate, never()).batchUpdate(anyString(), any(java.util.List.class));
        verify(jdbcTemplate, never()).update(anyString(), any(), any(), any());
    }

    @Test
    void missingRemoteStaffTotalStopsBeforeDatabaseStatusUpdate() throws Exception {
        when(findsOpenApiClient.postFailFast(eq("finds.staff.list"), anyMap()))
                .thenReturn(json("{\"code\":200,\"data\":{\"data\":[]}}"));

        FindsStaffLocationStatusSyncService service = new FindsStaffLocationStatusSyncService(
                findsOpenApiClient, mappingSyncService, jdbcTemplate);

        assertThrows(RuntimeException.class, service::synchronize);
        verify(mappingSyncService, never()).syncAll();
        verify(jdbcTemplate, never()).batchUpdate(anyString(), any(java.util.List.class));
        verify(jdbcTemplate, never()).update(anyString(), any(), any(), any());
    }

    private void stubCount(long remoteCount) throws Exception {
        when(findsOpenApiClient.postFailFast(eq("finds.staff.list"), anyMap()))
                .thenReturn(json("{\"code\":200,\"data\":{\"total\":" + remoteCount
                        + ",\"data\":[]}}"));
        when(jdbcTemplate.queryForObject(anyString(), eq(Long.class), eq("FINDS")))
                .thenReturn(remoteCount);
        when(jdbcTemplate.queryForObject(anyString(), eq(LocalDateTime.class), eq("FINDS")))
                .thenReturn(LocalDateTime.now());
    }

    private void stubTerminalStatusPages() throws Exception {
        when(findsOpenApiClient.postFailFast(eq("finds.terminal.list"), anyMap()))
                .thenReturn(json("{\"code\":200,\"data\":{\"total\":0,\"data\":[]}}"));
    }

    private JsonNode json(String value) throws Exception {
        return objectMapper.readTree(value);
    }
}
