package de.avatic.lcc.service.report;

import de.avatic.lcc.dto.generic.NodeDTO;
import de.avatic.lcc.dto.generic.TransportType;
import de.avatic.lcc.dto.report.ReportDTO;
import de.avatic.lcc.dto.report.ReportDestinationDTO;
import de.avatic.lcc.dto.report.ReportEntryDTO;
import de.avatic.lcc.service.bulk.helper.HeaderCellStyleProvider;
import de.avatic.lcc.service.bulk.helper.HeaderGenerator;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.stereotype.Service;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Service
public class ExcelReportingService {


    private final ReportingService reportingService;
    private final HeaderCellStyleProvider headerCellStyleProvider;
    private final HeaderGenerator headerGenerator;

    public ExcelReportingService(ReportingService reportingService, HeaderCellStyleProvider headerCellStyleProvider, HeaderGenerator headerGenerator) {

        this.reportingService = reportingService;
        this.headerCellStyleProvider = headerCellStyleProvider;
        this.headerGenerator = headerGenerator;
    }

    public ByteArrayResource generateExcelReport(Integer materialId, List<Integer> nodeIds, List<Integer> userNodeIds ) {
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            var reports = reportingService.getReport(materialId, nodeIds, userNodeIds);

            Workbook workbook = new XSSFWorkbook();
            Sheet sheet = workbook.createSheet("report");

            CellStyle headerStyle = headerCellStyleProvider.createHeaderCellStyle(workbook);

            ArrayList<String> headers = new ArrayList<>();
            headers.add("");
            headers.addAll(reports.stream().map(ReportDTO::getSupplier).map(NodeDTO::getName).toList());

            headerGenerator.generateHeader(sheet, headers.toArray(String[]::new), headerStyle);

            List<ReportFlattener> flatterers = reports.stream().map(ReportFlattener::new).toList();


            while(true) {
                boolean hasData = false;
                boolean headerWritten = false;

                var row = sheet.createRow(sheet.getLastRowNum() + 1);
                int cellIdx = 1 /* 0 is the header column */;
                for(ReportFlattener flattener : flatterers) {

                    Cell cell = row.createCell(cellIdx);


                    if(flattener.hasData(row.getRowNum())) {
                        if(!headerWritten) {
                            row.createCell(0).setCellValue(flattener.getHeader(row.getRowNum()));
                            headerWritten = true;
                        }
                        cell.setCellValue(flattener.getCell(row.getRowNum()));
                        hasData = true;
                    }

                    cellIdx++;
                }

                if(!hasData) break;
            }

            headerGenerator.fixWidth(sheet, headers.toArray(String[]::new));

            // Return the Excel file as an InputStreamSource
            workbook.write(outputStream);
            return new ByteArrayResource(outputStream.toByteArray());
        } catch (
                IOException e) {
            throw new RuntimeException("Failed to generate template", e);
        }
    }

    private static class ReportFlattener {

        private static final String SUPPLIER_NAME = "Supplier";
        private static final String SUPPLIER_ADDRESS = "Address";

        private static final String DESTINATION_NAME = "Destination";
        private static final String DESTINATION_ADDRESS = "Address";

        private static final String DESTINATION_QUANTITY = "Annual quantity";
        private static final String DESTINATION_HS_CODE = "HS code";
        private static final String DESTINATION_TARIFF_RATE = "Tariff rate";
        private static final String DESTINATION_OVERSHARE = "Oversea share";
        private static final String DESTINATION_AIR_FREIGHT_SHARE = "Air freight share";
        private static final String DESTINATION_TRANSPORT_TIME = "Transport time";
        private static final String DESTINATION_SAFETY_STOCK = "Safety stock";

        private static final String DESTINATION_WIDTH = "HU Width";
        private static final String DESTINATION_HEIGHT = "HU Height";
        private static final String DESTINATION_LENGTH = "HU Length";
        private static final String DESTINATION_WEIGHT = "HU Weight";
        private static final String DESTINATION_HU_UNIT_COUNT = "HU Unit count";

        private static final String DESTINATION_WEIGHT_UNIT = "HU Weight unit";
        private static final String DESTINATION_DIMENSION_UNIT = "HU Unit";

        private static final String DESTINATION_CONTAINER_LAYER = "Container layers";
        private static final String DESTINATION_CONTAINER_UNIT_COUNT = "Container unit count";
        private static final String DESTINATION_CONTAINER_UTILIZATION = "Container utilization";
        private static final String DESTINATION_CONTAINER_TYPE = "Container type";
        private static final String DESTINATION_CONTAINER_WEIGHT_EXCEEDED = "Container weight exceeded";
        private static final String DESTINATION_CONTAINER_RATE = "Container rate";
        private static final String DESTINATION_MIXED = "Mixed";


        private final ReportDTO report;
        private final List<String> data = new ArrayList<>();
        private final List<String> dataHeader = new ArrayList<>();

        public ReportFlattener(ReportDTO report) {
            this.report = report;
            flatten();
        }

        private void flatten() {

            addData(SUPPLIER_NAME, report.getSupplier().getName());
            addData(SUPPLIER_ADDRESS, report.getSupplier().getAddress());

            // TODO: hardcoded (otherwise values wont match
            report.getCost().keySet().forEach(costName -> addData(costName, report.getCost().get(costName)));
            report.getRisk().keySet().forEach(riskName -> addData(riskName, report.getRisk().get(riskName)));

            report.getDestinations().forEach(this::flattenDestination);
        }

        private void flattenDestination(ReportDestinationDTO destination) {

            var hasMainRun = destination.getSections().stream().anyMatch(s -> s.getTransportType().equals(TransportType.RAIL) || s.getTransportType().equals(TransportType.SEA));

            addData(DESTINATION_NAME, destination.getDestination().getName());
            addData(DESTINATION_ADDRESS, destination.getDestination().getAddress());

            addData(DESTINATION_QUANTITY, destination.getAnnualQuantity().toString());
            addData(DESTINATION_HS_CODE, destination.getHsCode());
            addData(DESTINATION_TARIFF_RATE, destination.getTariffRate().toString());
            addData(DESTINATION_OVERSHARE, destination.getOverseaShare().toString());

            if(destination.getAirFreightShare() != null)
                addData(DESTINATION_AIR_FREIGHT_SHARE, destination.getAirFreightShare().toString());
            addData(DESTINATION_TRANSPORT_TIME, destination.getTransportTime().toString());
            addData(DESTINATION_SAFETY_STOCK, destination.getSafetyStock().toString());

            addData(DESTINATION_WIDTH, destination.getWidth().toString());
            addData(DESTINATION_HEIGHT, destination.getHeight().toString());
            addData(DESTINATION_LENGTH, destination.getLength().toString());
            addData(DESTINATION_DIMENSION_UNIT, destination.getDimensionUnit().toString());
            addData(DESTINATION_WEIGHT, destination.getWeight().toString());
            addData(DESTINATION_WEIGHT_UNIT, destination.getWeightUnit().toString());
            addData(DESTINATION_HU_UNIT_COUNT, destination.getHuUnitCount().toString());
            addData(DESTINATION_CONTAINER_LAYER, !hasMainRun ? "-" : destination.getLayer().toString());

            addData(DESTINATION_CONTAINER_UNIT_COUNT, !hasMainRun ? "-" : destination.getUnitCount().toString());
            addData(DESTINATION_CONTAINER_UTILIZATION, !hasMainRun ? "-" : destination.getUtilization().toString());
            addData(DESTINATION_CONTAINER_TYPE, !hasMainRun ? "-" : destination.getType().toString());
            addData(DESTINATION_CONTAINER_WEIGHT_EXCEEDED, !hasMainRun ? "-" : destination.getWeightExceeded().toString());
            addData(DESTINATION_CONTAINER_RATE, !hasMainRun ? "-" : destination.getRate().toString());
            addData(DESTINATION_MIXED, !hasMainRun ? "-" : destination.getMixed().toString());

        }

        private void addData(String header, String data) {
            this.dataHeader.add(header);
            this.data.add(data);
        }

        private void addData(String header, ReportEntryDTO data) {
            this.dataHeader.add(header);
            this.data.add(data.getTotal().toString()); // + " (" + data.getPercentage().doubleValue()*100 + "%)");
        }

        public String getCell(int rowIdx) {
            return data.get(rowIdx);
        }

        public String getHeader(int rowIdx) {
            return dataHeader.get(rowIdx);
        }

        public boolean hasData(int index) {
            return data.size() > index;
        }
    }

}
