package com.yumu.noveltranslator.domain.service;
import com.yumu.noveltranslator.port.dto.translation.SelectionTranslationRequest;
import com.yumu.noveltranslator.adapter.out.translate.UserLevelThrottledTranslationClient;
import com.yumu.noveltranslator.domain.service.QuotaService;
import com.yumu.noveltranslator.adapter.out.translate.TeamTranslationService;
import com.yumu.noveltranslator.domain.service.TranslationPostProcessingService;
import com.yumu.noveltranslator.port.out.TranslationCachePort;
import com.yumu.noveltranslator.port.dto.translation.ReaderTranslateRequest;
import com.yumu.noveltranslator.port.dto.translation.WebpageTranslateRequest;
import com.yumu.noveltranslator.application.service.RagTranslationApplicationService;
import com.yumu.noveltranslator.application.service.TranslationApplicationService;
import com.yumu.noveltranslator.domain.service.EntityConsistencyService;
import com.yumu.noveltranslator.port.dto.translation.RagTranslationResponse;

import com.yumu.noveltranslator.port.dto.common.*;
import com.yumu.noveltranslator.port.dto.collab.*;
import com.yumu.noveltranslator.port.dto.entity.*;
import com.yumu.noveltranslator.port.dto.translation.*;
import com.yumu.noveltranslator.port.dto.subscription.*;
import com.yumu.noveltranslator.port.dto.auth.*;
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 org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
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)
@DisplayName("TranslationService 流式翻译测试")
class TranslationServiceStreamTest {

    @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 service;

    @BeforeEach
    void setUp() {
        service = new TranslationApplicationService(
                translationClient, cachePort, ragTranslationService,
                entityConsistencyService, postProcessingService, teamTranslationService, quotaService);
        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()));
    }

    // ============ streamTextTranslate 测试 ============

    @Nested
    @DisplayName("streamTextTranslate - 文本流式翻译")
    class StreamTextTranslateTests {

        @Test
        void 空文本立即返回错误() {
            SelectionTranslationRequest req = new SelectionTranslationRequest();
            req.setText("");
            service.streamTextTranslate(null, req, (event) -> {});
        }

        @Test
        void null文本立即返回错误() {
            SelectionTranslationRequest req = new SelectionTranslationRequest();
            req.setText(null);
            service.streamTextTranslate(null, req, (event) -> {});
        }

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

            SelectionTranslationRequest req = new SelectionTranslationRequest();
            req.setText("Hello World");
            req.setEngine("google");
            req.setTargetLang("zh");
            service.streamTextTranslate(null, req, (event) -> {});
        }

        @Test
        void 无认证用户正常翻译() {
            SecurityContextHolder.clearContext();
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.empty());
            when(translationClient.translate(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean(), anyList(), any(), any()))
                    .thenReturn("{\"code\":200,\"data\":\"翻译结果\"}");

            SelectionTranslationRequest req = new SelectionTranslationRequest();
            req.setText("Hello");
            req.setEngine("google");
            req.setTargetLang("zh");
            service.streamTextTranslate(null, req, (event) -> {});
        }
    }

    // ============ webpageTranslateStream 测试 ============

    @Nested
    @DisplayName("webpageTranslateStream - 网页流式翻译")
    class WebpageTranslateStreamTests {

        @Test
        void 返回SseEmitter() {
            WebpageTranslateRequest req = new WebpageTranslateRequest();
            req.setTextRegistry(List.of());
            req.setTargetLang("zh");
            service.webpageTranslateStream(null, req, (event) -> {});
        }

        @Test
        void 空items正常返回done() {
            WebpageTranslateRequest req = new WebpageTranslateRequest();
            req.setTextRegistry(List.of());
            req.setTargetLang("zh");
            req.setEngine("google");
            service.webpageTranslateStream(null, req, (event) -> {});
        }

        @Test
        void 配额不足直接发送错误() {
            setAuthenticatedUser(1L);
            when(quotaService.tryConsumeChars(anyLong(), anyString(), anyLong(), anyString())).thenReturn(false);

            WebpageTranslateRequest req = new WebpageTranslateRequest();
            req.setTextRegistry(List.of(createTextItem("1", "Hello")));
            service.webpageTranslateStream(null, req, (event) -> {});
        }

        @Test
        void 单条文本翻译成功() throws InterruptedException {
            setAuthenticatedUser(1L);
            when(quotaService.tryConsumeChars(anyLong(), anyString(), anyLong(), anyString())).thenReturn(true);
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.empty());
            when(translationClient.translate(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean(), anyList(), any(), any()))
                    .thenReturn("{\"code\":200,\"data\":\"你好\"}");

            WebpageTranslateRequest req = new WebpageTranslateRequest();
            req.setTextRegistry(List.of(createTextItem("1", "Hello World")));
            req.setTargetLang("zh");
            req.setEngine("google");
            service.webpageTranslateStream(null, req, (event) -> {});

            // Allow virtual threads to complete
            Thread.sleep(2000);
        }

        @Test
        void 翻译失败使用原文兜底() {
            WebpageTranslateRequest req = new WebpageTranslateRequest();
            List<WebpageTranslateRequest.TextItem> items = new ArrayList<>();
            items.add(createTextItem("1", "Hello"));
            req.setTextRegistry(items);
            req.setTargetLang("zh");

            // No auth user, no quota check
            SecurityContextHolder.clearContext();
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.empty());
            when(translationClient.translate(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean(), anyList(), any(), any()))
                    .thenReturn(null); // simulate failure

            service.webpageTranslateStream(null, req, (event) -> {});
        }

        @Test
        void fastModeFalse使用专家模式() {
            WebpageTranslateRequest req = new WebpageTranslateRequest();
            req.setTextRegistry(List.of(createTextItem("1", "Test")));
            req.setTargetLang("zh");
            req.setFastMode(false);
            req.setEngine("google");

            SecurityContextHolder.clearContext();
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.empty());
            when(translationClient.translate(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean(), anyList(), any(), any()))
                    .thenReturn("{\"code\":200,\"data\":\"Expert translation\"}");

            service.webpageTranslateStream(null, req, (event) -> {});
        }

        private WebpageTranslateRequest.TextItem createTextItem(String id, String original) {
            WebpageTranslateRequest.TextItem item = new WebpageTranslateRequest.TextItem();
            item.setId(id);
            item.setOriginal(original);
            return item;
        }
    }

    // ============ 配额与模式测试 ============

    @Nested
    @DisplayName("配额模式 - fast/expert")
    class QuotaModeTests {

        @Test
        void fastMode使用fast配额() {
            setAuthenticatedUser(1L);
            when(quotaService.tryConsumeChars(anyLong(), anyString(), anyLong(), anyString())).thenReturn(true);
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.of("cached"));

            // fastMode = true -> mode = "fast"
            WebpageTranslateRequest req = new WebpageTranslateRequest();
            req.setTextRegistry(List.of(createTextItem("1", "Hello")));
            req.setFastMode(true);
            req.setTargetLang("zh");
            service.webpageTranslateStream(1L, req, (event) -> {});

            verify(quotaService).tryConsumeChars(eq(1L), eq("free"), anyLong(), eq("fast"));
        }

        @Test
        void expertMode使用expert配额() {
            setAuthenticatedUser(1L);
            when(quotaService.tryConsumeChars(anyLong(), anyString(), anyLong(), anyString())).thenReturn(true);
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.of("cached"));

            WebpageTranslateRequest req = new WebpageTranslateRequest();
            req.setTextRegistry(List.of(createTextItem("1", "Hello")));
            req.setFastMode(false);
            req.setTargetLang("zh");
            service.webpageTranslateStream(1L, req, (event) -> {});

            verify(quotaService).tryConsumeChars(eq(1L), eq("free"), anyLong(), eq("expert"));
        }

        private WebpageTranslateRequest.TextItem createTextItem(String id, String original) {
            WebpageTranslateRequest.TextItem item = new WebpageTranslateRequest.TextItem();
            item.setId(id);
            item.setOriginal(original);
            return item;
        }
    }

    // ============ selectionTranslate mode参数测试 ============

    @Nested
    @DisplayName("selectionTranslate - mode参数")
    class SelectionModeTests {

        @Test
        void 默认fast模式() {
            setAuthenticatedUser(1L);
            when(quotaService.tryConsumeChars(anyLong(), anyString(), anyLong(), anyString())).thenReturn(true);
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.empty());
            when(ragTranslationService.searchSimilarWithModes(anyLong(), anyString(), anyString(), anyList()))
                    .thenReturn(new RagTranslationResponse());
            when(entityConsistencyService.shouldUseConsistency(anyString())).thenReturn(false);
            when(translationClient.translate(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean(), anyList(), any(), any()))
                    .thenReturn("{\"code\":200,\"data\":\"结果\"}");

            SelectionTranslationRequest req = new SelectionTranslationRequest();
            req.setText("Hello");
            req.setTargetLang("zh");
            // No mode set, should default to "fast"
            var resp = service.selectionTranslate(1L, req);
            assertTrue(resp.getSuccess());

            verify(quotaService).tryConsumeChars(eq(1L), eq("free"), anyLong(), eq("fast"));
        }

        @Test
        void expert模式() {
            setAuthenticatedUser(1L);
            when(quotaService.tryConsumeChars(anyLong(), anyString(), anyLong(), anyString())).thenReturn(true);
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.empty());
            when(ragTranslationService.searchSimilarWithModes(anyLong(), anyString(), anyString(), anyList()))
                    .thenReturn(new RagTranslationResponse());
            when(entityConsistencyService.shouldUseConsistency(anyString())).thenReturn(false);
            // mode=expert uses pipeline.execute() which calls 8-param translate
            when(translationClient.translate(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean(), anyList(), any(), any()))
                    .thenReturn("{\"code\":200,\"data\":\"结果\"}");

            SelectionTranslationRequest req = new SelectionTranslationRequest();
            req.setText("Hello");
            req.setTargetLang("zh");
            req.setMode("expert");
            var resp = service.selectionTranslate(1L, req);
            assertTrue(resp.getSuccess());

            verify(quotaService).tryConsumeChars(eq(1L), eq("free"), anyLong(), eq("expert"));
        }

        @Test
        void 翻译返回空结果() {
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.empty());
            when(ragTranslationService.searchSimilarWithModes(anyLong(), anyString(), anyString(), anyList()))
                    .thenReturn(new RagTranslationResponse());
            when(entityConsistencyService.shouldUseConsistency(anyString())).thenReturn(false);
            when(translationClient.translate(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean(), anyList(), any(), any()))
                    .thenReturn("");

            SelectionTranslationRequest req = new SelectionTranslationRequest();
            req.setText("Hello");
            req.setTargetLang("zh");
            var resp = service.selectionTranslate(null, req);

            // executeFast returns original text on empty result, which is non-empty -> success=true
            assertTrue(resp.getSuccess());
            assertEquals("Hello", resp.getTranslation());
        }

        @Test
        void 翻译客户端抛异常() {
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.empty());
            when(ragTranslationService.searchSimilarWithModes(anyLong(), anyString(), anyString(), anyList()))
                    .thenReturn(new RagTranslationResponse());
            when(entityConsistencyService.shouldUseConsistency(anyString())).thenReturn(false);
            when(translationClient.translate(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean(), anyList(), any(), any()))
                    .thenThrow(new RuntimeException("connection timeout"));

            SelectionTranslationRequest req = new SelectionTranslationRequest();
            req.setText("Hello");
            req.setTargetLang("zh");
            var resp = service.selectionTranslate(null, req);

            // executeFast catches exception and returns original text -> success=true
            assertTrue(resp.getSuccess());
            assertEquals("Hello", resp.getTranslation());
        }
    }

    // ============ readerTranslate 补充测试 ============

    @Nested
    @DisplayName("readerTranslate - 补充测试")
    class ReaderTranslateExtendedTests {

        @Test
        void 缓存全部命中不调用翻译客户端() {
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.of("cached translation"));

            ReaderTranslateRequest req = new ReaderTranslateRequest();
            req.setContent("<p>test</p>");
            req.setTargetLang("zh");
            req.setEngine("google");
            var resp = service.readerTranslate(null, req);

            assertTrue(resp.getSuccess());
            assertEquals("cached translation", resp.getTranslatedContent());
            verify(translationClient, never()).translate(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean(), anyList(), any(), any());
        }

        @Test
        void 翻译失败时使用原文兜底() {
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.empty());
            when(translationClient.translate(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean(), anyList(), any(), any()))
                    .thenThrow(new RuntimeException("engine failed"));

            ReaderTranslateRequest req = new ReaderTranslateRequest();
            req.setContent("<p>Hello</p>");
            req.setTargetLang("zh");
            req.setEngine("google");
            var resp = service.readerTranslate(null, req);

            assertTrue(resp.getSuccess());
            // Should fall back to original text
            assertNotNull(resp.getTranslatedContent());
        }

        @Test
        void 阅读器模式html为true() {
            when(cachePort.getCacheByMode(anyString(), anyString())).thenReturn(Optional.empty());
            when(translationClient.translate(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean(), anyList(), any(), any()))
                    .thenReturn("{\"code\":200,\"data\":\"翻译结果\"}");

            ReaderTranslateRequest req = new ReaderTranslateRequest();
            req.setContent("<p>test</p>");
            req.setTargetLang("zh");
            var resp = service.readerTranslate(null, req);

            assertTrue(resp.getSuccess());
            // reader mode uses html=true for MTranServer
            verify(translationClient).translate(anyString(), anyString(), anyString(), eq(true), eq(true), anyList(), any(), any());
        }
    }
}
