/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.bytesw.bytebot.service;

import com.amazonaws.services.dynamodbv2.xspec.S;
import com.bytesw.bytebot.http.FileValidationResponse;
import com.bytesw.bytebot.http.FileValidationResult;
import com.bytesw.bytebot.http.enums.ValidationStatusEnum;
import com.bytesw.bytebot.model.QuestionFile;
import com.bytesw.bytebot.model.enums.FrequentQuestionStatusEnum;
import com.bytesw.bytebot.repository.QuestionFileRepository;
import com.bytesw.bytebot.utils.Utilities;

import java.io.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

import com.bytesw.xdf.exception.NotFoundException;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.io.FilenameUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.RichTextString;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

@Service
@Transactional
@Log4j2
public class FileManagementService {

    String[] validExtensions = new String[]{"xls", "xlsx", "csv"};
    String[] fileHeaders = new String[]{"Class", "Question", "Answer"};
    
    @Autowired
    private QuestionFileRepository questionFileRepository;

    public FileValidationResponse saveFile(String uuid, MultipartFile file) throws IOException {
        FileValidationResponse response = new FileValidationResponse();
        FileValidationResult result = new FileValidationResult();
        //@Validaciones: Tipo de Extension
        boolean acceptedExtension = false;
        String fileExtension = FilenameUtils.getExtension(file.getOriginalFilename());
        for (String validExension : validExtensions) {
            if (validExension.equalsIgnoreCase(fileExtension.trim())) {
                acceptedExtension = true;
                break;
            }
        }

        if (!acceptedExtension) {
            log.info("La extension [" + fileExtension + "] es inválida");
            result.setStatus(ValidationStatusEnum.INCOMPATIBLE_EXTENSION);
            response.setFileValidationResult(result);
            return response;
        }

        switch (fileExtension) {
            case "csv":
                response = validateCsv(uuid, file);
                break;
            case  "xls":
            case "xlsx":
                response = validateAndSaveFile(uuid, file);
                break;
        }
        return response;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public FileValidationResponse validateAndSaveFile(String uuid, MultipartFile file) throws IOException {

        FileValidationResponse response = new FileValidationResponse();
        FileValidationResult result = new FileValidationResult();
        
        //@Validaciones: De Estructura del Excel (Cabeceras)
        byte[] fileData = file.getBytes();
        log.debug("File Size: [" + file.getSize() + "]");
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(fileData);
        Workbook workbook = WorkbookFactory.create(byteArrayInputStream);
        Sheet sheet = workbook.getSheetAt(0);
        Row headerRow = sheet.getRow(0);

        //@HU: Llenando los headers encontrados
        Map<String, Integer> headersXPosition = new HashMap<>();
        List<String> headersList = new ArrayList<>();

        //Cabeceras vacias
        if (headerRow == null) {
            Map<String, String> headerErrorMap = new HashMap<>();
            for (String key : fileHeaders) {
                headerErrorMap.put(key, "HEADER.NOT.FOUND.FIRST.ROW");
            }
            result.setHeadersErrorMap(headerErrorMap);
            result.setStatus(ValidationStatusEnum.HEADER_ERROR);
            response.setFileValidationResult(result);
            return response;
        }

        Iterator<Cell> cells = headerRow.cellIterator();
        int position = 0;
        while (cells.hasNext()) {
            Cell cell = (Cell) cells.next();
            RichTextString value = cell.getRichStringCellValue();
            headersXPosition.put(value.getString(), position++);
            headersList.add(value.getString());
        }

        Map<String, String> headerErrorMap = validateHeaders(headersList);

        if (!headerErrorMap.isEmpty()) {
            log.info("Se encontraron los siguientes errores: " + headerErrorMap.values());
            result.setStatus(ValidationStatusEnum.HEADER_ERROR);
            result.setHeadersErrorMap(headerErrorMap);
            response.setFileValidationResult(result);
            return response;
        }

        //@Validaciones: De Contenido(Todos deben tener valor)
        int rowNumber = 0;
        int rowError = 0;
        Iterator<Row> rowIterator = sheet.iterator();
        Map<Integer, Object> recordsRowErrorMap = new HashMap<>();

        while (rowIterator.hasNext()) {
            Row row = (Row) rowIterator.next();
            if (rowNumber == 0) {
                rowNumber++;
                continue;
            }

            log.debug("Row number: [" + rowNumber + "]");
            Iterator<String> columnsIterator = headersXPosition.keySet().iterator();
            Map<String, Object> recordsXRow = new HashMap<>();

            while (columnsIterator.hasNext()) {
                String name = columnsIterator.next();
                log.debug("Read columnName [" + name + "]");

                if (!isFileHeader(name.toUpperCase().trim())) {
                    log.debug("Skip columnName [" + name + "] - not required in format");
                    continue;
                }

                Cell cell = row.getCell(headersXPosition.get(name));

                if (cell == null) {
                    recordsXRow.put(name, null);
                    continue;
                }

                CellType cellType = row.getCell(headersXPosition.get(name)).getCellType();

                switch (cellType) {
                    case STRING:
                        RichTextString cellValue = row.getCell(headersXPosition.get(name)).getRichStringCellValue();
                        String value = (cellValue != null ? cellValue.getString().trim() : null);
                        recordsXRow.put(name, value);
                        break;

                    case NUMERIC:
                        if (DateUtil.isCellDateFormatted(row.getCell(headersXPosition.get(name)))) {
                            Date date = row.getCell(headersXPosition.get(name)).getDateCellValue();
                            recordsXRow.put(name, Utilities.convertDateToString(date, "dd-MMM-yyyy"));
                        } else {
                            double numericVal = row.getCell(headersXPosition.get(name)).getNumericCellValue();
                            recordsXRow.put(name, numericVal);
                        }
                        break;

                    case BLANK:
                        recordsXRow.put(name, null);
                        break;

                    default:
                        log.info(" CellType not supported yet [" + row.getCell(headersXPosition.get(name)).getCellType() + "]");
                        log.info(" Value [" + row.getCell(headersXPosition.get(name)).getStringCellValue() + "]");
                }
            }

            Map<String, String> recordsErrorMap = validateRecordsXRow(recordsXRow);

            if (!recordsErrorMap.isEmpty()) {
                if (recordsErrorMap.size() != fileHeaders.length){
                    recordsRowErrorMap.put(rowNumber + 1, recordsErrorMap);
                } else {
                    Map<String, String> rowErrorResult = new HashMap<>();
                    rowErrorResult.put("ROW.NULL", "ROW.DATA.NULL");
                    recordsRowErrorMap.put(rowNumber, rowErrorResult);
                }
            }

            rowNumber++;
        }

        if (!recordsRowErrorMap.isEmpty()) {
            log.info("Hay errores en el contenido del archivo: " + recordsRowErrorMap);
            result.setStatus(ValidationStatusEnum.CONTENT_ERROR);
            result.setRecordsErrorMap(recordsRowErrorMap);
            response.setFileValidationResult(result);
            return response;
        }

        //@HU: Proceder a guardar en BD el archivo subido
        result.setStatus(ValidationStatusEnum.OK);
        
        QuestionFile questionFileBD = new QuestionFile();
        
        questionFileBD.setUuid(uuid);
        questionFileBD.setName(file.getOriginalFilename());
        questionFileBD.setSize(file.getSize());
        questionFileBD.setUploadDate(LocalDateTime.now());
        questionFileBD.setData(fileData);
        
        questionFileBD = questionFileRepository.save(questionFileBD);
        
        response.setId(questionFileBD.getId());
        response.setUuid(questionFileBD.getUuid());
        response.setFileName(questionFileBD.getName());
        response.setStatus(FrequentQuestionStatusEnum.PENDING_SYNCHRONIZED.getName());
        response.setUploadDate(getUploadDate(questionFileBD.getUploadDate()));
        
        response.setFileValidationResult(result);
        
        return response;
    }

    private String getUploadDate(LocalDateTime uploadDate) {
        if (uploadDate != null) {
            return uploadDate.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"));
        }
        return null;
    }

    public FileValidationResponse validateCsv(String uuid, MultipartFile file) throws IOException{
        FileValidationResponse response = new FileValidationResponse();
        FileValidationResult result = new FileValidationResult();
        List<List<String>> rows = new ArrayList<>();
        try {
            String line;
            InputStream is = file.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            Map<String, String> headerErrorMap = new HashMap<>();
            Map<Integer, Object>  recordsRowErrorMap = new HashMap<>();
            int rowData = 1;
            while ((line = br.readLine()) != null) {
                List<String> row = new ArrayList<>();
                String[] data = line.split(";");

                if (data.length <= 1) {
                    headerErrorMap.put("FORMAT.SEPARATION", "ERROR.SEPARATION.FORMAT");
                }

                if (data.length < fileHeaders.length) {
                    Map<String, String> recordsErrorMap = new HashMap<>();
                    recordsErrorMap.put(ValidationStatusEnum.DATA_INCOMPLETE.getName(), "DATA.INCOMPLETE");
                    recordsRowErrorMap.put(rowData, recordsErrorMap);
                }

                for (String dato : data) {
                    if (validateData(dato)) {
                        headerErrorMap.put("FORMAT.DATA", "WRONG.DATA.INCORRECT");
                    }
                    row.add(dato);
                }
                rowData++;
                rows.add(row);
            }
            if(!headerErrorMap.isEmpty()) {
                result.setHeadersErrorMap(headerErrorMap);
                result.setStatus(ValidationStatusEnum.FORMAT_INCORRECT);
                response.setFileValidationResult(result);
                return response;
            }

            if (!recordsRowErrorMap.isEmpty()) {
                log.info("Hay errores en el contenido del archivo: " + recordsRowErrorMap);
                result.setStatus(ValidationStatusEnum.DATA_INCOMPLETE);
                result.setRecordsErrorMap(recordsRowErrorMap);
                response.setFileValidationResult(result);
                return response;
            }

            List<String> headersList = new ArrayList<>();
            for (String header: rows.get(0)) {
                headersList.add(header.replace('"', ' '));
            }

            headerErrorMap = validateHeaders(headersList);
            if (!headerErrorMap.isEmpty()) {
                result.setHeadersErrorMap(headerErrorMap);
                result.setStatus(ValidationStatusEnum.HEADER_ERROR);
                response.setFileValidationResult(result);
                return response;
            }
            rows.remove(0);
        } catch (IOException e) {
            System.err.println(e.getMessage());
        }

        int rowNumber = 0;
        Map<Integer, Object> recordsRowErrorMap = new HashMap<>();
        for (List<String> item: rows) {
            Map<String, Object> recordsXRow = new HashMap<>();

            Map<String, String> recordsErrorMap = validateRecordsXRow(recordsXRow);

            if (!recordsErrorMap.isEmpty()) {
                if (recordsErrorMap.size() != fileHeaders.length){
                    recordsRowErrorMap.put(rowNumber + 1, recordsErrorMap);
                }
            }

            rowNumber++;
        }

        if (!recordsRowErrorMap.isEmpty()) {
            log.info("Hay errores en el contenido del archivo: " + recordsRowErrorMap);
            result.setStatus(ValidationStatusEnum.CONTENT_ERROR);
            result.setRecordsErrorMap(recordsRowErrorMap);
            response.setFileValidationResult(result);
            return response;
        }

        result.setStatus(ValidationStatusEnum.OK);
        QuestionFile questionFileBD = new QuestionFile();

        questionFileBD.setUuid(uuid);
        questionFileBD.setName(file.getOriginalFilename());
        questionFileBD.setSize(file.getSize());
        questionFileBD.setUploadDate(LocalDateTime.now());
        questionFileBD.setData(file.getBytes());

        questionFileBD = questionFileRepository.save(questionFileBD);
        response.setId(questionFileBD.getId());
        response.setUuid(questionFileBD.getUuid());
        response.setFileName(questionFileBD.getName());
        response.setStatus(FrequentQuestionStatusEnum.PENDING_SYNCHRONIZED.getName());
        response.setUploadDate(getUploadDate(questionFileBD.getUploadDate()));

        response.setFileValidationResult(result);

        return response;
    }

    private boolean validateData (String data) {
        if (data.charAt(0) == '"' && data.charAt(data.length()-1) == '"') {
            return false;
        }
        return true;
    }

    private Map<String, String> validateHeaders(List<String> headersList) {

        Map<String, Integer> headerOcurrencesMap = new HashMap<>();
        Map<String, String> headerErrorMap = new HashMap<>();
        Map<String, Integer> headerOrganized = new HashMap<>();
        int index = 0;

        for (String fileHeader : fileHeaders) {
            String keyHeader = fileHeader.toUpperCase().trim();
            if (headerOcurrencesMap.get(keyHeader) == null) {
                headerOcurrencesMap.put(keyHeader, 0);
            }

            for (String header : headersList) {
                if (header.toUpperCase().trim().equals(keyHeader)) {
                    int ocurrences = headerOcurrencesMap.get(keyHeader);
                    headerOcurrencesMap.put(keyHeader, ocurrences + 1);
                    headerOrganized.put(keyHeader, index);
                }
                index++;
            }
            index = 0;
        }


        for (String keyHeader : headerOcurrencesMap.keySet()) {
            int ocurrences = headerOcurrencesMap.get(keyHeader);
            if (ocurrences == 0) {
                headerErrorMap.put(keyHeader, "HEADER.NOT.FOUND");
            } else if (ocurrences >= 1) {
                if (ocurrences > 1){
                    headerErrorMap.put(keyHeader, "HEADER.REPETEAD");
                } else if (!keyHeader.equalsIgnoreCase(fileHeaders[headerOrganized.get(keyHeader)])) {
                    headerErrorMap.put(keyHeader, "HEADER.NOT.ORGANIZED");
                }
            }
            index ++;
        }


        return headerErrorMap;
    }

    private Map<String, String> validateRecordsXRow(Map<String, Object> recordsXRow) {
        Map<String, String> recordsErrorMap = new HashMap<>();

        for (String headerName : recordsXRow.keySet()) {

            String keyHeader = headerName.toUpperCase().trim();
            if (!isFileHeader(keyHeader)) {
                continue;
            }
            if (recordsErrorMap.get(keyHeader) != null) {
                continue;
            }

            if (recordsXRow.get(headerName) == null) {
                recordsErrorMap.put(keyHeader, "NOT.FOUND.VALUE");
            } else {
                Object recordRowValue = recordsXRow.get(headerName);

                if (recordRowValue instanceof String) {
                    String record = (String) recordRowValue;
                    if (record.trim().equals("")) {
                        recordsErrorMap.put(keyHeader, "EMPTY.VALUE");
                    }
                }
            }
        }

        return recordsErrorMap;
    }

    private boolean isFileHeader(String keyHeader) {
        boolean isFileHeader = false;
        for (String fileHeader : fileHeaders) {
            if (keyHeader.equals(fileHeader.toUpperCase().trim())) {
                isFileHeader = true;
                break;
            }
        }
        return isFileHeader;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void deleteGoalForAction(Long id) {
        Optional<QuestionFile> file = questionFileRepository.findById(id);

        if (!file.isPresent()) {
            throw new NotFoundException("File no econtrado.");
        }

        questionFileRepository.delete(file.get());
    }

}
