package com.yumu.noveltranslator.domain.service;
import com.yumu.noveltranslator.port.dto.translation.SelectionTranslationRequest;
import com.yumu.noveltranslator.port.dto.translation.ReaderTranslateResponse;
import com.yumu.noveltranslator.port.dto.translation.ReaderTranslateRequest;
import com.yumu.noveltranslator.port.dto.translation.SelectionTranslateResponse;
import com.yumu.noveltranslator.application.service.RagTranslationApplicationService;
import com.yumu.noveltranslator.application.service.TranslationApplicationService;
import com.yumu.noveltranslator.port.dto.translation.RagTranslationResponse;

import com.yumu.noveltranslator.port.out.TranslationCachePort;
import com.yumu.noveltranslator.adapter.out.translate.TeamTranslationService;
import com.yumu.noveltranslator.adapter.out.translate.UserLevelThrottledTranslationClient;
import com.yumu.noveltranslator.domain.model.User;
import com.yumu.noveltranslator.adapter.out.persistence.mapper.UserMapper;
import com.yumu.noveltranslator.adapter.out.security.CustomUserDetails;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
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.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;

import java.util.Map;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class TranslationServiceTest {

    @Mock
    private UserLevelThrottledTranslationClient translationClient;

    @Mock
    private TranslationCachePort cachePort;

    @Mock
    private RagTranslationApplicationService ragTranslationService;

    @Mock
    private EntityConsistencyService entityConsistencyService;

    @Mock
    private TranslationPostProcessingService postProcessingService;

    @Mock
    private TeamTranslationService teamTranslationService;

    @Mock
    private QuotaService quotaService;

    @Mock
    private UserMapper userMapper;

    private TranslationApplicationService translationService;

    @BeforeEach
    void setUp() {
        translationService = new TranslationApplicationService(
                translationClient, cachePort, ragTranslationService,
                entityConsistencyService, postProcessingService, teamTranslationService, quotaService);
        // Pipeline 内部调用 fixUntranslatedChinese，mock 返回原文
        when(postProcessingService.fixUntranslatedChinese(anyString(), anyString(), anyString(), anyString()))
                .thenAnswer(invocation -> invocation.getArgument(1));
        SecurityContextHolder.clearContext();
    }

    @AfterEach
    void tearDown() {
        SecurityContextHolder.clearContext();
    }

    /**
     * 设置认证用户上下文
     */
    private void setAuthenticatedUser(Long userId) {
        User user = new User();
        user.setId(userId);
        user.setUserLevel("free");
        CustomUserDetails userDetails = new CustomUserDetails(user);
        SecurityContextHolder.getContext().setAuthentication(
                new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()));
    }

    @Nested
    @DisplayName("选中文本翻译")
    class SelectionTranslateTests {

        @Test
        void 空文本返回错误() {
            SelectionTranslationRequest req = new SelectionTranslationRequest();
            SelectionTranslateResponse resp = translationService.selectionTranslate(null, req);

            assertFalse(resp.getSuccess());
        }

        @Test
        void null文本返回错误() {
            SelectionTranslationRequest req = new SelectionTranslationRequest();
            SelectionTranslateResponse resp = translationService.selectionTranslate(null, req);

            assertFalse(resp.getSuccess());
        }

        @Test
        void 缓存命中直接返回() {
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.of("缓存结果"));

            SelectionTranslationRequest req = new SelectionTranslationRequest();
            req.setText("测试文本");
            req.setSourceLang("en");
            req.setTargetLang("zh");
            SelectionTranslateResponse resp = translationService.selectionTranslate(null, req);

            assertTrue(resp.getSuccess());
            assertEquals("缓存结果", resp.getTranslation());
            verify(translationClient, never()).translate(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean());
        }

        @Test
        void 缓存未命中调用翻译客户端() {
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.empty());
            RagTranslationResponse ragResp = new RagTranslationResponse();
            when(ragTranslationService.searchSimilarWithModes(anyLong(), anyString(), anyString(), anyList()))
                    .thenReturn(ragResp);
            when(translationClient.translate(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean(), anyList(), isNull(), isNull()))
                    .thenReturn("{\"code\":200,\"data\":\"翻译结果\"}");

            SelectionTranslationRequest req = new SelectionTranslationRequest();
            req.setText("Hello World");
            req.setSourceLang("en");
            req.setTargetLang("zh");
            req.setEngine("google");
            SelectionTranslateResponse resp = translationService.selectionTranslate(null, req);

            assertTrue(resp.getSuccess());
            assertEquals("翻译结果", resp.getTranslation());
            verify(translationClient).translate(eq("Hello World"), eq("zh"), eq("fast"), eq(false), eq(true), anyList(), isNull(), isNull());
        }

        @Test
        void 翻译失败返回错误() {
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.empty());
            RagTranslationResponse ragResp = new RagTranslationResponse();
            when(ragTranslationService.searchSimilarWithModes(anyLong(), anyString(), anyString(), anyList()))
                    .thenReturn(ragResp);
            when(translationClient.translate(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean(), anyList(), isNull(), isNull()))
                    .thenReturn("{\"code\":500,\"error\":\"引擎不可用\"}");

            SelectionTranslationRequest req = new SelectionTranslationRequest();
            req.setText("Hello World");
            SelectionTranslateResponse resp = translationService.selectionTranslate(null, req);

            // executeFast 失败时返回原文，success=true（原文兜底）
            assertTrue(resp.getSuccess());
            assertEquals("Hello World", resp.getTranslation());
        }

        @Test
        void 默认语言和引擎() {
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.empty());
            RagTranslationResponse ragResp = new RagTranslationResponse();
            when(ragTranslationService.searchSimilarWithModes(anyLong(), anyString(), anyString(), anyList()))
                    .thenReturn(ragResp);
            when(translationClient.translate(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean(), anyList(), isNull(), isNull()))
                    .thenReturn("{\"code\":200,\"data\":\"result\"}");

            SelectionTranslationRequest req = new SelectionTranslationRequest();
            req.setText("test");
            SelectionTranslateResponse resp = translationService.selectionTranslate(null, req);

            assertTrue(resp.getSuccess());
            verify(translationClient).translate(eq("test"), eq("zh"), eq("fast"), eq(false), eq(true), anyList(), isNull(), isNull());
        }
    }

    @Nested
    @DisplayName("阅读器翻译")
    class ReaderTranslateTests {

        @Test
        void 空内容返回错误() {
            ReaderTranslateRequest req = new ReaderTranslateRequest();
            req.setContent("");
            ReaderTranslateResponse resp = translationService.readerTranslate(null, req);

            assertFalse(resp.getSuccess());
            assertEquals("没有收到内容", resp.getTranslatedContent());
        }

        @Test
        void null内容返回错误() {
            ReaderTranslateRequest req = new ReaderTranslateRequest();
            req.setContent(null);
            ReaderTranslateResponse resp = translationService.readerTranslate(null, req);

            assertFalse(resp.getSuccess());
        }

        @Test
        void 单段落翻译() {
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.empty());
            when(translationClient.translate(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean(), anyList(), isNull(), isNull()))
                    .thenReturn("{\"code\":200,\"data\":\"翻译段落\"}");

            ReaderTranslateRequest req = new ReaderTranslateRequest();
            req.setContent("<p>Test paragraph</p>");
            req.setTargetLang("zh");
            req.setEngine("google");
            ReaderTranslateResponse resp = translationService.readerTranslate(null, req);

            assertTrue(resp.getSuccess());
            assertNotNull(resp.getTranslatedContent());
        }

        @Test
        void 多段落并行翻译() {
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.empty());
            when(translationClient.translate(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean(), anyList(), isNull(), isNull()))
                    .thenAnswer(invocation -> {
                        String text = invocation.getArgument(0);
                        return "{\"code\":200,\"data\":\"[translated]\" + text}";
                    });

            ReaderTranslateRequest req = new ReaderTranslateRequest();
            req.setContent("<p>段落1</p><p>段落2</p><p>段落3</p>");
            req.setTargetLang("zh");
            ReaderTranslateResponse resp = translationService.readerTranslate(null, req);

            assertTrue(resp.getSuccess());
            assertNotNull(resp.getTranslatedContent());
        }

        @Test
        void 长文本分段翻译() {
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.empty());
            when(translationClient.translate(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean(), anyList(), isNull(), isNull()))
                    .thenAnswer(invocation -> {
                        String text = invocation.getArgument(0);
                        return "{\"code\":200,\"data\":\"[T:\" + text.length() + \"]\"}";
                    });

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 10; i++) {
                sb.append("This is sentence number ").append(i).append(". ");
            }

            ReaderTranslateRequest req = new ReaderTranslateRequest();
            req.setContent(sb.toString());
            req.setSourceLang("en");
            req.setTargetLang("zh");
            ReaderTranslateResponse resp = translationService.readerTranslate(null, req);

            assertTrue(resp.getSuccess());
            assertNotNull(resp.getTranslatedContent());
        }
    }

    @Nested
    @DisplayName("缓存统计")
    class CacheStatsTests {

        @Test
        void 获取缓存统计() {
            @SuppressWarnings("unchecked")
            var expected = (Map<String, Object>) (Map<?, ?>) Map.of("l1Hits", 5L, "misses", 2L);
            when(cachePort.getCacheStats()).thenReturn(expected);

            var stats = translationService.getCacheStats();
            assertEquals(expected, stats);
        }
    }

    @Nested
    @DisplayName("配额检查")
    class QuotaCheckTests {

        @Test
        void 字符配额不足返回错误() {
            setAuthenticatedUser(1L);
            when(quotaService.tryConsumeChars(anyLong(), anyString(), anyLong(), anyString())).thenReturn(false);

            SelectionTranslationRequest req = new SelectionTranslationRequest();
            req.setText("Hello World");
            req.setSourceLang("en");
            req.setTargetLang("zh");
            // Pass userId=1L so quota check is actually executed
            SelectionTranslateResponse resp = translationService.selectionTranslate(1L, req);

            assertFalse(resp.getSuccess());
            assertTrue(resp.getTranslation().contains("字符配额不足"));
        }

        @Test
        void 用户不存在跳过配额检查() {
            setAuthenticatedUser(999L);
            when(cachePort.getCache(anyString())).thenReturn(Optional.of("cached"));

            SelectionTranslationRequest req = new SelectionTranslationRequest();
            req.setText("Hello");
            req.setText("Hello");
            req.setSourceLang("en");
            req.setTargetLang("zh");
            SelectionTranslateResponse resp = translationService.selectionTranslate(null, req);

            assertTrue(resp.getSuccess());
        }

        @Test
        void 阅读器翻译配额不足() {
            setAuthenticatedUser(1L);
            when(quotaService.tryConsumeChars(anyLong(), anyString(), anyLong(), anyString())).thenReturn(false);

            ReaderTranslateRequest req = new ReaderTranslateRequest();
            req.setContent("<p>Hello</p>");
            req.setTargetLang("zh");
            // Pass userId=1L so quota check is actually executed
            ReaderTranslateResponse resp = translationService.readerTranslate(1L, req);

            assertFalse(resp.getSuccess());
            assertTrue(resp.getTranslatedContent().contains("字符配额不足"));
        }

        @Test
        void 选中文本翻译异常兜底() {
            when(cachePort.getCacheByMode(anyString(), anyString())).thenThrow(new RuntimeException("cache error"));

            SelectionTranslationRequest req = new SelectionTranslationRequest();
            req.setText("Hello");
            req.setText("Hello");
            req.setSourceLang("en");
            req.setTargetLang("zh");
            SelectionTranslateResponse resp = translationService.selectionTranslate(null, req);

            assertFalse(resp.getSuccess());
            assertTrue(resp.getTranslation().contains("翻译失败"));
        }
    }
}
