package com.infoepoch.pms.agent.domain.care.state;

import com.infoepoch.pms.agent.domain.care.model.CareConversationState;
import com.infoepoch.pms.agent.domain.care.model.CareQuery;
import com.infoepoch.pms.agent.domain.care.model.CareTaskType;
import com.infoepoch.pms.agent.domain.care.understanding.CareQueryUnderstandingService;
import com.infoepoch.pms.agent.observability.ai.AiModelInvoker;
import com.infoepoch.pms.agent.observability.ai.AiObservationLogger;
import com.infoepoch.pms.agent.observability.ai.AiObservationSupport;
import com.infoepoch.pms.agent.properties.AiObservabilityProperties;
import com.infoepoch.pms.agent.properties.CareAgentProperties;
import com.infoepoch.pms.agent.tool.union.care.provider.CareBusinessDataProvider;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class CareConversationStateServiceTest {

    @Mock
    private RedisTemplate<String, Object> redisTemplate;

    @Mock
    private ValueOperations<String, Object> valueOperations;

    private CareConversationStateService service;

    @BeforeEach
    void setUp() {
        CareAgentProperties properties = new CareAgentProperties();
        properties.setConversationTtlMinutes(120);
        when(redisTemplate.opsForValue()).thenReturn(valueOperations);
        service = new CareConversationStateService(redisTemplate, properties);
    }

    @Test
    void shouldLoadTypedConversationStateDirectly() {
        CareConversationState expectedState = createState();
        when(valueOperations.get("care:state:session-1")).thenReturn(expectedState);

        Optional<CareConversationState> loaded = service.load("session-1");

        assertTrue(loaded.isPresent());
        assertEquals(expectedState, loaded.orElseThrow());
    }

    @Test
    void shouldConvertLinkedHashMapToConversationState() {
        CareConversationState expectedState = createState();
        when(valueOperations.get("care:state:session-1")).thenReturn(toCachedMap(expectedState));

        Optional<CareConversationState> loaded = service.load("session-1");

        assertTrue(loaded.isPresent());
        CareConversationState actual = loaded.orElseThrow();
        assertEquals(expectedState.sessionId(), actual.sessionId());
        assertEquals(expectedState.taskType(), actual.taskType());
        assertEquals(expectedState.requestedCount(), actual.requestedCount());
        assertEquals(expectedState.activityIntent(), actual.activityIntent());
        assertEquals(expectedState.preferenceSummary(), actual.preferenceSummary());
        assertEquals(expectedState.searchConditions(), actual.searchConditions());
        assertEquals(expectedState.nextPageNo(), actual.nextPageNo());
        assertEquals(expectedState.deliveredIds(), actual.deliveredIds());
        assertEquals(expectedState.updatedAt(), actual.updatedAt());
    }

    @Test
    void shouldReturnEmptyWhenCachedObjectCannotConvertToConversationState() {
        LinkedHashMap<String, Object> invalidState = new LinkedHashMap<>();
        invalidState.put("sessionId", "session-1");
        invalidState.put("taskType", "RECOMMEND_USERS");
        invalidState.put("updatedAt", "not-a-date");
        when(valueOperations.get("care:state:session-1")).thenReturn(invalidState);

        Optional<CareConversationState> loaded = service.load("session-1");

        assertFalse(loaded.isPresent());
    }

    @Test
    void shouldReuseConvertedStateForContinuationQuery() {
        when(valueOperations.get("care:state:session-1")).thenReturn(toCachedMap(createState()));
        Optional<CareConversationState> loaded = service.load("session-1");

        ChatModel chatModel = mock(ChatModel.class);
        CareBusinessDataProvider dataProvider = mock(CareBusinessDataProvider.class);
        CareAgentProperties properties = new CareAgentProperties();
        properties.setDefaultCount(10);
        properties.setMaxRequestedCount(500);
        AiObservationSupport support = new AiObservationSupport(new AiObservabilityProperties(), "pms-agent");
        CareQueryUnderstandingService understandingService =
                new CareQueryUnderstandingService(
                        chatModel,
                        properties,
                        dataProvider,
                        new AiModelInvoker(new AiObservationLogger(support), support),
                        support
                );

        CareQuery query = understandingService.understand("trace-1", "session-1", "再来一些", loaded.orElse(null));

        assertTrue(loaded.isPresent());
        assertTrue(query.continuation());
        assertEquals(CareTaskType.RECOMMEND_USERS, query.taskType());
        assertEquals("健步走活动", query.activityIntent());
        assertEquals(Map.of("regionName", "浦东新区"), query.searchConditions());
        verify(chatModel, never()).call(org.mockito.ArgumentMatchers.anyString());
    }

    private CareConversationState createState() {
        return new CareConversationState(
                "session-1",
                CareTaskType.RECOMMEND_USERS,
                25,
                "健步走活动",
                "推荐更多适合健步走的用户",
                Map.of("regionName", "浦东新区"),
                2,
                Set.of("u-1", "u-2"),
                LocalDateTime.of(2026, 4, 8, 14, 38, 5)
        );
    }

    private Map<String, Object> toCachedMap(CareConversationState state) {
        LinkedHashMap<String, Object> cached = new LinkedHashMap<>();
        cached.put("sessionId", state.sessionId());
        cached.put("taskType", state.taskType().name());
        cached.put("requestedCount", state.requestedCount());
        cached.put("activityIntent", state.activityIntent());
        cached.put("preferenceSummary", state.preferenceSummary());
        cached.put("searchConditions", new LinkedHashMap<>(state.searchConditions()));
        cached.put("nextPageNo", state.nextPageNo());
        cached.put("deliveredIds", List.copyOf(state.deliveredIds()));
        cached.put("updatedAt", state.updatedAt().toString());
        return cached;
    }
}
