package com.njcn.gather.icd.mapping.service.impl;

import com.njcn.gather.icd.mapping.mapper.CsIcdPathMapper;
import com.njcn.gather.icd.mapping.pojo.bo.icd.IcdDocument;
import com.njcn.gather.icd.mapping.pojo.param.CsIcdPathParam;
import com.njcn.gather.icd.mapping.pojo.param.IcdCheckResultSaveParam;
import com.njcn.gather.icd.mapping.pojo.po.CsIcdPathPO;
import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathDetailVO;
import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathVO;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

import java.util.Collections;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class CsIcdPathServiceImplTest {

    private final ObjectMapper objectMapper = new ObjectMapper();
    private final CsIcdPathMapper csIcdPathMapper = mock(CsIcdPathMapper.class);
    private final CsIcdPathServiceImpl service = new CsIcdPathServiceImpl(csIcdPathMapper);

    @Test
    void checkResultParamShouldIgnoreIcdContentFromJson() throws Exception {
        String requestJson = "{\"mappingJson\":\"{}\",\"result\":1,\"icdContent\":\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\"}";

        IcdCheckResultSaveParam param = objectMapper.readValue(requestJson, IcdCheckResultSaveParam.class);

        Assertions.assertEquals("{}", param.getMappingJson());
        Assertions.assertEquals(1, param.getResult());
        Assertions.assertNull(param.getIcdContent());
    }

    @Test
    void listIcdPathsShouldTrimKeywordBeforeQuery() {
        CsIcdPathParam.ListParam param = new CsIcdPathParam.ListParam();
        param.setKeyword("  standard  ");

        service.listIcdPaths(param);

        verify(csIcdPathMapper).selectIcdPathList(eq("standard"), eq(null), eq(null));
    }

    @Test
    void listReferenceIcdPathsShouldQueryStandardIcdTypes() {
        service.listReferenceIcdPaths();

        verify(csIcdPathMapper).selectReferenceIcdPathList();
    }

    @Test
    void getIcdCheckMsgShouldQueryMsgByTrimmedId() {
        JsonNode msg = objectMapper.createObjectNode().put("summary", "通过");
        CsIcdPathVO vo = new CsIcdPathVO();
        vo.setMsg(msg);
        when(csIcdPathMapper.selectIcdCheckMsgById(eq("icd-001"))).thenReturn(vo);

        JsonNode result = service.getIcdCheckMsg("  icd-001  ");

        Assertions.assertSame(msg, result);
        verify(csIcdPathMapper).selectIcdCheckMsgById(eq("icd-001"));
    }

    @Test
    void addIcdPathShouldInsertEnabledRecord() {
        CsIcdPathParam param = buildParam("标准ICD");
        when(csIcdPathMapper.insert(any(CsIcdPathPO.class))).thenReturn(1);

        boolean result = service.addIcdPath(param);

        ArgumentCaptor<CsIcdPathPO> captor = ArgumentCaptor.forClass(CsIcdPathPO.class);
        verify(csIcdPathMapper).insert(captor.capture());
        Assertions.assertTrue(result);
        Assertions.assertEquals("标准ICD", captor.getValue().getName());
        Assertions.assertEquals(1, captor.getValue().getState());
        Assertions.assertNotNull(captor.getValue().getId());
        Assertions.assertNotNull(captor.getValue().getCreateTime());
    }

    @Test
    void addIcdPathShouldSaveIcdBinaryContent() {
        CsIcdPathParam param = buildParam("标准ICD");
        byte[] fileContent = "<SCL></SCL>".getBytes();
        param.setIcdContent(fileContent);
        when(csIcdPathMapper.insert(any(CsIcdPathPO.class))).thenReturn(1);

        boolean result = service.addIcdPath(param);

        ArgumentCaptor<CsIcdPathPO> captor = ArgumentCaptor.forClass(CsIcdPathPO.class);
        verify(csIcdPathMapper).insert(captor.capture());
        Assertions.assertTrue(result);
        Assertions.assertArrayEquals(fileContent, captor.getValue().getIcdContent());
    }

    @Test
    void addIcdPathShouldSaveReferenceIcdId() {
        CsIcdPathParam param = buildParam("非标准ICD");
        param.setReferenceIcdId("  reference-icd  ");
        when(csIcdPathMapper.insert(any(CsIcdPathPO.class))).thenReturn(1);

        boolean result = service.addIcdPath(param);

        ArgumentCaptor<CsIcdPathPO> captor = ArgumentCaptor.forClass(CsIcdPathPO.class);
        verify(csIcdPathMapper).insert(captor.capture());
        Assertions.assertTrue(result);
        Assertions.assertEquals("reference-icd", captor.getValue().getReferenceIcdId());
    }

    @Test
    void addIcdPathShouldDefaultTypeToManualNonStandard() {
        CsIcdPathParam param = buildParam("手动录入非标准ICD");
        param.setType(null);
        when(csIcdPathMapper.insert(any(CsIcdPathPO.class))).thenReturn(1);

        boolean result = service.addIcdPath(param);

        ArgumentCaptor<CsIcdPathPO> captor = ArgumentCaptor.forClass(CsIcdPathPO.class);
        verify(csIcdPathMapper).insert(captor.capture());
        Assertions.assertTrue(result);
        Assertions.assertEquals(2, captor.getValue().getType());
    }

    @Test
    void addManualStandardIcdPathShouldDefaultReferenceIcdIdToSelf() {
        CsIcdPathParam param = buildParam("标准ICD");
        param.setType(1);
        param.setReferenceIcdId(null);
        when(csIcdPathMapper.insert(any(CsIcdPathPO.class))).thenReturn(1);

        boolean result = service.addIcdPath(param);

        ArgumentCaptor<CsIcdPathPO> captor = ArgumentCaptor.forClass(CsIcdPathPO.class);
        verify(csIcdPathMapper).insert(captor.capture());
        Assertions.assertTrue(result);
        Assertions.assertEquals(captor.getValue().getId(), captor.getValue().getReferenceIcdId());
    }

    @Test
    void addUpstreamStandardIcdPathShouldDefaultReferenceIcdIdToSelf() {
        CsIcdPathParam param = buildParam("上游标准ICD");
        param.setType(3);
        param.setReferenceIcdId(" ");
        when(csIcdPathMapper.insert(any(CsIcdPathPO.class))).thenReturn(1);

        boolean result = service.addIcdPath(param);

        ArgumentCaptor<CsIcdPathPO> captor = ArgumentCaptor.forClass(CsIcdPathPO.class);
        verify(csIcdPathMapper).insert(captor.capture());
        Assertions.assertTrue(result);
        Assertions.assertEquals(captor.getValue().getId(), captor.getValue().getReferenceIcdId());
    }

    @Test
    void updateIcdPathShouldRejectDeletedRecord() {
        CsIcdPathParam.UpdateParam param = new CsIcdPathParam.UpdateParam();
        param.setId("icd-001");
        param.setName("标准ICD");

        CsIcdPathPO deleted = new CsIcdPathPO();
        deleted.setId("icd-001");
        deleted.setState(0);
        when(csIcdPathMapper.selectById(eq("icd-001"))).thenReturn(deleted);

        IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class,
                () -> service.updateIcdPath(param));

        Assertions.assertEquals("ICD记录不存在或已删除", exception.getMessage());
    }

    @Test
    void updateIcdPathShouldSaveIcdBinaryContentWhenProvided() {
        CsIcdPathParam.UpdateParam param = new CsIcdPathParam.UpdateParam();
        byte[] fileContent = "<SCL version=\"1\"></SCL>".getBytes();
        param.setId("icd-001");
        param.setName("标准ICD");
        param.setIcdContent(fileContent);

        CsIcdPathPO existed = new CsIcdPathPO();
        existed.setId("icd-001");
        existed.setState(1);
        when(csIcdPathMapper.selectById(eq("icd-001"))).thenReturn(existed);
        when(csIcdPathMapper.updateById(any(CsIcdPathPO.class))).thenReturn(1);

        boolean result = service.updateIcdPath(param);

        ArgumentCaptor<CsIcdPathPO> captor = ArgumentCaptor.forClass(CsIcdPathPO.class);
        verify(csIcdPathMapper).updateById(captor.capture());
        Assertions.assertTrue(result);
        Assertions.assertArrayEquals(fileContent, captor.getValue().getIcdContent());
    }

    @Test
    void deleteIcdPathShouldMarkRecordDeleted() {
        when(csIcdPathMapper.update(any(CsIcdPathPO.class), any())).thenReturn(1);

        boolean result = service.deleteIcdPath(Collections.singletonList("icd-001"));

        ArgumentCaptor<CsIcdPathPO> captor = ArgumentCaptor.forClass(CsIcdPathPO.class);
        verify(csIcdPathMapper).update(captor.capture(), any());
        Assertions.assertTrue(result);
        Assertions.assertEquals(0, captor.getValue().getState());
        Assertions.assertNotNull(captor.getValue().getUpdateTime());
    }

    @Test
    void activateIcdPathShouldOnlyKeepTargetAsManualStandardIcd() {
        CsIcdPathPO icdPath = new CsIcdPathPO();
        icdPath.setId("icd-001");
        icdPath.setState(1);
        icdPath.setType(2);
        when(csIcdPathMapper.selectById(eq("icd-001"))).thenReturn(icdPath);
        when(csIcdPathMapper.update(any(CsIcdPathPO.class), any())).thenReturn(1);

        boolean result = service.activateIcdPath("icd-001");

        ArgumentCaptor<CsIcdPathPO> captor = ArgumentCaptor.forClass(CsIcdPathPO.class);
        verify(csIcdPathMapper, times(3)).update(captor.capture(), any());
        Assertions.assertTrue(result);
        Assertions.assertNull(captor.getAllValues().get(0));
        Assertions.assertNull(captor.getAllValues().get(1));
        Assertions.assertEquals(1, captor.getAllValues().get(2).getType());
        Assertions.assertNotNull(captor.getAllValues().get(2).getUpdateTime());
    }

    @Test
    void activateIcdPathShouldKeepUpstreamSourceWhenTargetIsUpstreamIcd() {
        CsIcdPathPO icdPath = new CsIcdPathPO();
        icdPath.setId("icd-001");
        icdPath.setState(1);
        icdPath.setType(4);
        when(csIcdPathMapper.selectById(eq("icd-001"))).thenReturn(icdPath);
        when(csIcdPathMapper.update(any(CsIcdPathPO.class), any())).thenReturn(1);

        boolean result = service.activateIcdPath("icd-001");

        ArgumentCaptor<CsIcdPathPO> captor = ArgumentCaptor.forClass(CsIcdPathPO.class);
        verify(csIcdPathMapper, times(3)).update(captor.capture(), any());
        Assertions.assertTrue(result);
        Assertions.assertEquals(3, captor.getAllValues().get(2).getType());
    }

    @Test
    void saveIcdCheckResultShouldUpdateIcdRecord() {
        CsIcdPathPO referenceIcd = new CsIcdPathPO();
        referenceIcd.setId("reference-icd");
        when(csIcdPathMapper.selectList(any())).thenReturn(Collections.singletonList(referenceIcd));

        CsIcdPathPO icdPath = new CsIcdPathPO();
        icdPath.setId("icd-001");
        icdPath.setState(1);
        when(csIcdPathMapper.selectById(eq("icd-001"))).thenReturn(icdPath);
        when(csIcdPathMapper.updateById(any(CsIcdPathPO.class))).thenReturn(1);

        IcdCheckResultSaveParam param = new IcdCheckResultSaveParam();
        param.setMappingJson("{}");
        param.setXml("<xml/>");
        param.setResult(1);
        JsonNode msg = objectMapper.createObjectNode().put("summary", "通过");
        param.setMsg(msg);

        boolean result = service.saveIcdCheckResult("icd-001", param);

        ArgumentCaptor<CsIcdPathPO> captor = ArgumentCaptor.forClass(CsIcdPathPO.class);
        verify(csIcdPathMapper).updateById(captor.capture());
        Assertions.assertTrue(result);
        Assertions.assertEquals("reference-icd", captor.getValue().getReferenceIcdId());
        Assertions.assertEquals(1, captor.getValue().getResult());
        Assertions.assertEquals("通过", captor.getValue().getMsg().get("summary").asText());
    }

    @Test
    void saveIcdCheckResultShouldSaveIcdBinaryContentWhenProvided() {
        CsIcdPathPO referenceIcd = new CsIcdPathPO();
        referenceIcd.setId("reference-icd");
        when(csIcdPathMapper.selectList(any())).thenReturn(Collections.singletonList(referenceIcd));

        CsIcdPathPO icdPath = new CsIcdPathPO();
        icdPath.setId("icd-001");
        icdPath.setState(1);
        when(csIcdPathMapper.selectById(eq("icd-001"))).thenReturn(icdPath);
        when(csIcdPathMapper.updateById(any(CsIcdPathPO.class))).thenReturn(1);

        byte[] fileContent = "<SCL name=\"checked\"></SCL>".getBytes();
        IcdCheckResultSaveParam param = new IcdCheckResultSaveParam();
        param.setMappingJson("{}");
        param.setResult(1);
        param.setIcdContent(fileContent);

        boolean result = service.saveIcdCheckResult("icd-001", param);

        ArgumentCaptor<CsIcdPathPO> captor = ArgumentCaptor.forClass(CsIcdPathPO.class);
        verify(csIcdPathMapper).updateById(captor.capture());
        Assertions.assertTrue(result);
        Assertions.assertArrayEquals(fileContent, captor.getValue().getIcdContent());
    }

    @Test
    void saveIcdCheckResultShouldSaveIcdDocumentJsonContentWhenProvided() throws Exception {
        CsIcdPathPO referenceIcd = new CsIcdPathPO();
        referenceIcd.setId("reference-icd");
        when(csIcdPathMapper.selectList(any())).thenReturn(Collections.singletonList(referenceIcd));

        CsIcdPathPO icdPath = new CsIcdPathPO();
        icdPath.setId("icd-001");
        icdPath.setState(1);
        when(csIcdPathMapper.selectById(eq("icd-001"))).thenReturn(icdPath);
        when(csIcdPathMapper.updateById(any(CsIcdPathPO.class))).thenReturn(1);

        IcdDocument icdDocument = new IcdDocument();
        icdDocument.setFileName("checked.icd");
        icdDocument.setIedName("IED1");
        icdDocument.setLdInst("LD0");
        IcdCheckResultSaveParam param = new IcdCheckResultSaveParam();
        param.setMappingJson("{}");
        param.setResult(1);
        param.setIcdDocument(icdDocument);

        boolean result = service.saveIcdCheckResult("icd-001", param);

        ArgumentCaptor<CsIcdPathPO> captor = ArgumentCaptor.forClass(CsIcdPathPO.class);
        verify(csIcdPathMapper).updateById(captor.capture());
        Assertions.assertTrue(result);
        Assertions.assertArrayEquals(objectMapper.writeValueAsBytes(icdDocument), captor.getValue().getIcdContent());
    }

    @Test
    void getMappingDetailShouldReturnJsonXmlAndUtf8IcdText() {
        CsIcdPathDetailVO detail = new CsIcdPathDetailVO();
        detail.setId("icd-001");
        detail.setName("标准ICD");
        detail.setJsonStr("{\"ied\":\"IED1\"}");
        detail.setXmlStr("<Root></Root>");
        detail.setIcdContent("<SCL name=\"demo\"></SCL>".getBytes(java.nio.charset.StandardCharsets.UTF_8));
        when(csIcdPathMapper.selectIcdPathDetailById(eq("icd-001"))).thenReturn(detail);

        CsIcdPathDetailVO result = service.getMappingDetail("  icd-001  ");

        Assertions.assertEquals("{\"ied\":\"IED1\"}", result.getJsonStr());
        Assertions.assertEquals("<Root></Root>", result.getXmlStr());
        Assertions.assertEquals("<SCL name=\"demo\"></SCL>", result.getIcdText());
        verify(csIcdPathMapper).selectIcdPathDetailById(eq("icd-001"));
    }

    @Test
    void getMappingDetailShouldReturnNullWhenRecordMissing() {
        when(csIcdPathMapper.selectIcdPathDetailById(eq("missing"))).thenReturn(null);

        CsIcdPathDetailVO result = service.getMappingDetail("missing");

        Assertions.assertNull(result);
    }

    private CsIcdPathParam buildParam(String name) {
        CsIcdPathParam param = new CsIcdPathParam();
        param.setName(name);
        param.setAngle(0);
        param.setUsePhaseIndex(1);
        param.setType(1);
        return param;
    }
}
