package com.nisum.myteam.service.impl;

import com.nisum.myteam.exception.handler.MyTeamException;
import com.nisum.myteam.model.dao.*;
import com.nisum.myteam.model.vo.MyProjectAllocationVO;
import com.nisum.myteam.model.vo.ResourceVO;
import com.nisum.myteam.repository.ResourceAllocationRepo;
import com.nisum.myteam.service.*;
import com.nisum.myteam.statuscodes.ResourceStatus;
import com.nisum.myteam.utils.MyTeamDateUtils;
import com.nisum.myteam.utils.MyTeamUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ResourceAllocationService implements IResourceAllocationService {

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private ResourceAllocationRepo resourceAllocationRepo;

    @Autowired
    private IAccountService accountService;

    @Autowired
    private IDomainService domainService;

    @Autowired
    private IProjectService projectService;

    @Autowired
    private IEmployeeService employeeService;


    public HashMap<String, Object> respMap = new HashMap<>();


    public ResourceAllocation addResourceAllocation(ResourceAllocation resourceAllocation, String loginEmpId) throws MyTeamException {

        return resourceAllocationRepo.save(resourceAllocation);
    }

    public boolean isResourceExistsForProject(String employeeId, String projectId) {
        boolean isExists = false;

        List<ResourceAllocation> resourceList = resourceAllocationRepo.findByEmployeeIdAndProjectId(employeeId, projectId);
        if (resourceList != null && resourceList.size() > 0) {
            isExists = true;

        }
        respMap.put("message", "Resource Not Found");
        return isExists;
    }


    public void updateResourceAlloc(ResourceAllocation resourceAlloc) {

        if (resourceAlloc != null) {
            ResourceAllocation resourcePers = resourceAllocationRepo.save(resourceAlloc);
            respMap.put("message", "resource has been updated");
            respMap.put("resourceObj", resourcePers);

        }

    }

    public void updatePreviousResource(ResourceAllocation resourceAllocReq, String loginEmpId) throws MyTeamException {

        List<ResourceAllocation> resourceAllocList = resourceAllocationRepo.findByEmployeeIdAndProjectId(resourceAllocReq.getEmployeeId(), resourceAllocReq.getProjectId());

        List<ResourceAllocation> resourceListWithLatestRecord = resourceAllocList.stream().filter(resource -> resource.getBillingEndDate().compareTo(new Date()) >= 0).collect(Collectors.toList());

        if (resourceListWithLatestRecord != null && resourceListWithLatestRecord.size() > 0) {
            ResourceAllocation resourceAllocPrev = resourceListWithLatestRecord.get(0);//latest resource record.

            log.info("The before date is::" + MyTeamDateUtils.getYesterdayDate());
            resourceAllocPrev.setBillingEndDate(MyTeamDateUtils.getYesterdayDate()); //adding resource.
            //resourceAllocPrev.setBillingEndDate(); //adding resource.

            log.info("After setting the date:::before saving the Resource::" + resourceAllocPrev);
            this.updateResourceAlloc(resourceAllocPrev);
        }

    }


    public boolean validateBillingStartEndDateAgainstProjectStartEndDate(ResourceAllocation resourceAllocation, String loginEmpId) throws MyTeamException {

        boolean isValid = true;

        Project project = projectService.getProjectByProjectId(resourceAllocation.getProjectId());
        log.info("Project::" + project);

        if (!resourceAllocation.getBillingStartDate().after(project.getProjectStartDate())) {
            log.info("Billing start date should be after Project start date");
            respMap.put("message", "Billing start date should be after Project start date");
            isValid = false;
        }

        if (!resourceAllocation.getBillingStartDate().before(resourceAllocation.getBillingEndDate())) {
            log.info("Billing start date should be before Billing End Date.");
            respMap.put("message", "Billing start date should be before Billing End Date.");
            isValid = false;
        }
        log.info("ResourceALloc Req::" + resourceAllocation);
        log.info("" + project.getProjectEndDate().toString());

        if (!resourceAllocation.getBillingEndDate().before(project.getProjectEndDate())) {
            log.info("Billing end date should be before Project End Date.");
            respMap.put("message", "Billing end date should be before Project End Date.");
            isValid = false;
        }

        respMap.put("resourceObj", resourceAllocation);
        return isValid;
    }


    public boolean validateBillingStartDateAgainstDOJ(ResourceAllocation resource) {
        String message = "";
        boolean isValid = true;

        Employee employee = employeeService.getEmployeeById(resource.getEmployeeId());
        Date empDoj = employeeService.getEmployeeById(resource.getEmployeeId()).getDateOfJoining();
        if (resource.getBillingStartDate().compareTo(empDoj) < 0) {
            message = "Resource Billing Start Date (" + resource.getBillingStartDate() + " ) in "
                    + projectService.getProjectByProjectId(resource.getProjectId()).getProjectName()
                    + " project should not be before Date of Joining ( " + empDoj + ").";
            isValid = false;
            respMap.put("message", message);
        }
        return isValid;
    }


    public boolean isResourceAssignedToAnyProject(ResourceAllocation resourceAllocReq) {

        boolean isAssigned = false;
        String message = "";
        //List<ResourceAllocation> resourceAllocList = resourceAllocationRepo.findByEmployeeIdAndProjectId(resourceAllocReq.getEmployeeId(), resourceAllocReq.getProjectId());
        List<ResourceAllocation> resourceAllocList = resourceAllocationRepo.findByEmployeeId(resourceAllocReq.getEmployeeId());

        List<ResourceAllocation> resourceListWithLatestRecord = resourceAllocList.stream().filter(resource -> resource.getBillingEndDate().compareTo(new Date()) >= 0).collect(Collectors.toList());

        if (resourceListWithLatestRecord != null && resourceListWithLatestRecord.size() > 0) {
            ResourceAllocation resourceAllocPrev = resourceListWithLatestRecord.get(0);//latest resource record.

            message = "Resource " + resourceAllocPrev.getEmployeeId() + " already Assigned to the "
                    + projectService.getProjectByProjectId(resourceAllocPrev.getProjectId()).getProjectName()
                    + " Project" + " from " + resourceAllocPrev.getBillingStartDate() + "to " + resourceAllocPrev.getBillingEndDate();
            isAssigned = true;
            respMap.put("message", message);
        }
        return isAssigned;
    }


    public void updateResource(ResourceAllocation resourceAllocReq, String loginEmpId) throws MyTeamException {
        ResourceAllocation resourceAllocationPers = resourceAllocationRepo.save(resourceAllocReq);
        respMap.put("message", "Resource updated successfully");
        respMap.put("resourceObj", resourceAllocationPers);

    }

    public void deleteResource(ResourceAllocation resourceReq, String loginEmpId) {
        List<ResourceAllocation> resourcesList = resourceAllocationRepo.findByEmployeeIdAndProjectId(resourceReq.getEmployeeId(), resourceReq.getProjectId());
        resourcesList.forEach(resource -> resourceAllocationRepo.delete(resource));

    }

    @Override
    public List<ResourceAllocation> getAllResources() {
        return resourceAllocationRepo.findAll();
    }


    public List<ResourceAllocation> getResourcesSortByBillingStartDate(String employeeId) {
        Query query = prepareQuery(employeeId, MyTeamUtils.BILLING_START_DATE);
        return mongoTemplate.find(query, ResourceAllocation.class);
    }

    private Query prepareQuery(String employeeId, String dateColumn) {
        Query query = new Query();
        query.addCriteria(Criteria.where(MyTeamUtils.EMPLOYEE_ID).is(employeeId));
        query.limit(MyTeamUtils.ONE);
        query.with(new Sort(Sort.Direction.DESC, dateColumn));
        return query;
    }

    @Override
    public List<ResourceAllocation> getActiveResources(String empId) {
        List<ResourceAllocation> resourcesList = new ArrayList<>();
        for (ResourceAllocation resource : resourceAllocationRepo.findByEmployeeId(empId)) {
            //if (resource.isActive()) {
            if (resource.getBillingEndDate().compareTo(new Date()) > 0) {
                resourcesList.addAll(getAllResourcesForProject(resource.getProjectId()));
            }
        }
        return resourcesList;
    }

    public List<ResourceAllocation> getAllResourcesForProject(String projectId) {
        return resourceAllocationRepo.findByProjectId(projectId);
    }


    @Override
    public List<ResourceAllocation> getAllResourcesForAllActiveProjects() {
        List<ResourceAllocation> resourceList = new ArrayList<>();

        for (Project activeProject : projectService.getOnlyActiveProjects()) {
            resourceList.addAll(getAllResourcesForProject(activeProject.getProjectId()));
        }

        return resourceList;
    }


    @Override
    public List<ResourceVO> getResourcesForProject(String projectId, String statusFlag) {
        List<ResourceVO> resourcesList = new ArrayList<>();

        for (ResourceAllocation resource : resourceAllocationRepo.findByProjectId(projectId)) {
            Date billingEndDate = resource.getBillingEndDate();
            if (billingEndDate != null) {

                ResourceVO resourceVO = new ResourceVO();
                resourceVO.setId(resource.getId());
                resourceVO.setProjectId(resource.getProjectId());
                resourceVO.setProjectName(projectService.getProjectByProjectId(resource.getProjectId()).getProjectName());
                resourceVO.setResourceRole(resource.getResourceRole());
                resourceVO.setBillingStartDate(resource.getBillingStartDate());
                resourceVO.setBillingEndDate(resource.getBillingEndDate());
                resourceVO.setBillableStatus(resource.getBillableStatus());
                resourceVO.setEmployeeId(resource.getEmployeeId());

                Employee employee = employeeService.getEmployeeById(resource.getEmployeeId());
                resourceVO.setEmailId(employee.getEmailId());
                resourceVO.setEmployeeName(employee.getEmployeeName());
                resourceVO.setDesignation(employee.getDesignation());


                // Active
                if (statusFlag.equals(ResourceStatus.ACTIVE.getStatus()) && billingEndDate.compareTo(new Date()) >= 0) {
                    resourceVO.setResourceStatus(ResourceStatus.ACTIVE.getStatus());
                    resourcesList.add(resourceVO);
                } else if (statusFlag.equals(ResourceStatus.IN_ACTIVE.getStatus()) && billingEndDate.compareTo(new Date()) < 0) {
                    resourceVO.setResourceStatus(ResourceStatus.IN_ACTIVE.getStatus());
                    resourcesList.add(resourceVO);
                } else if (statusFlag.equals(MyTeamUtils.BOTH))
                    resourcesList.add(resourceVO);

            }

        }
        return resourcesList;
    }


    @Override
    public List<MyProjectAllocationVO> getWorkedProjectsForResource(String empId) {

        Project project = null;
        Account account = null;
        Domain domain = null;
        Employee employee = null;
        List<MyProjectAllocationVO> myProjectList = new ArrayList<>();

        List<ResourceAllocation> resourcesAllocatedList = resourceAllocationRepo.findByEmployeeId(empId);

        if (null != resourcesAllocatedList && !resourcesAllocatedList.isEmpty() && MyTeamUtils.INT_ZERO < resourcesAllocatedList.size()) {
            for (ResourceAllocation resourceAlloc : resourcesAllocatedList) {

                project = projectService.getProjectByProjectId(resourceAlloc.getProjectId());
                account = accountService.getAccountById(project.getAccountId());
                domain = domainService.getDomainById(project.getDomainId());
                employee = employeeService.getEmployeeById(resourceAlloc.getEmployeeId());

                MyProjectAllocationVO myProject = new MyProjectAllocationVO();
                myProject.setProjectId(project.getProjectId());
                myProject.setProjectName(project.getProjectName());
                myProject.setAccountName(account.getAccountName());

                myProject.setBillableStatus(resourceAlloc.getBillableStatus());
                myProject.setBillingStartDate(resourceAlloc.getBillingStartDate());
                myProject.setBillingEndDate(resourceAlloc.getBillingEndDate());
                myProject.setShift(employee.getShift());
                if (resourceAlloc.getBillingEndDate().compareTo(new Date()) > 0) {
                    myProject.setResourceStatus(ResourceStatus.ACTIVE.getStatus());
                } else {
                    myProject.setResourceStatus(ResourceStatus.IN_ACTIVE.getStatus());
                }

                if (project.getDeliveryLeadIds() != null) {
                    myProject.setDeliverLeadIds(employeeService.getDeliveryManagerMap(project.getDeliveryLeadIds()));
                }
                myProjectList.add(myProject);
            }
        }
        return myProjectList;
    }


    @Override
    public List<ResourceAllocation> getResourcesUnderDeliveryLead(String deliveryLeadId) {
        List<String> projectIdsList = new ArrayList<>();
        List<ResourceAllocation> resourcesList = new ArrayList<>();

        for (Project project : projectService.getProjectsForDeliveryLead(deliveryLeadId))
            projectIdsList.add(project.getProjectId());

        Query query = new Query(Criteria.where("projectId").in(projectIdsList));
        List<ResourceAllocation> resourcesListPersisted = mongoTemplate.find(query, ResourceAllocation.class);

        for (ResourceAllocation resourceAlloc : resourcesListPersisted) {
            if (!resourceAlloc.getEmployeeId().equals(deliveryLeadId))
                resourcesList.add(resourceAlloc);
        }
        return resourcesList;

    }


    @Override
    public List<ResourceAllocation> getBillingsForEmployee(String empId) {
//        List<Billing> billingsList = billingRepo.findByEmployeeId(empId);
        List<ResourceAllocation> resourcesList = resourceAllocationRepo.findByEmployeeId(empId);
        if (resourcesList == null || resourcesList.size() == 0) {
            return resourcesList;
        } else {
            log.info("The resources billing list before sorting::" + resourcesList);
            //return billingsList.stream().sorted(Comparator.comparing(Billing::getCreatedOn).reversed()).collect(Collectors.toList());
            return resourcesList.stream().sorted(Comparator.comparing(ResourceAllocation::getBillingStartDate).reversed()).collect(Collectors.toList());

        }
    }

    @Override
    public List<ResourceAllocation> getBillingsForProject(String empId, String projectId) {
        List<ResourceAllocation> resourcesList = resourceAllocationRepo.findByEmployeeIdAndProjectId(empId, projectId);

        if (resourcesList == null || resourcesList.size() == 0) {
            return resourcesList;
        } else {
            //return billingsList.stream().sorted(Comparator.comparing(Billing::getCreatedOn).reversed()).collect(Collectors.toList());
            return resourcesList.stream().sorted(Comparator.comparing(ResourceAllocation::getBillingStartDate).reversed()).collect(Collectors.toList());
        }

    }


    @Override
    public List<Employee> getUnAssignedEmployees() {

        List<Employee> notAssignedEmployees = new ArrayList<>();
        List<String> resourceIdsList = new ArrayList<>();

        for (ResourceAllocation resource : this.getAllResources()) {
            Project project = projectService.getProjectByProjectId(resource.getProjectId());
            if (project != null && project.getStatus() != null && !"Completed".equalsIgnoreCase(project.getStatus())) {
                resourceIdsList.add(resource.getEmployeeId());
            }
        }
        for (Employee employee : employeeService.getAllEmployees()) {
            if (!resourceIdsList.contains(employee.getEmployeeId())) {
                notAssignedEmployees.add(employee);
            }
        }

        return notAssignedEmployees;
    }


}
