package com.yumu.noveltranslator.adapter.in.rest.web;
import com.yumu.noveltranslator.port.dto.entity.DocumentInfoResponse;
import com.yumu.noveltranslator.domain.model.User;

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.Document;
import com.yumu.noveltranslator.domain.model.TranslationTask;
import com.yumu.noveltranslator.adapter.out.security.CustomUserDetails;
import com.yumu.noveltranslator.port.in.DocumentPort;
import com.yumu.noveltranslator.port.in.CollabPort;
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.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockMultipartFile;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@ExtendWith(MockitoExtension.class)
class WebDocumentControllerTest {

    private MockMvc mockMvc;

    @org.mockito.Mock
    private DocumentPort documentPort;

    @org.mockito.Mock
    private CollabPort collabPort;

    private WebDocumentController controller;

    @BeforeEach
    void setUp() {
        controller = new WebDocumentController(documentPort, collabPort);
        mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
    }

    private void setupSecurityContext() {
        User user = new User();
        user.setId(1L);
        user.setEmail("test@test.com");
        user.setUserLevel("free");
        CustomUserDetails userDetails = new CustomUserDetails(user);
        UsernamePasswordAuthenticationToken auth =
            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(auth);
    }

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

    private Document createTestDocument() {
        Document doc = new Document();
        doc.setId(1L);
        doc.setUserId(1L);
        doc.setName("test.txt");
        doc.setPath("/uploads/test.txt");
        doc.setFileType("txt");
        doc.setSourceLang("en");
        doc.setTargetLang("zh");
        doc.setStatus("pending");
        return doc;
    }

    @Nested
    @DisplayName("获取文档列表")
    class GetDocumentsTests {

        @Test
        void 获取文档列表成功() throws Exception {
            setupSecurityContext();
            Document doc = createTestDocument();
            when(documentPort.getUserDocuments(eq(1L), eq("all"))).thenReturn(List.of(doc));

            DocumentInfoResponse infoResp = new DocumentInfoResponse();
            infoResp.setId(1L);
            infoResp.setName("test.txt");
            when(documentPort.toDocumentInfoResponse(any())).thenReturn(infoResp);

            mockMvc.perform(get("/user/documents"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.data.total").value(1));
        }

        @Test
        void 带分页和状态参数() throws Exception {
            setupSecurityContext();
            when(documentPort.getUserDocuments(eq(1L), eq("completed"))).thenReturn(List.of());

            mockMvc.perform(get("/user/documents")
                    .param("page", "1")
                    .param("pageSize", "10")
                    .param("status", "completed"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true));
        }
    }

    @Nested
    @DisplayName("获取文档详情")
    class GetDocumentTests {

        @Test
        void 获取文档详情成功() throws Exception {
            setupSecurityContext();
            Document doc = createTestDocument();
            when(documentPort.getDocumentById(1L, 1L)).thenReturn(doc);

            DocumentInfoResponse infoResp = new DocumentInfoResponse();
            infoResp.setId(1L);
            when(documentPort.toDocumentInfoResponse(any())).thenReturn(infoResp);

            mockMvc.perform(get("/user/documents/1"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.data.id").value(1));
        }

        @Test
        void 文档不存在返回错误() throws Exception {
            setupSecurityContext();
            when(documentPort.getDocumentById(999L, 1L)).thenReturn(null);

            mockMvc.perform(get("/user/documents/999"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(false))
                .andExpect(jsonPath("$.message").value("文档不存在"));
        }
    }

    @Nested
    @DisplayName("删除文档")
    class DeleteDocumentTests {

        @Test
        void 删除文档成功() throws Exception {
            setupSecurityContext();
            when(documentPort.deleteDocument(1L, 1L)).thenReturn(true);

            mockMvc.perform(delete("/user/documents/1"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true));
        }

        @Test
        void 删除失败返回错误() throws Exception {
            setupSecurityContext();
            when(documentPort.deleteDocument(1L, 1L)).thenReturn(false);

            mockMvc.perform(delete("/user/documents/1"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(false))
                .andExpect(jsonPath("$.message").value("删除失败"));
        }
    }

    @Nested
    @DisplayName("取消翻译")
    class CancelTranslationTests {

        @Test
        void 取消翻译成功() throws Exception {
            setupSecurityContext();
            when(documentPort.cancelTranslation(1L, 1L)).thenReturn(true);

            mockMvc.perform(post("/user/documents/1/cancel"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true));
        }

        @Test
        void 取消失败返回错误() throws Exception {
            setupSecurityContext();
            when(documentPort.cancelTranslation(1L, 1L)).thenReturn(false);

            mockMvc.perform(post("/user/documents/1/cancel"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(false))
                .andExpect(jsonPath("$.message").value("取消失败"));
        }
    }

    @Nested
    @DisplayName("重新翻译")
    class RetryTranslateTests {

        @Test
        void 重新翻译成功() throws Exception {
            setupSecurityContext();
            when(documentPort.retryTranslation(1L, 1L)).thenReturn(true);

            mockMvc.perform(post("/user/documents/1/retry"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true));
        }

        @Test
        void 重试失败返回错误() throws Exception {
            setupSecurityContext();
            when(documentPort.retryTranslation(1L, 1L)).thenReturn(false);

            mockMvc.perform(post("/user/documents/1/retry"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(false))
                .andExpect(jsonPath("$.message").value("重试失败，文档不存在"));
        }
    }

    @Nested
    @DisplayName("上传文档")
    class UploadDocumentTests {

        @Test
        void 上传文档成功_fast模式() throws Exception {
            setupSecurityContext();
            Document doc = createTestDocument();
            when(documentPort.uploadAndStartTranslation(eq(1L), any(), any(), any())).thenAnswer(inv -> {
                DocumentTranslationResponse resp = new DocumentTranslationResponse();
                resp.setDocumentId(1L);
                resp.setTaskId("task-001");
                resp.setStatus("pending");
                return resp;
            });

            MockMultipartFile file = new MockMultipartFile(
                "file", "test.txt", "text/plain", "Hello World".getBytes());

            mockMvc.perform(multipart("/user/documents/upload")
                    .file(file)
                    .param("sourceLang", "en")
                    .param("targetLang", "zh")
                    .param("mode", "fast"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.data.documentId").value(1));
        }

        @Test
        void 上传文档_team模式() throws Exception {
            setupSecurityContext();
            Document doc = createTestDocument();
            when(documentPort.uploadDocument(eq(1L), any(), any(), any())).thenReturn(doc);

            CollabPort.TeamProjectCreateResult projectResult =
                new CollabPort.TeamProjectCreateResult(1L, "test.txt", 3);
            when(collabPort.createProjectFromDocument(anyLong(), anyLong(), anyString(), anyString(), anyString(), anyString(), anyString()))
                .thenReturn(projectResult);
            doNothing().when(collabPort).startMultiAgentTranslation(1L);

            MockMultipartFile file = new MockMultipartFile(
                "file", "test.txt", "text/plain", "Hello World".getBytes());

            mockMvc.perform(multipart("/user/documents/upload")
                    .file(file)
                    .param("sourceLang", "en")
                    .param("targetLang", "zh")
                    .param("mode", "team"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.data.projectId").value(1))
                .andExpect(jsonPath("$.data.message").value("团队模式已创建项目，共 3 个章节"));
        }
    }

    @Nested
    @DisplayName("下载文档")
    class DownloadDocumentTests {

        @Test
        void 文档不存在返回404() throws Exception {
            setupSecurityContext();
            when(documentPort.getDocumentById(1L, 1L)).thenReturn(null);

            mockMvc.perform(get("/user/documents/1/download"))
                .andExpect(status().isNotFound());
        }

        @Test
        @DisplayName("下载成功 - 用户拥有文档且文件存在")
        void 下载文档成功() throws Exception {
            setupSecurityContext();

            Document doc = createTestDocument();
            doc.setStatus("completed");
            when(documentPort.getDocumentById(1L, 1L)).thenReturn(doc);

            Path fakePath = Path.of("/uploads/test_translated.txt");
            byte[] fileContent = "translated content".getBytes();

            try (var mockedFiles = mockStatic(Files.class)) {
                mockedFiles.when(() -> Files.exists(any(Path.class)))
                    .thenReturn(true);
                mockedFiles.when(() -> Files.readAllBytes(fakePath))
                    .thenReturn(fileContent);

                mockMvc.perform(get("/user/documents/1/download"))
                    .andExpect(status().isOk())
                    .andExpect(header().string("Content-Type", "text/plain"))
                    .andExpect(content().bytes(fileContent));
            }
        }

        @Test
        @DisplayName("文件不存在 - 文档存在但磁盘文件缺失")
        void 下载文档文件不存在() throws Exception {
            setupSecurityContext();

            Document doc = createTestDocument();
            doc.setStatus("completed");
            when(documentPort.getDocumentById(1L, 1L)).thenReturn(doc);

            try (var mockedFiles = mockStatic(Files.class)) {
                // Document exists in DB, but Files.exists returns false for the path
                mockedFiles.when(() -> Files.exists(any(Path.class)))
                    .thenReturn(false);

                mockMvc.perform(get("/user/documents/1/download"))
                    .andExpect(status().isNotFound());
            }
        }

        @Test
        @DisplayName("无权下载 - 用户不拥有文档时服务层返回null")
        void 下载文档无权操作() throws Exception {
            setupSecurityContext();

            // User 1 tries to download, but the document belongs to another user
            // Service returns null because getDocumentById filters by userId
            when(documentPort.getDocumentById(1L, 1L)).thenReturn(null);

            mockMvc.perform(get("/user/documents/1/download"))
                .andExpect(status().isNotFound());
        }

        @Test
        @DisplayName("读取文件时发生IOException返回500")
        void 下载文档发生IO异常() throws Exception {
            setupSecurityContext();

            Document doc = createTestDocument();
            when(documentPort.getDocumentById(1L, 1L)).thenReturn(doc);

            try (var mockedFiles = mockStatic(Files.class)) {
                mockedFiles.when(() -> Files.exists(any(Path.class)))
                    .thenReturn(true);
                mockedFiles.when(() -> Files.readAllBytes(any(Path.class)))
                    .thenThrow(new IOException("disk read error"));

                mockMvc.perform(get("/user/documents/1/download"))
                    .andExpect(status().isInternalServerError());
            }
        }
    }
}
