package pwc.taxtech.atms.service.impl;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.nutz.lang.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

import pwc.taxtech.atms.CommonIT;
import pwc.taxtech.atms.common.AuthUserHelper;
import pwc.taxtech.atms.common.CommonUtils;
import pwc.taxtech.atms.common.OperationAction;
import pwc.taxtech.atms.common.OperationModule;
import pwc.taxtech.atms.dao.OperationLogBasicDataMapper;
import pwc.taxtech.atms.dao.OrganizationStructureMapper;
import pwc.taxtech.atms.dto.OrganizationStructureDto;
import pwc.taxtech.atms.dto.OrganizationStructureInputDto;
import pwc.taxtech.atms.entitiy.OperationLogBasicData;
import pwc.taxtech.atms.entitiy.OperationLogBasicDataExample;
import pwc.taxtech.atms.entitiy.OrganizationStructure;
import pwc.taxtech.atms.entitiy.OrganizationStructureExample;
import pwc.taxtech.atms.entitiy.OrganizationStructureExample.Criteria;
import pwc.taxtech.atms.security.JwtAuthenticationProvider;
import pwc.taxtech.atms.security.JwtAuthenticationToken;
import pwc.taxtech.atms.service.OrganizationStructureService;

public class OrganizationStructureServiceIT extends CommonIT {

    private static Logger logger = LoggerFactory.getLogger(OrganizationStructureServiceIT.class);

    private static final String UUID1 = CommonUtils.getUUID();
    private static final String UUID2 = CommonUtils.getUUID();
    private static final String UUID3 = CommonUtils.getUUID();
    private static final String UUID4 = CommonUtils.getUUID();

    @Autowired
    private OrganizationStructureService organizationStructureService;

    @Autowired
    private OrganizationStructureMapper organizationStructureMapper;

    @Autowired
    private JwtAuthenticationProvider jwtAuthenticationProvider;

    @Autowired
    private OperationLogBasicDataMapper operationLogBasicDataMapper;

    @Autowired
    private AuthUserHelper authUserHelper;

    private OrganizationStructure organizationStructure1;

    private OrganizationStructure organizationStructure2;

    private OrganizationStructure organizationStructure3;

    private OrganizationStructureInputDto organizationStructureDto1;

    private OrganizationStructureInputDto organizationStructureDto2;

    @Before
    public void init() {
        organizationStructure1 = new OrganizationStructure();
        organizationStructure1.setID(UUID1);
        organizationStructure1.setIsActive(true);
        organizationStructure1.setName("test-name1");
        organizationStructure1.setCreateTime(new Date());
        organizationStructure1.setUpdateTime(new Date());
        organizationStructureMapper.insert(organizationStructure1);

        organizationStructure2 = new OrganizationStructure();
        organizationStructure2.setID(UUID2);
        organizationStructure2.setIsActive(true);
        organizationStructure2.setName("test-name2");
        organizationStructure2.setCreateTime(new Date());
        organizationStructure2.setUpdateTime(new Date());
        organizationStructureMapper.insert(organizationStructure2);

        organizationStructureDto1 = new OrganizationStructureInputDto();
        organizationStructureDto1.setID(UUID3);
        organizationStructureDto1.setIsActive(true);
        organizationStructureDto1.setName("test-name3");

        organizationStructureDto2 = new OrganizationStructureInputDto();
        organizationStructureDto2.setID(UUID4);
        organizationStructureDto2.setIsActive(true);
        organizationStructureDto2.setName("test-name4");

        organizationStructure3 = new OrganizationStructure();
        organizationStructure3.setID(organizationStructureDto2.getID());
        organizationStructure3.setIsActive(organizationStructureDto2.getIsActive());
        organizationStructure3.setName(organizationStructureDto2.getName());
        organizationStructure3.setCreateTime(new Date());
        organizationStructure3.setUpdateTime(new Date());
        organizationStructureMapper.insert(organizationStructure3);

        // get current user name
        Authentication request = new JwtAuthenticationToken("xxxx");
        Authentication authenticaiton = jwtAuthenticationProvider.authenticate(request);
        SecurityContextHolder.getContext().setAuthentication(authenticaiton);

    }

    @Test
    public void getOrganizationStructuresTest() {
        logger.debug("enter getOrganizationStructuresTest");
        List<OrganizationStructureDto> organizationStructureDtoList = organizationStructureService
                .getOrganizationStructures();
        logger.debug("get organizationStructureDtoList: {}", organizationStructureDtoList);
        Assert.assertNotNull(organizationStructureDtoList);
        Assert.assertTrue(!organizationStructureDtoList.isEmpty());
    }

    @Test
    public void addOrganizationStructureTest() {
        logger.debug("enter addOrganizationStructureTest");
        List<OrganizationStructureInputDto> organizationStructureDtoList = new ArrayList<>();
        organizationStructureDtoList.add(organizationStructureDto1);
        organizationStructureService.addOrganizationStructures(organizationStructureDtoList);
        OrganizationStructure actualOrganizationStructure = findByNameAndIsActive(organizationStructureDto1);
        Assert.assertEquals(organizationStructureDto1.getName(), actualOrganizationStructure.getName());
        Assert.assertEquals(organizationStructureDto1.getIsActive(), actualOrganizationStructure.getIsActive());
        logger.debug("expected OrganizationStructureDto: {}", organizationStructureDto1);
        logger.debug("actual OrganizationStructure: {}", actualOrganizationStructure);

        logger.debug("basic data operation log for add");
        List<OperationLogBasicData> operationLogBasicDataList = findOperationLogBasicData(organizationStructureDto1);
        OperationLogBasicData operationLogBasicData = operationLogBasicDataList.get(0);
        Assert.assertEquals(organizationStructureDto1.getName(), operationLogBasicData.getOperationObject());
        Assert.assertEquals(authUserHelper.getCurrentAuditor(), operationLogBasicData.getOperationUser());
        Assert.assertEquals(OperationModule.BasicDataOrganizationStructure.toString(),
                operationLogBasicData.getModuleName());
        Assert.assertEquals(OperationAction.New.toString(), operationLogBasicData.getOperationAction());
    }

    @Test
    public void updateOrganizationStructuresTest() {
        logger.debug("enter updateOrganizationStructuresTest");
        OrganizationStructure originOrganizationStructure = findByNameAndIsActive(organizationStructureDto2);
        OrganizationStructureInputDto updateOrganizationStructureDto = new OrganizationStructureInputDto();
        CommonUtils.copyProperties(organizationStructureDto2, updateOrganizationStructureDto);
        String originName = originOrganizationStructure.getName();
        boolean originIsActive = originOrganizationStructure.getIsActive();
        updateOrganizationStructureDto.setName("tmp-name");
        updateOrganizationStructureDto.setIsActive(false);
        Assert.assertFalse(originName.equals(updateOrganizationStructureDto.getName()));
        Assert.assertFalse(originIsActive == updateOrganizationStructureDto.getIsActive());

        List<OrganizationStructureInputDto> organizationStructureDtoList = new ArrayList<>();
        organizationStructureDtoList.add(updateOrganizationStructureDto);
        boolean result = organizationStructureService.updateOrganizationStructures(organizationStructureDtoList);
        Assert.assertTrue(result);

        OrganizationStructure updateOrganizationStructure = findByNameAndIsActive(updateOrganizationStructureDto);
        compareOrganizationStructure(updateOrganizationStructure, updateOrganizationStructureDto);
        logger.debug("expected OrganizationStructureDto: {}", updateOrganizationStructure);
        logger.debug("actual OrganizationStructure: {}", updateOrganizationStructureDto);

        logger.debug("basic data operation log for add");
        OperationLogBasicDataExample example = new OperationLogBasicDataExample();
        pwc.taxtech.atms.entitiy.OperationLogBasicDataExample.Criteria criteria = example.createCriteria();
        criteria.andOperationObjectEqualTo(organizationStructureDto2.getName());
        criteria.andOperationUserEqualTo(authUserHelper.getCurrentAuditor());
        criteria.andModuleNameEqualTo(OperationModule.BasicDataOrganizationStructure.toString());
        criteria.andOperationActionEqualTo(OperationAction.Update.toString());
        List<OperationLogBasicData> operationLogBasicDataList = operationLogBasicDataMapper.selectByExample(example);
        Assert.assertNotNull(operationLogBasicDataList);
        Assert.assertTrue(!operationLogBasicDataList.isEmpty());
        // update both name and isAcitve, there should be two records in operaiton log
        Assert.assertTrue(operationLogBasicDataList.size() > 1);

        operationLogBasicDataList.sort(new Comparator<OperationLogBasicData>() {
            @Override
            public int compare(OperationLogBasicData o1, OperationLogBasicData o2) {
                return o1.getOperationContent().compareTo(o2.getOperationContent());
            }
        });

        // for isActive
        OperationLogBasicData logForFieldIsActive = operationLogBasicDataList.get(0);
        Assert.assertEquals("ISACTIVE", logForFieldIsActive.getOperationContent().toUpperCase());
        Assert.assertEquals(OperationModule.BasicDataOrganizationStructure.toString(),
                logForFieldIsActive.getModuleName());
        Assert.assertEquals(organizationStructureDto2.getName(), logForFieldIsActive.getOperationObject());
        Assert.assertEquals(OperationAction.Update.toString(), logForFieldIsActive.getOperationAction());
        Assert.assertEquals(authUserHelper.getCurrentAuditor(), logForFieldIsActive.getOperationUser());
        Assert.assertEquals(Strings.upperFirst(organizationStructureDto2.getIsActive().toString()), logForFieldIsActive.getOriginalState());
        Assert.assertEquals(Strings.upperFirst(updateOrganizationStructureDto.getIsActive().toString()),
                logForFieldIsActive.getUpdateState());

        // for name
        OperationLogBasicData logForFieldName = operationLogBasicDataList.get(1);
        Assert.assertEquals("NAME", logForFieldName.getOperationContent().toUpperCase());
        Assert.assertEquals(OperationModule.BasicDataOrganizationStructure.toString(), logForFieldName.getModuleName());
        Assert.assertEquals(organizationStructureDto2.getName(), logForFieldName.getOperationObject());
        Assert.assertEquals(OperationAction.Update.toString(), logForFieldName.getOperationAction());
        Assert.assertEquals(authUserHelper.getCurrentAuditor(), logForFieldName.getOperationUser());
        Assert.assertEquals(organizationStructureDto2.getName(), logForFieldName.getOriginalState());
        Assert.assertEquals(updateOrganizationStructureDto.getName(), logForFieldName.getUpdateState());

    }

    private List<OperationLogBasicData> findOperationLogBasicData(OrganizationStructureInputDto organizationStructureDto) {
        OperationLogBasicDataExample example = new OperationLogBasicDataExample();
        pwc.taxtech.atms.entitiy.OperationLogBasicDataExample.Criteria criteria = example.createCriteria();
        criteria.andOperationObjectEqualTo(organizationStructureDto.getName());
        criteria.andOperationUserEqualTo(authUserHelper.getCurrentAuditor());
        criteria.andModuleNameEqualTo(OperationModule.BasicDataOrganizationStructure.toString());
        criteria.andOperationActionEqualTo(OperationAction.New.toString());
        List<OperationLogBasicData> operationLogBasicDataList = operationLogBasicDataMapper.selectByExample(example);
        Assert.assertNotNull(operationLogBasicDataList);
        Assert.assertTrue(!operationLogBasicDataList.isEmpty());
        return operationLogBasicDataList;
    }

    private void compareOrganizationStructure(OrganizationStructure organizationStructure,
            OrganizationStructureInputDto organizationStructureDto) {
        Assert.assertEquals(organizationStructure.getID(), organizationStructureDto.getID());
        Assert.assertEquals(organizationStructure.getIsActive(), organizationStructureDto.getIsActive());
        Assert.assertEquals(organizationStructure.getName(), organizationStructureDto.getName());
    }

    private OrganizationStructure findByNameAndIsActive(OrganizationStructureInputDto organizationStructureDto) {
        OrganizationStructureExample example = new OrganizationStructureExample();
        Criteria criteria = example.createCriteria();
        criteria.andNameEqualTo(organizationStructureDto.getName());
        criteria.andIsActiveEqualTo(organizationStructureDto.getIsActive());

        List<OrganizationStructure> actualOrganizationStructures = organizationStructureMapper.selectByExample(example);
        Assert.assertNotNull(actualOrganizationStructures);
        Assert.assertTrue(!actualOrganizationStructures.isEmpty());
        OrganizationStructure organizationStructure = actualOrganizationStructures.get(0);
        return organizationStructure;
    }

    @After
    public void clean() {
        organizationStructureMapper.deleteByExample(null);
        operationLogBasicDataMapper.deleteByExample(null);
    }

}