package com.njcn.gather.tool.addledger.service.impl;

import com.njcn.gather.tool.addledger.component.AddLedgerTreeBuilder;
import com.njcn.gather.tool.addledger.mapper.AddLedgerEngineeringMapper;
import com.njcn.gather.tool.addledger.mapper.AddLedgerEquipmentMapper;
import com.njcn.gather.tool.addledger.mapper.AddLedgerLedgerMapper;
import com.njcn.gather.tool.addledger.mapper.AddLedgerLineMapper;
import com.njcn.gather.tool.addledger.mapper.AddLedgerProjectMapper;
import com.njcn.gather.tool.addledger.mapper.AddDeviceUnitMapper;
import com.njcn.gather.tool.addledger.mapper.AddOverlimitMapper;
import com.njcn.gather.tool.addledger.pojo.constant.AddLedgerConst;
import com.njcn.gather.tool.addledger.pojo.param.AddDeviceUnitSaveParam;
import com.njcn.gather.tool.addledger.pojo.param.AddLedgerEquipmentSaveParam;
import com.njcn.gather.tool.addledger.pojo.param.AddLedgerLineSaveParam;
import com.njcn.gather.tool.addledger.pojo.po.AddDeviceUnit;
import com.njcn.gather.tool.addledger.pojo.po.AddLedgerEquipmentPO;
import com.njcn.gather.tool.addledger.pojo.po.AddLedgerLedgerPO;
import com.njcn.gather.tool.addledger.pojo.po.AddLedgerLinePO;
import com.njcn.gather.tool.addledger.pojo.po.AddOverlimitPO;
import com.njcn.gather.tool.addledger.pojo.vo.AddDeviceUnitVO;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

import java.math.BigDecimal;

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

class AddLedgerServiceImplTest {

    private final AddLedgerEngineeringMapper engineeringMapper = mock(AddLedgerEngineeringMapper.class);
    private final AddLedgerProjectMapper projectMapper = mock(AddLedgerProjectMapper.class);
    private final AddLedgerEquipmentMapper equipmentMapper = mock(AddLedgerEquipmentMapper.class);
    private final AddLedgerLineMapper lineMapper = mock(AddLedgerLineMapper.class);
    private final AddLedgerLedgerMapper ledgerMapper = mock(AddLedgerLedgerMapper.class);
    private final AddDeviceUnitMapper deviceUnitMapper = mock(AddDeviceUnitMapper.class);
    private final AddOverlimitMapper overlimitMapper = mock(AddOverlimitMapper.class);
    private final AddLedgerTreeBuilder treeBuilder = mock(AddLedgerTreeBuilder.class);

    private final AddLedgerServiceImpl service = new AddLedgerServiceImpl(
            engineeringMapper,
            projectMapper,
            equipmentMapper,
            lineMapper,
            ledgerMapper,
            deviceUnitMapper,
            overlimitMapper,
            treeBuilder);

    @Test
    void saveLineShouldExplainLineIdWhenCreatingWithUnknownLineId() {
        String lineId = "25e263294c2f47559258941bb75f1de8";
        AddLedgerLineSaveParam param = buildValidLineParam();
        param.setLineId(lineId);

        when(ledgerMapper.selectActiveNode(eq(lineId), eq(AddLedgerConst.LEVEL_LINE))).thenReturn(null);
        when(ledgerMapper.selectNodeById(eq(lineId))).thenReturn(null);

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

        Assertions.assertEquals("测点节点不存在，id=" + lineId
                + "。如果是新增监测点，请不要传 lineId；如果是编辑监测点，请传已存在的测点 ID", exception.getMessage());
    }

    @Test
    void saveEquipmentShouldCreateDefaultDeviceUnitWhenCreatingEquipment() {
        AddLedgerEquipmentSaveParam param = buildValidEquipmentParam();
        AddLedgerLedgerPO projectLedger = buildLedger("project-001", "engineering-001",
                AddLedgerConst.ROOT_PARENT_ID + ",engineering-001", AddLedgerConst.LEVEL_PROJECT);
        AddLedgerLedgerPO engineeringLedger = buildLedger("engineering-001", AddLedgerConst.ROOT_PARENT_ID,
                AddLedgerConst.ROOT_PARENT_ID, AddLedgerConst.LEVEL_ENGINEERING);

        when(ledgerMapper.selectActiveNode(eq("project-001"), eq(AddLedgerConst.LEVEL_PROJECT))).thenReturn(projectLedger);
        when(ledgerMapper.selectActiveNode(eq("engineering-001"), eq(AddLedgerConst.LEVEL_ENGINEERING))).thenReturn(engineeringLedger);
        when(ledgerMapper.selectActiveNode(any(String.class), eq(AddLedgerConst.LEVEL_EQUIPMENT)))
                .thenAnswer(invocation -> buildLedger(invocation.getArgument(0), "project-001",
                        AddLedgerConst.ROOT_PARENT_ID + ",engineering-001,project-001", AddLedgerConst.LEVEL_EQUIPMENT));
        when(equipmentMapper.selectOne(any())).thenReturn(buildActiveEquipment("device-001"));

        service.saveEquipment(param);

        ArgumentCaptor<AddDeviceUnit> captor = ArgumentCaptor.forClass(AddDeviceUnit.class);
        verify(deviceUnitMapper).insert(captor.capture());
        Assertions.assertNotNull(captor.getValue().getDevId());
        Assertions.assertEquals("Hz", captor.getValue().getUnitFrequency());
        Assertions.assertEquals("kV", captor.getValue().getPhaseVoltage());
    }

    @Test
    void saveDeviceUnitShouldUpdateExistingDeviceUnit() {
        AddDeviceUnit existing = new AddDeviceUnit();
        existing.setDevId("device-001");
        when(deviceUnitMapper.selectById(eq("device-001"))).thenReturn(existing);
        when(equipmentMapper.selectOne(any())).thenReturn(buildActiveEquipment("device-001"));

        AddDeviceUnitSaveParam param = new AddDeviceUnitSaveParam();
        param.setDevId("device-001");
        param.setUnitFrequency("MHz");
        param.setPhaseVoltage("V");

        AddDeviceUnitVO result = service.saveDeviceUnit(param);

        verify(deviceUnitMapper).updateById(any(AddDeviceUnit.class));
        Assertions.assertEquals("device-001", result.getDevId());
        Assertions.assertEquals("MHz", result.getUnitFrequency());
        Assertions.assertEquals("V", result.getPhaseVoltage());
    }

    @Test
    void getLineOverlimitShouldQueryExistingLineOverlimit() {
        AddLedgerLinePO line = new AddLedgerLinePO();
        line.setLineId("line-001");
        AddOverlimitPO overlimit = new AddOverlimitPO();
        overlimit.setId("line-001");

        when(lineMapper.selectOne(any())).thenReturn(line);
        when(overlimitMapper.selectById(eq("line-001"))).thenReturn(overlimit);

        AddOverlimitPO result = service.getLineOverlimit("line-001");

        Assertions.assertSame(overlimit, result);
    }

    private AddLedgerLineSaveParam buildValidLineParam() {
        AddLedgerLineSaveParam param = new AddLedgerLineSaveParam();
        param.setDeviceId("device-001");
        param.setName("测试测点");
        param.setLineNo(1);
        param.setConType(0);
        param.setVolGrade(new BigDecimal("10"));
        param.setCtRatio(BigDecimal.ONE);
        param.setCt2Ratio(BigDecimal.ONE);
        param.setPtRatio(BigDecimal.ONE);
        param.setPt2Ratio(BigDecimal.ONE);
        return param;
    }

    private AddLedgerEquipmentSaveParam buildValidEquipmentParam() {
        AddLedgerEquipmentSaveParam param = new AddLedgerEquipmentSaveParam();
        param.setProjectId("project-001");
        param.setName("设备A");
        param.setNdid("ndid-001");
        param.setMac("00:11:22:33:44:55");
        param.setDevModel("model-001");
        return param;
    }

    private AddLedgerLedgerPO buildLedger(String id, String pid, String pids, Integer level) {
        AddLedgerLedgerPO ledger = new AddLedgerLedgerPO();
        ledger.setId(id);
        ledger.setPid(pid);
        ledger.setPids(pids);
        ledger.setLevel(level);
        ledger.setState(AddLedgerConst.STATE_NORMAL);
        ledger.setName(id);
        return ledger;
    }

    private AddLedgerEquipmentPO buildActiveEquipment(String id) {
        AddLedgerEquipmentPO equipment = new AddLedgerEquipmentPO();
        equipment.setId(id);
        equipment.setName("设备A");
        equipment.setRunStatus(AddLedgerConst.EQUIPMENT_RUN_STATUS_OFFLINE);
        return equipment;
    }
}
