package de.avatic.lcc.service.access;

import de.avatic.lcc.dto.calculation.PremiseDTO;
import de.avatic.lcc.dto.calculation.ResolvePremiseDTO;
import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
import de.avatic.lcc.dto.calculation.edit.masterData.MaterialUpdateDTO;
import de.avatic.lcc.dto.calculation.edit.masterData.PackagingUpdateDTO;
import de.avatic.lcc.dto.calculation.edit.masterData.PriceUpdateDTO;
import de.avatic.lcc.model.db.premises.Premise;
import de.avatic.lcc.model.db.premises.PremiseState;
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
import de.avatic.lcc.repositories.premise.PremiseRepository;
import de.avatic.lcc.service.transformer.generic.DimensionTransformer;
import de.avatic.lcc.service.transformer.premise.PremiseTransformer;
import de.avatic.lcc.service.users.AuthorizationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Service
public class PremisesService {


    private static final Logger log = LoggerFactory.getLogger(PremisesService.class);

    private final PremiseRepository premiseRepository;
    private final PremiseTransformer premiseTransformer;
    private final DimensionTransformer dimensionTransformer;
    private final DestinationService destinationService;

    private final AuthorizationService authorizationService;

    public PremisesService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DimensionTransformer dimensionTransformer, DestinationService destinationService, AuthorizationService authorizationService) {
        this.premiseRepository = premiseRepository;
        this.premiseTransformer = premiseTransformer;
        this.dimensionTransformer = dimensionTransformer;
        this.destinationService = destinationService;
        this.authorizationService = authorizationService;
    }

    @Transactional(readOnly = true)
    public SearchQueryResult<PremiseDTO> listPremises(String filter, Integer page, Integer limit, Integer userId, Boolean draft, Boolean archived, Boolean done) {
        var admin = authorizationService.isSuper();
        userId = authorizationService.getUserId();
        return SearchQueryResult.map(premiseRepository.listPremises(filter, new SearchQueryPagination(page, limit), userId, draft, archived, done), admin ? premiseTransformer::toPremiseDTOWithUserInfo : premiseTransformer::toPremiseDTO);
    }

    @Transactional(readOnly = true)
    public List<PremiseDetailDTO> getPremises(List<Integer> premiseIds) {
        var admin = authorizationService.isSuper();
        var userId = authorizationService.getUserId();

        if (!admin)
            premiseRepository.checkOwner(premiseIds, userId);

        var premisses = premiseRepository.getPremisesById(premiseIds).stream().filter(p -> p.getState().equals(PremiseState.DRAFT)).toList();

        return premisses.stream().map(premiseTransformer::toPremiseDetailDTO).toList();
    }


    public void updatePackaging(PackagingUpdateDTO packagingDTO) {
        var admin = authorizationService.isSuper();
        var userId = authorizationService.getUserId();

        if (!admin)
            premiseRepository.checkOwner(packagingDTO.getPremiseIds(), userId);

        var dimensions = packagingDTO.getDimensions() == null ? null : dimensionTransformer.toDimensionEntity(packagingDTO.getDimensions());

        premiseRepository.resetPackaging(packagingDTO.getPremiseIds(), dimensions, packagingDTO.getStackable(), packagingDTO.getMixable());

    }

    public void updateMaterial(MaterialUpdateDTO materialUpdateDTO) {
        var admin = authorizationService.isSuper();
        var userId = authorizationService.getUserId();

        if (!admin)
            premiseRepository.checkOwner(materialUpdateDTO.getPremiseIds(), userId);



        var unlockedIds = premiseRepository.getIdsWithUnlockedTariffs(materialUpdateDTO.getPremiseIds());

        if (unlockedIds.isEmpty()) return;

        var tariffRate = materialUpdateDTO.getTariffRate() == null ? null : BigDecimal.valueOf(materialUpdateDTO.getTariffRate().doubleValue());
        premiseRepository.updateMaterial(unlockedIds, materialUpdateDTO.getHsCode(), tariffRate);

    }

    public void updatePrice(PriceUpdateDTO priceUpdateDTO) {
        var admin = authorizationService.isSuper();
        var userId = authorizationService.getUserId();

        if (!admin)
            premiseRepository.checkOwner(priceUpdateDTO.getPremiseIds(), userId);

        var price = priceUpdateDTO.getPrice() == null ? null : BigDecimal.valueOf(priceUpdateDTO.getPrice().doubleValue());
        var overseaShare = priceUpdateDTO.getOverseaShare() == null ? null : BigDecimal.valueOf(priceUpdateDTO.getOverseaShare().doubleValue());

        premiseRepository.updatePrice(priceUpdateDTO.getPremiseIds(), price, priceUpdateDTO.getIncludeFcaFee(), overseaShare);
    }

    @Transactional
    public void delete(List<Integer> premiseIds) {
        var admin = authorizationService.isSuper();
        var userId = authorizationService.getUserId();

        if (!admin)
            premiseRepository.checkOwner(premiseIds, userId);


        // only delete drafts.
        var toBeDeleted = premiseRepository.getPremisesById(premiseIds).stream().filter(p -> p.getState().equals(PremiseState.DRAFT)).map(Premise::getId).toList();

        if (!toBeDeleted.isEmpty()) {
            destinationService.deleteAllDestinationsByPremiseId(toBeDeleted, false);
            premiseRepository.deletePremisesById(toBeDeleted);
        }

    }

    @Transactional
    public void archive(List<Integer> premiseIds) {
        var admin = authorizationService.isSuper();
        var userId = authorizationService.getUserId();

        if (!admin)
            premiseRepository.checkOwner(premiseIds, userId);


        // only archive completed.
        var premisses = premiseRepository.getPremisesById(premiseIds);
        premiseRepository.setStatus(premisses.stream().filter(p -> p.getState().equals(PremiseState.COMPLETED)).map(Premise::getId).toList(), PremiseState.ARCHIVED);
    }

    public ResolvePremiseDTO resolveStatus(List<Integer> premissIds) {
        ResolvePremiseDTO resolveDTO = new ResolvePremiseDTO();
        var drafts = new ArrayList<Integer>();
        var completed = new ArrayList<Integer>();
        var archived = new ArrayList<Integer>();

        var premisses = premiseRepository.getPremisesById(premissIds);

        premisses.forEach(p -> {
            if(PremiseState.DRAFT.equals(p.getState())) drafts.add(p.getId());
            else if(PremiseState.COMPLETED.equals(p.getState())) completed.add(p.getId());
            else if(PremiseState.ARCHIVED.equals(p.getState())) archived.add(p.getId());
        });

        resolveDTO.setCompleted(completed);
        resolveDTO.setDraft(drafts);
        resolveDTO.setArchived(archived);

        return resolveDTO;
    }

    @Transactional
    public List<Integer> duplicate(List<Integer> premissIds) {
        var admin = authorizationService.isSuper();
        var userId = authorizationService.getUserId();

        if (!admin)
            premiseRepository.checkOwner(premissIds, userId);

        var newIds = new ArrayList<Integer>();

        premissIds.forEach(id -> {

            var old = premiseRepository.getPremiseById(id).orElseThrow();


            var existingPremises = premiseRepository.findByMaterialIdAndSupplierId(old.getMaterialId(), old.getSupplierNodeId(), old.getUserSupplierNodeId(), userId);
            var existingDrafts = existingPremises.stream().filter(p -> p.getState().equals(PremiseState.DRAFT)).toList();
            this.delete(existingDrafts.stream().map(Premise::getId).toList());

            var newId = premiseRepository.insert(old.getMaterialId(), old.getSupplierNodeId(), old.getUserSupplierNodeId(), BigDecimal.valueOf(old.getLocation().getLatitude()), BigDecimal.valueOf(old.getLocation().getLongitude()), old.getCountryId(), userId);

            premiseRepository.updateMaterial(Collections.singletonList(newId), old.getHsCode(), old.getTariffRate(), old.getTariffUnlocked());
            premiseRepository.updatePrice(Collections.singletonList(newId), old.getMaterialCost(), old.getFcaEnabled(), old.getOverseaShare());
            premiseRepository.resetPackaging(Collections.singletonList(newId), dimensionTransformer.toDimensionEntity(old), old.getHuStackable(), old.getHuMixable());
            premiseRepository.setPackagingId(newId, old.getPackagingId());

            destinationService.duplicate(old.getId(), newId);

            newIds.add(newId);

        });

        return newIds;
    }
}
