package com.oguerreiro.resilient.service;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

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.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.oguerreiro.resilient.domain.Variable;
import com.oguerreiro.resilient.repository.VariableRepository;
import com.oguerreiro.resilient.service.dto.UnitDTO;
import com.oguerreiro.resilient.service.dto.VariableDTO;
import com.oguerreiro.resilient.service.mapper.UnitMapper;
import com.oguerreiro.resilient.service.mapper.VariableMapper;

/**
 * Service Implementation for managing {@link com.oguerreiro.resilient.domain.Variable}.
 */
@Service
@Transactional
public class VariableService extends AbstractResilientService<VariableDTO, Variable, Long> {
  private final VariableRepository variableRepository;
  private final UnitMapper unitMapper;
  private final VariableMapper variableMapper;

  public VariableService(VariableRepository variableRepository, VariableMapper variableMapper, UnitMapper unitMapper) {
    super(Variable.class, variableRepository, variableMapper);
    this.variableRepository = variableRepository;
    this.unitMapper = unitMapper;
    this.variableMapper = variableMapper;
  }

  public Optional<UnitDTO> getVariableBaseUnit(Long variableId) {
    Variable variable = variableRepository.findById(variableId).orElseThrow();
    UnitDTO unitDTO = this.unitMapper.toDto(variable.getBaseUnit());
    return Optional.of(unitDTO);
  }

  public List<VariableDTO> findVariableForManualInput() {
    log.debug("SERVICE request to findVariableForManualInput");
    //@formatter:off
    return variableRepository.findAllForManualInput()
            .stream()
            .map(variableMapper::toDto).collect(Collectors.toCollection(LinkedList::new));
    //@formatter:on
  }

  @Transactional(readOnly = true)
  public byte[] exportVariables() {
    //Build a Workbook
    try (Workbook workbook = new XSSFWorkbook(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
      Sheet sheet = workbook.createSheet("Input");
      exportVariable(sheet, true, false);

      sheet = workbook.createSheet("Output");
      exportVariable(sheet, false, true);

      workbook.write(out);
      return out.toByteArray();
    } catch (IOException e) {
      throw new RuntimeException("Failed to generate Excel file", e);
    }
  }

  private void exportVariable(Sheet sheet, Boolean input, Boolean output) {
    // Header row
    int c = 0;
    Row headerRow = sheet.createRow(0);
    headerRow.createCell(c++).setCellValue("Código");
    headerRow.createCell(c++).setCellValue("Nome");
    headerRow.createCell(c++).setCellValue("Descrição");

    headerRow.createCell(c++).setCellValue("Unidade Base-Código");
    headerRow.createCell(c++).setCellValue("Unidade Base-Nome");
    headerRow.createCell(c++).setCellValue("Unidade Base-Simbolo");

    headerRow.createCell(c++).setCellValue("Outras Unidades");
    headerRow.createCell(c++).setCellValue("Escala");

    headerRow.createCell(c++).setCellValue("Âmbito-Código");
    headerRow.createCell(c++).setCellValue("Âmbito-Nome");

    headerRow.createCell(c++).setCellValue("Categoria-Código");
    headerRow.createCell(c++).setCellValue("Categoria-Código");

    headerRow.createCell(c++).setCellValue("Formula");

    // Get data with a stream (query cursor)
    try (Stream<Variable> stream = this.variableRepository.streamAll(input, output)) {
      // Loop data
      stream.forEach(variable -> {
        int cc = 0;
        // Write row
        Row row = sheet.createRow(sheet.getLastRowNum() + 1);
        row.createCell(cc++).setCellValue(variable.getCode());
        row.createCell(cc++).setCellValue(variable.getName());
        row.createCell(cc++).setCellValue(variable.getDescription());

        row.createCell(cc++).setCellValue(variable.getBaseUnit().getCode());
        row.createCell(cc++).setCellValue(variable.getBaseUnit().getName());
        row.createCell(cc++).setCellValue(variable.getBaseUnit().getSymbol());

        String codes = variable.getVariableUnits().stream().map(vu -> "\"" + vu.getUnit().getCode() + "\"").collect(
            Collectors.joining("; "));
        row.createCell(cc++).setCellValue(codes);
        row.createCell(cc++).setCellValue(variable.getValueScale());

        row.createCell(cc++).setCellValue(variable.getVariableScope().getCode());
        row.createCell(cc++).setCellValue(variable.getVariableScope().getName());

        row.createCell(cc++).setCellValue(variable.getVariableCategory().getCode());
        row.createCell(cc++).setCellValue(variable.getVariableCategory().getName());

        row.createCell(cc++).setCellValue(variable.getOutputFormula());
      });
    }
  }
}
