package com.kapdion.omds.productdefinitions.calculate;

import at.vvo.omds.helpers.GuiProdukt;
import at.vvo.omds.helpers.RDFHelper;
import at.vvo.omds.helpers.TreeHelper;
import at.vvo.omds.types.omds3.r2025_05.common.*;
import javafx.scene.control.TreeItem;
import org.eclipse.rdf4j.model.*;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.Rio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;


public class CalculateService {
    Logger log = LoggerFactory.getLogger(CalculateService.class);
    RDFHelper rdfHelper = new RDFHelper();
    public String buildResponse(String calculateRequest) throws Exception {
        VerkaufsproduktType vp = rdfHelper.calculateRequestToVerkaufsprodukt(calculateRequest);

        vp = validateRequest(vp);

        vp.getAttribute().removeIf(a -> a.getBezeichnung().equals("Praemie"));
        if (vp.getMeldungen().isEmpty()) {
            calculate(vp);
        }

        TreeHelper treeHelper = new TreeHelper();
        TreeItem<GuiProdukt> vpTree = new TreeItem<>(new GuiProdukt(vp));
        treeHelper.produktToTree(vp, vpTree);

        Model response = rdfHelper.createRdfModel(vpTree);;

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Rio.write(response, baos, RDFFormat.JSONLD);
        return baos.toString();
    }

    private VerkaufsproduktType validateRequest(VerkaufsproduktType vp) throws IOException {
//        pruefeVUNr(calculateRequest.getVUNr());
        pruefePlausis(vp);
        pruefeVerkaufsoffen(vp);
        pruefeWerte(vp);
        pruefeRisikoobjekt(vp);
        pruefeErgaentzungen(vp);

        return vp;
    }

    private VerkaufsproduktType pruefePlausis(VerkaufsproduktType vp) throws IOException {

//        File file = new File("src/main/resources/data/plausis");
//
//        Repository repo = new SailRepository(new MemoryStore());
//        repo.init();
//
//        try (RepositoryConnection conn = repo.getConnection()) {
//            TreeHelper treeHelper = new TreeHelper();
//            TreeItem<GuiProdukt> vpTree = new TreeItem<>(new GuiProdukt(vp));
//            treeHelper.produktToTree(vp, vpTree);
//            Model model = rdfHelper.createRdfModel(vpTree);
//            conn.add(model);
//
//            for (File plausi : file.listFiles()) {
//                var lines = Files.lines(plausi.toPath()).map(String::trim).toList();
//
//                String art = lines.stream().filter(line -> line.startsWith("#art:")).findFirst().get().substring(5);
//                String query = lines.stream().filter(line -> !line.startsWith("#") && !line.isEmpty()).collect(Collectors.joining("\n"));
//
//
//                if (art.equals("update")){
//                    conn.prepareUpdate(query).execute();
//
//                    model.clear();
//                    conn.getStatements(null, null, null).forEach(model::add);
//                }else if (art.equals("graph")){
//                    GraphQuery q = conn.prepareGraphQuery(query);
//                    Model validatedModel = QueryResults.asModel(q.evaluate());
//                    model.addAll(validatedModel);
//                }
//
//            }
//            ByteArrayOutputStream baos = new ByteArrayOutputStream();
//            Rio.write(model, baos, RDFFormat.JSONLD);
//
//            return rdfHelper.calculateRequestToVerkaufsprodukt(baos.toString());
//        } catch(Exception ignored) {
//            System.out.println(ignored.getMessage());
//        }
        return vp;
    }

    private void pruefeWerte(VerkaufsproduktType verkaufsprodukt) {
    }

    private void pruefeErgaentzungen(VerkaufsproduktType verkaufsprodukt) {
    }

    private void pruefeRisikoobjekt(VerkaufsproduktType verkaufsprodukt) {
    }

    private void pruefeVerkaufsoffen(VerkaufsproduktType verkaufsprodukt) {
    }

    private void pruefeVUNr(String vuNr) {
    }

    private void calculate(VerkaufsproduktType vp) {
        AttributDezimalType attInt = new AttributDezimalType();

        BigDecimal jahresNettoPraemie;
        BigDecimal jahresNettoPraemieKasko = new BigDecimal(0);
        BigDecimal jahresNettoPraemieHp = new BigDecimal(0);

        for (ProduktbausteinType baustein : vp.getBausteine()) {
            if (baustein.getTyp().toLowerCase().contains("haftpflicht")) {
                BigDecimal tmp = calculateHp(baustein);
                if (tmp.compareTo(BigDecimal.valueOf(0)) < 1) {
                    addMeldungToProdukt(vp, "Fehler bei der Berechnung der Prämie für KFZ-Haftpflicht", 1);
                    System.out.println(vp.getBezeichnung());
                }else {
                    jahresNettoPraemieHp = jahresNettoPraemieHp.add(tmp);
                }
            }else if (baustein.getTyp().toLowerCase().contains("kasko")) {
                BigDecimal tmp = calculateKasko(baustein);

                if (tmp.compareTo(BigDecimal.valueOf(0)) < 1) {
                    addMeldungToProdukt(vp, "Fehler bei der Berechnung der Prämie für Kasko", 1);
                }else {
                    jahresNettoPraemieHp = jahresNettoPraemieHp.add(tmp);
                }
            }
        }

        jahresNettoPraemie = jahresNettoPraemieHp.add(jahresNettoPraemieKasko);

        attInt.setId(BigInteger.valueOf(4));
        attInt.setBezeichnung("Praemie");
        attInt.setValue(jahresNettoPraemie.setScale(2, RoundingMode.HALF_UP));
        attInt.setMax(BigDecimal.valueOf(999999));
        attInt.setMin(BigDecimal.valueOf(1));
        attInt.setDefault(BigDecimal.valueOf(10));
        attInt.setPflichtfeld(false);
        attInt.setAenderbar(false);

        vp.getAttribute().removeIf(a -> a.getBezeichnung().equals("Praemie"));
        vp.getAttribute().add(attInt);

    }

    private Bundesland getBundeslandFromKennzeichen(String kennzeichen) {
        kennzeichen = kennzeichen.toUpperCase().trim().substring(0, kennzeichen.indexOf("-"));

        HashMap< String, Bundesland> kennzeichenMap = new HashMap<>();

        kennzeichenMap.put("AM", Bundesland.Niederoesterreich);
        kennzeichenMap.put("BL", Bundesland.Niederoesterreich);
        kennzeichenMap.put("BN", Bundesland.Niederoesterreich);
        kennzeichenMap.put("GD", Bundesland.Niederoesterreich);
        kennzeichenMap.put("GF", Bundesland.Niederoesterreich);
        kennzeichenMap.put("HL", Bundesland.Niederoesterreich);
        kennzeichenMap.put("HO", Bundesland.Niederoesterreich);
        kennzeichenMap.put("KG", Bundesland.Niederoesterreich);
        kennzeichenMap.put("KO", Bundesland.Niederoesterreich);
        kennzeichenMap.put("KR", Bundesland.Niederoesterreich);
        kennzeichenMap.put("KS", Bundesland.Niederoesterreich);
        kennzeichenMap.put("LF", Bundesland.Niederoesterreich);
        kennzeichenMap.put("MD", Bundesland.Niederoesterreich);
        kennzeichenMap.put("ME", Bundesland.Niederoesterreich);
        kennzeichenMap.put("MI", Bundesland.Niederoesterreich);
        kennzeichenMap.put("NK", Bundesland.Niederoesterreich);
        kennzeichenMap.put("P", Bundesland.Niederoesterreich);
        kennzeichenMap.put("PL", Bundesland.Niederoesterreich);
        kennzeichenMap.put("SB", Bundesland.Niederoesterreich);
        kennzeichenMap.put("SW", Bundesland.Niederoesterreich);
        kennzeichenMap.put("TU", Bundesland.Niederoesterreich);
        kennzeichenMap.put("WB", Bundesland.Niederoesterreich);
        kennzeichenMap.put("WN", Bundesland.Niederoesterreich);
        kennzeichenMap.put("WT", Bundesland.Niederoesterreich);
        kennzeichenMap.put("WY", Bundesland.Niederoesterreich);
        kennzeichenMap.put("ZT", Bundesland.Niederoesterreich);

        kennzeichenMap.put("BR", Bundesland.Oberoesterreich);
        kennzeichenMap.put("EF", Bundesland.Oberoesterreich);
        kennzeichenMap.put("FR", Bundesland.Oberoesterreich);
        kennzeichenMap.put("GM", Bundesland.Oberoesterreich);
        kennzeichenMap.put("GR", Bundesland.Oberoesterreich);
        kennzeichenMap.put("KI", Bundesland.Oberoesterreich);
        kennzeichenMap.put("L", Bundesland.Oberoesterreich);
        kennzeichenMap.put("LL", Bundesland.Oberoesterreich);
        kennzeichenMap.put("PE", Bundesland.Oberoesterreich);
        kennzeichenMap.put("RI", Bundesland.Oberoesterreich);
        kennzeichenMap.put("RO", Bundesland.Oberoesterreich);
        kennzeichenMap.put("SD", Bundesland.Oberoesterreich);
        kennzeichenMap.put("SE", Bundesland.Oberoesterreich);
        kennzeichenMap.put("SR", Bundesland.Oberoesterreich);
        kennzeichenMap.put("UU", Bundesland.Oberoesterreich);
        kennzeichenMap.put("VB", Bundesland.Oberoesterreich);
        kennzeichenMap.put("WE", Bundesland.Oberoesterreich);
        kennzeichenMap.put("WL", Bundesland.Oberoesterreich);

        kennzeichenMap.put("W", Bundesland.Wien);

        kennzeichenMap.put("BA", Bundesland.Steiermark);
        kennzeichenMap.put("BM", Bundesland.Steiermark);
        kennzeichenMap.put("DL", Bundesland.Steiermark);
        kennzeichenMap.put("G", Bundesland.Steiermark);
        kennzeichenMap.put("GB", Bundesland.Steiermark);
        kennzeichenMap.put("GU", Bundesland.Steiermark);
        kennzeichenMap.put("HF", Bundesland.Steiermark);
        kennzeichenMap.put("LB", Bundesland.Steiermark);
        kennzeichenMap.put("LE", Bundesland.Steiermark);
        kennzeichenMap.put("LI", Bundesland.Steiermark);
        kennzeichenMap.put("LN", Bundesland.Steiermark);
        kennzeichenMap.put("MT", Bundesland.Steiermark);
        kennzeichenMap.put("MU", Bundesland.Steiermark);
        kennzeichenMap.put("SO", Bundesland.Steiermark);
        kennzeichenMap.put("VO", Bundesland.Steiermark);
        kennzeichenMap.put("WZ", Bundesland.Steiermark);

        kennzeichenMap.put("I", Bundesland.Tirol);
        kennzeichenMap.put("IL", Bundesland.Tirol);
        kennzeichenMap.put("IM", Bundesland.Tirol);
        kennzeichenMap.put("KB", Bundesland.Tirol);
        kennzeichenMap.put("KU", Bundesland.Tirol);
        kennzeichenMap.put("LA", Bundesland.Tirol);
        kennzeichenMap.put("LZ", Bundesland.Tirol);
        kennzeichenMap.put("RE", Bundesland.Tirol);
        kennzeichenMap.put("SZ", Bundesland.Tirol);

        kennzeichenMap.put("HA", Bundesland.Salzburg);
        kennzeichenMap.put("JO", Bundesland.Salzburg);
        kennzeichenMap.put("S", Bundesland.Salzburg);
        kennzeichenMap.put("SL", Bundesland.Salzburg);
        kennzeichenMap.put("TA", Bundesland.Salzburg);
        kennzeichenMap.put("ZE", Bundesland.Salzburg);

        kennzeichenMap.put("FE", Bundesland.Kaernten);
        kennzeichenMap.put("HE", Bundesland.Kaernten);
        kennzeichenMap.put("K", Bundesland.Kaernten);
        kennzeichenMap.put("KL", Bundesland.Kaernten);
        kennzeichenMap.put("SP", Bundesland.Kaernten);
        kennzeichenMap.put("SV", Bundesland.Kaernten);
        kennzeichenMap.put("VI", Bundesland.Kaernten);
        kennzeichenMap.put("VK", Bundesland.Kaernten);
        kennzeichenMap.put("VL", Bundesland.Kaernten);
        kennzeichenMap.put("WO", Bundesland.Kaernten);

        kennzeichenMap.put("B", Bundesland.Vorarlberg);
        kennzeichenMap.put("BZ", Bundesland.Vorarlberg);
        kennzeichenMap.put("DO", Bundesland.Vorarlberg);
        kennzeichenMap.put("FK", Bundesland.Vorarlberg);

        kennzeichenMap.put("E", Bundesland.Burgenland);
        kennzeichenMap.put("EU", Bundesland.Burgenland);
        kennzeichenMap.put("GS", Bundesland.Burgenland);
        kennzeichenMap.put("JE", Bundesland.Burgenland);
        kennzeichenMap.put("MA", Bundesland.Burgenland);
        kennzeichenMap.put("ND", Bundesland.Burgenland);
        kennzeichenMap.put("OP", Bundesland.Burgenland);
        kennzeichenMap.put("OW", Bundesland.Burgenland);

        return kennzeichenMap.getOrDefault(kennzeichen, Bundesland.Unbekannt);
    }

    private BigDecimal getBundeslandMultiplikator(Bundesland bundesland) {
        return switch (bundesland) {
            case Niederoesterreich -> BigDecimal.valueOf(1.1);
            case Oberoesterreich -> BigDecimal.valueOf(1.09);
            case Wien -> BigDecimal.valueOf(1.08);
            case Steiermark -> BigDecimal.valueOf(1.07);
            case Tirol -> BigDecimal.valueOf(1.06);
            case Salzburg -> BigDecimal.valueOf(1.05);
            case Kaernten -> BigDecimal.valueOf(1.04);
            case Vorarlberg -> BigDecimal.valueOf(1.03);
            case Burgenland -> BigDecimal.valueOf(1.02);
            case Unbekannt -> BigDecimal.valueOf(2);
        };
    }

    private BigDecimal calculateHp(ProduktbausteinType vp) {
        log.atDebug().log("----- calculateHp -----");
        //a ... Versicherungssumme
        BigDecimal a = ((AttributDezimalType)(vp.getAttribute().stream().filter(at -> at.getBezeichnung()
                .equalsIgnoreCase("versicherungssumme")).toList().getFirst())).getValue();
        //b ... Leistung des Fahrzeuges
        int b = 0;

        //c ... Bundesland multiplikator
        BigDecimal c = BigDecimal.valueOf(0);

        for (VersichertesInteresseType fa : vp.getVersicherteObjekte()){

            int tempB = ((FahrzeugType)fa).getLeistung();
            BigDecimal tempC = getBundeslandMultiplikator(getBundeslandFromKennzeichen(((FahrzeugType)fa).getPolKennz()));
            if (tempB > b && tempC.compareTo(c) >= 0){
                b = tempB;
                c = tempC;
            }
        }

        log.atDebug().log("Eingangsvariablen: ");
        log.atDebug().log("a: " + a);
        log.atDebug().log("b: " + b);
        log.atDebug().log("c: " + c);
        log.atDebug().log("");

        if (a != null && b != 0 && !c.equals(BigDecimal.valueOf(0))) {
            BigDecimal normA = (a.subtract(BigDecimal.valueOf(10000000)))
                    .divide(BigDecimal.valueOf(20000000), 10, RoundingMode.HALF_UP);
            BigDecimal normB = (BigDecimal.valueOf(b).subtract(BigDecimal.valueOf(50)))
                    .divide(BigDecimal.valueOf(1350), 10, RoundingMode.HALF_UP);
            BigDecimal normC = (c.subtract(BigDecimal.valueOf(1.01)))
                    .divide(BigDecimal.valueOf(0.09), 10, RoundingMode.HALF_UP);

            BigDecimal gewichtungA = BigDecimal.valueOf(1.2);
            BigDecimal gewichtungB = BigDecimal.valueOf(1.2);
            BigDecimal gewichtungC = BigDecimal.valueOf(1);

            log.atDebug().log("Normierte Variablen: ");
            log.atDebug().log("normA: " + normA + " mit gewichtungA: " + gewichtungA);
            log.atDebug().log("normB: " + normB + " mit gewichtungB: " + gewichtungB);
            log.atDebug().log("normC: " + normC + " mit gewichtungC: " + gewichtungC);


            BigDecimal x = (normA.multiply(gewichtungA))
                    .add((normB.multiply(gewichtungB)))
                    .add((normC.multiply(gewichtungC)))
                    .add(BigDecimal.valueOf(-1.5));

            log.atDebug().log("500 + 100 * tanh(x)");
            log.atDebug().log("x: " + x);


            //500 als mittelwert
            //+- 100 also Jahresnettor Prämie muss zwischen 400 und 600 liegen
            BigDecimal erg = BigDecimal.valueOf(500)
                    .add(BigDecimal.valueOf(100).multiply(BigDecimal.valueOf(Math.tanh(x.doubleValue()))));
            log.atDebug().log("Ergebnis: " + erg);
            return erg;
        }

        return new BigDecimal(-1);
    }

    private BigDecimal calculateKasko(ProduktbausteinType vp) {
        log.atDebug().log("----- calculateKasko -----");

        BigDecimal praemie;

        List<BigDecimal> anschaffungspreise = new ArrayList<>();
        for (VersichertesInteresseType fa : vp.getVersicherteObjekte()) {
            if (fa instanceof FahrzeugType) {
                if (((FahrzeugType) fa).getListenpreis() == null) {
                    addMeldungToProdukt(vp, "Kein Listenpreis vorhanden für fahrzeug " + ((FahrzeugType) fa).getHandelsbez(), 1);
                    return new BigDecimal(-1);
                }
                if (((FahrzeugType) fa).getSonderausstattung() != null) {
                    anschaffungspreise.add(((FahrzeugType) fa).getListenpreis().add(((FahrzeugType) fa).getSonderausstattung()));
                }else {
                    anschaffungspreise.add(((FahrzeugType) fa).getListenpreis());
                }
                log.atDebug().log("Anschaffungspreis: " + ((FahrzeugType) fa).getListenpreis().add(((FahrzeugType) fa).getSonderausstattung()));
            }
        }

        anschaffungspreise.sort(BigDecimal::compareTo);

        if (anschaffungspreise.isEmpty()) {
            addMeldungToProdukt(vp, "Es muss ein Fahrzeug vorhanden sein", 1);
            return new BigDecimal(-1);
        }
        praemie = anschaffungspreise.getLast().multiply(BigDecimal.valueOf(0.042));
        log.atDebug().log("Prämie Teuerstes Fahrzeug: " + praemie);


        for (int i = 0; i < anschaffungspreise.size()-1; i++) {
            praemie = praemie.add(anschaffungspreise.get(i)
                    .multiply(BigDecimal.valueOf(0.042)).multiply(BigDecimal.valueOf(0.3)));

            log.atDebug().log("Prämie Fahrzeug " + (i+2) + ": " + anschaffungspreise.get(i)
                    .multiply(BigDecimal.valueOf(0.042)).multiply(BigDecimal.valueOf(0.3)));
        }

        List<String> praemienfaktoren = new ArrayList<>();
        getPraemienfaktoren(vp, praemienfaktoren);

        for (String pf : praemienfaktoren){
            BigDecimal value = new BigDecimal(pf.substring(pf.indexOf(" ") + 1));
            if (pf.contains("mult")) {
                praemie = praemie.multiply(value);

                log.atDebug().log("Prämie multipliziert mit " + value);
            }else if (pf.contains("add")){
                praemie = praemie.add(value);

                log.atDebug().log("Prämie addirt mit " + value);
            }
        }

        return praemie;
    }

    private void addMeldungToProdukt(ProduktbausteinType produktbaustein, String meldung, int errorType) {
        ServiceFault sf = new ServiceFault();
        sf.setErrorType(BigInteger.valueOf(errorType));
        sf.setErrorMsg(meldung);

        produktbaustein.getMeldungen().add(sf);
    }

    private void getPraemienfaktoren(ProduktbausteinType vp, List<String> praemienfaktoren) {
        if (vp.getPraemienfaktor() != null){
            praemienfaktoren.add(vp.getPraemienfaktor());
        }
        for (ProduktbausteinType baustein : vp.getBausteine()) {
            getPraemienfaktoren(baustein, praemienfaktoren);
        }
    }
}
