package com.iqser.red.service.persistence.management.v1.processor.service;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import jakarta.transaction.Transactional;

import org.springframework.stereotype.Service;

import com.google.common.collect.Lists;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.FileAttributesGeneralConfigurationEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileAttributeConfigEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileAttributeConfigPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileStatusPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.ImportCsvRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.ImportCsvResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.ProcessingStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.utils.SuppressFBWarnings;
import com.iqser.red.service.search.v1.model.IndexMessageType;
import com.opencsv.CSVParser;
import com.opencsv.CSVParserBuilder;
import com.opencsv.CSVReader;
import com.opencsv.CSVReaderBuilder;
import com.opencsv.exceptions.CsvException;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor
public class FileAttributesManagementService {

    private final FileStatusService fileStatusService;
    private final FileAttributeConfigPersistenceService fileAttributeConfigPersistenceService;
    private final FileStatusPersistenceService fileStatusPersistenceService;
    private final DossierPersistenceService dossierPersistenceService;
    private final IndexingService indexingService;

    public static String UTF_ENCODING = "UTF-8";
    public static String ASCII_ENCODING = "ASCII";
    public static String ISO_ENCODING = "ISO";


    @Transactional
    public ImportCsvResponse importCsv(String dossierId, ImportCsvRequest importCsvRequest) {

        DossierEntity dossier = dossierPersistenceService.getAndValidateDossier(dossierId);

        FileAttributesGeneralConfigurationEntity generalConfiguration = fileAttributeConfigPersistenceService.getFileAttributesGeneralConfiguration(dossier.getDossierTemplateId());
        List<FileAttributeConfigEntity> configuration = fileAttributeConfigPersistenceService.getFileAttributes(dossier.getDossierTemplateId());

        Map<String, FileModel> fileStatusByFilename = fileStatusService.getDossierStatus(dossierId)
                .stream()
                .filter(f -> !f.isSoftOrHardDeleted())
                .collect(Collectors.toMap(FileModel::getFilename, Function.identity()));

        List<List<String>> rows = getCsvRecords(importCsvRequest.getCsvFile(), generalConfiguration.getDelimiter(), generalConfiguration.getEncoding());

        Iterator<List<String>> rowIterator = rows.iterator();

        Map<String, Integer> attributeIdToColumnMapping = new HashMap<>();
        int filenameMappingColumn = -1;
        List<String> headerRow = rowIterator.next();
        for (int i = 0; i < headerRow.size(); i++) {
            if (headerRow.get(i).equals(generalConfiguration.getFilenameMappingColumnHeaderName())) {
                filenameMappingColumn = i;
            } else {
                for (FileAttributeConfigEntity fileAttributeConfig : configuration) {
                    if (headerRow.get(i).equals(fileAttributeConfig.getCsvColumnHeader())) {
                        attributeIdToColumnMapping.put(fileAttributeConfig.getId(), i);
                    }
                }
            }
        }

        if (filenameMappingColumn == -1) {
            throw new BadRequestException("Filename mapping column could not be found!");
        }

        Set<String> affectedFileIds = new HashSet<>();
        while (rowIterator.hasNext()) {
            List<String> row = rowIterator.next();

            String fileStatusMappingColumn = row.get(filenameMappingColumn);

            if (fileStatusMappingColumn == null) {
                continue;
            }

            // TODO this is a primitive solution we need smart name matching
            if (!fileStatusMappingColumn.toLowerCase().endsWith(".pdf")) {
                fileStatusMappingColumn = fileStatusMappingColumn + ".pdf";
            }

            if (!fileStatusByFilename.containsKey(fileStatusMappingColumn.trim())) {
                continue;
            }

            Map<String, String> attributeIdToValue = new HashMap<>();
            for (Map.Entry<String, Integer> entry : attributeIdToColumnMapping.entrySet()) {
                attributeIdToValue.put(entry.getKey(), row.get(entry.getValue()));
            }

            String fileId = fileStatusByFilename.get(fileStatusMappingColumn.trim()).getId();

            fileStatusPersistenceService.setFileAttributes(dossierId, fileId, attributeIdToValue);
            indexingService.addToIndexingQueue(IndexMessageType.UPDATE, null, dossierId, fileId, 2);
            affectedFileIds.add(fileId);
        }

        return new ImportCsvResponse(affectedFileIds, attributeIdToColumnMapping.keySet());
    }


    @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE")
    private List<List<String>> getCsvRecords(byte[] csv, String delimiter, String encoding) {

        CSVParser parser = new CSVParserBuilder().withSeparator(delimiter.charAt(0)).build();

        List<List<String>> records = new ArrayList<>();

        try (CSVReader csvReader = new CSVReaderBuilder(new BufferedReader(new InputStreamReader(new ByteArrayInputStream(csv), parseEncoding(encoding)))).withCSVParser(parser)
                .build()) {
            records.addAll(csvReader.readAll().stream().map(Lists::newArrayList).collect(Collectors.toList()));
        } catch (IOException | CsvException e) {
            log.error("Failed to process CSV", e);
        }

        return records;
    }


    private Charset parseEncoding(String encoding) {

        if (ASCII_ENCODING.equalsIgnoreCase(encoding)) {
            return StandardCharsets.US_ASCII;
        }
        if (ISO_ENCODING.equalsIgnoreCase(encoding)) {
            return StandardCharsets.ISO_8859_1;
        }
        return StandardCharsets.UTF_8;
    }


    @Transactional
    public void setFileAttributes(String dossierId, String fileId, Map<String, String> fileAttributesMap) {

        fileStatusPersistenceService.setFileAttributes(dossierId, fileId, fileAttributesMap);
        fileStatusService.setStatusReprocess(dossierId, fileId, true);
        indexingService.addToIndexingQueue(IndexMessageType.UPDATE, null, dossierId, fileId, 2);
    }

}

