package de.avatic.lcc.controller.calculation;

import de.avatic.lcc.dto.generic.RateType;
import de.avatic.lcc.dto.generic.TransportType;
import de.avatic.lcc.model.db.nodes.Node;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.sql.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@TestConfiguration
public class PremiseControllerTestData {

    private final JdbcTemplate jdbcTemplate;

    public PremiseControllerTestData(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Transactional
    public void deleteAll() {
        jdbcTemplate.execute("DELETE FROM premise_route_section");
        jdbcTemplate.execute("DELETE FROM premise_route_node");
        jdbcTemplate.execute("DELETE FROM premise_route");
        jdbcTemplate.execute("DELETE FROM premise_destination");

        jdbcTemplate.execute("ALTER TABLE premise_route_section AUTO_INCREMENT = 1;");
        jdbcTemplate.execute("ALTER TABLE premise_route_node AUTO_INCREMENT = 1;");
        jdbcTemplate.execute("ALTER TABLE premise_route AUTO_INCREMENT = 1;");
        jdbcTemplate.execute("ALTER TABLE premise_destination AUTO_INCREMENT = 1;");
    }


    @Transactional
    public void generateData() {

        var countryIdDE = getCountryId("DE");
        var countryIdCZ = getCountryId("CZ");
        var countryIdFR = getCountryId("FR");

        Node nodeAb = getNode("AB");
        Node nodeStr = getNode("STR");
        Node nodeCtt = getNode("CTT");

        Node nodeLx = getNode("LX");
        Node nodeCnxmn = getNode("CNXMN");
        Node nodeDeham = getNode("DEHAM");
        Node nodeBezee = getNode("BEZEE");

        var destinationId1 = createPremiseDestination(getPremise1Id(), 110, nodeAb.getId(), false, null, null, BigDecimal.valueOf(10), BigDecimal.valueOf(10), BigDecimal.valueOf(10), BigDecimal.valueOf(49.9506), BigDecimal.valueOf(9.1038), countryIdDE);
        var destinationId2 = createPremiseDestination(getPremise1Id(), 120, nodeStr.getId(), false, null, null, BigDecimal.valueOf(12), BigDecimal.valueOf(12), BigDecimal.valueOf(12), BigDecimal.valueOf(49.7002), BigDecimal.valueOf(13.035), countryIdCZ);

        var routeId1 = createPremiseRoute(destinationId1, false, false, true);
        var routeId2 = createPremiseRoute(destinationId1, true, true, false);

        var route1NodeId1 = createPremiseRouteNode(nodeLx, false, false, false, true, false);
        var route1NodeId2 = createPremiseRouteNode(nodeCnxmn, false, false, true, false, false);
        var route1NodeId3 = createPremiseRouteNode(nodeDeham, false, false, true, false, false);
        var route1NodeId4 = createPremiseRouteNode(nodeAb, false, true, false, false, false);

        var route1section1 = createPremiseRouteSection(routeId1, route1NodeId1, route1NodeId2, 1, TransportType.ROAD, RateType.CONTAINER, true, false, false, false);
        var route1section2 = createPremiseRouteSection(routeId1, route1NodeId2, route1NodeId3, 2, TransportType.SEA, RateType.CONTAINER, false, true, false, false);
        var route1section3 = createPremiseRouteSection(routeId1, route1NodeId3, route1NodeId4, 3, TransportType.POST_RUN, RateType.CONTAINER, false, false, true, false);

        var route2NodeId1 = createPremiseRouteNode(nodeLx, false, false, false, true, false);
        var route2NodeId2 = createPremiseRouteNode(nodeCnxmn, false, false, true, false, false);
        var route2NodeId3 = createPremiseRouteNode(nodeBezee, false, false, true, false, false);
        var route2NodeId4 = createPremiseRouteNode(nodeAb, false, true, false, false, false);

        var route2section1 = createPremiseRouteSection(routeId2, route2NodeId1, route2NodeId2, 1, TransportType.ROAD, RateType.CONTAINER, true, false, false, false);
        var route2section2 = createPremiseRouteSection(routeId2, route2NodeId2, route2NodeId3, 2, TransportType.SEA, RateType.CONTAINER, false, true, false, false);
        var route2section3 = createPremiseRouteSection(routeId2, route2NodeId3, route2NodeId4, 3, TransportType.POST_RUN, RateType.CONTAINER, false, false, true, false);

        var destinationId3 = createPremiseDestination(getPremise3Id(), 310, nodeCtt.getId(), true, BigDecimal.valueOf(3000.0), 30, BigDecimal.valueOf(30), BigDecimal.valueOf(30),BigDecimal.valueOf(30),BigDecimal.valueOf(46.7762),BigDecimal.valueOf(0.5351), countryIdFR);



    }

    private Integer createPremiseRouteSection(Integer routeId, Integer fromNode, Integer toNode, Integer listPosition, TransportType type, RateType rateType, boolean isPreRun, boolean isMainRun, boolean isPostRun, boolean isOutdated) {
        KeyHolder keyHolder = new GeneratedKeyHolder();

        String query = """
                INSERT INTO premise_route_section  ( premise_route_id, from_route_node_id, to_route_node_id, list_position, transport_type, rate_type, is_pre_run, is_main_run, is_post_run, is_outdated)
                VALUES (?, ?, ?, ?, ?, ?, ? ,?, ?, ?);
                """;

        jdbcTemplate.update(connection -> {
            PreparedStatement ps = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);

            ps.setInt(1, routeId);
            ps.setInt(2, fromNode);
            ps.setInt(3, toNode);
            ps.setInt(4, listPosition);
            ps.setString(5, type.name());
            ps.setString(6, rateType.name());
            ps.setBoolean(7, isPreRun);
            ps.setBoolean(8, isMainRun);
            ps.setBoolean(9, isPostRun);
            ps.setBoolean(10, isOutdated);

            return ps;
        }, keyHolder);

        return keyHolder.getKey() != null ? keyHolder.getKey().intValue() : null;

    }

    private Integer createPremiseRouteNode(Node node, Boolean isUserNode, Boolean isDestination, Boolean isIntermediate, Boolean isSource, Boolean isOutdated) {
        KeyHolder keyHolder = new GeneratedKeyHolder();

        String query = """
                INSERT INTO premise_route_node  ( node_id, user_node_id, name, address, country_id, is_destination, is_intermediate, is_source, geo_lat, geo_lng, is_outdated)
                VALUES (?, ?, ?, ?, ?, ?, ? ,?, ?, ?, ?);
                """;

        jdbcTemplate.update(connection -> {
            PreparedStatement ps = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);

            if (isUserNode) {
                ps.setNull(1, Types.INTEGER);
            } else {
                ps.setInt(1, node.getId());
            }

            if (!isUserNode) {
                ps.setNull(2, Types.INTEGER);
            } else {
                ps.setInt(2, node.getId());
            }

            ps.setString(3, node.getName());
            ps.setString(4, node.getAddress());
            ps.setInt(5, node.getCountryId());
            ps.setBoolean(6, isDestination);
            ps.setBoolean(7, isIntermediate);
            ps.setBoolean(8, isSource);
            ps.setBigDecimal(9, node.getGeoLat());
            ps.setBigDecimal(10, node.getGeoLng());
            ps.setBoolean(11, isOutdated);

            return ps;
        }, keyHolder);

        return keyHolder.getKey() != null ? keyHolder.getKey().intValue() : null;
    }

    private Node getNode(String externalMappingId) {
        String query = "SELECT * FROM node WHERE external_mapping_id = ?";
        return jdbcTemplate.queryForObject(query, new NodeMapper(), externalMappingId);
    }

    private Integer getPremise1Id() {
        String query = "SELECT id FROM premise WHERE material_id = (SELECT id FROM material WHERE part_number = '28152640129') AND hu_unit_count = 2";


        return jdbcTemplate.queryForObject(query, Integer.class);
    }


    private Integer getPremise3Id() {
        String query = "SELECT id FROM premise WHERE material_id = (SELECT id FROM material WHERE part_number = '8222640822') AND hu_unit_count = 3";



        return jdbcTemplate.queryForObject(query, Integer.class);
    }

    private Integer getCountryId(String isoCode) {
        String query = "SELECT id FROM country WHERE iso_code = ?";
        return jdbcTemplate.queryForObject(query, Integer.class, isoCode);
    }

    private Integer getNodeId(String externalMappingId) {
        String query = "SELECT id FROM node WHERE external_mapping_id = ?";
        return jdbcTemplate.queryForObject(query, Integer.class, externalMappingId);
    }

    private Integer createPremiseRoute(Integer premiseDestinationId, Boolean isFastest, Boolean isCheapest, Boolean isSelected) {
        KeyHolder keyHolder = new GeneratedKeyHolder();

        String query = """
                INSERT INTO premise_route  ( premise_destination_id, is_fastest, is_cheapest, is_selected)
                VALUES (?, ?, ?, ?);
                """;

        jdbcTemplate.update(connection -> {
            PreparedStatement ps = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);

            ps.setInt(1, premiseDestinationId);
            ps.setBoolean(2, isFastest);
            ps.setBoolean(3, isCheapest);
            ps.setBoolean(4, isSelected);

            return ps;
        }, keyHolder);

        return keyHolder.getKey() != null ? keyHolder.getKey().intValue() : null;
    }

    private Integer createPremiseDestination(Integer premiseId, Integer annualAmount, Integer destinationNodeId,
                                             Boolean isD2D, BigDecimal rateD2D, Integer leadTimeD2D, BigDecimal repackingCost, BigDecimal handlingCost,
                                             BigDecimal disposalCost, BigDecimal geoLat, BigDecimal geoLng, Integer countryId) {

        KeyHolder keyHolder = new GeneratedKeyHolder();

        String query = """
                INSERT INTO premise_destination (premise_id,
                                                 annual_amount,
                                                 destination_node_id,
                                                 is_d2d,
                                                 rate_d2d,
                                                 lead_time_d2d,
                                                 repacking_cost,
                                                 handling_cost,
                                                 disposal_cost,
                                                 geo_lat,
                                                 geo_lng,
                                                 country_id)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
                """;

        jdbcTemplate.update(connection -> {
            PreparedStatement ps = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);

            ps.setInt(1, premiseId);
            ps.setInt(2, annualAmount);
            ps.setInt(3, destinationNodeId);
            ps.setBoolean(4, isD2D);
            ps.setBigDecimal(5, rateD2D);
            if (leadTimeD2D == null) ps.setNull(6, Types.INTEGER);
            else ps.setInt(6, leadTimeD2D);
            ps.setBigDecimal(7, repackingCost);
            ps.setBigDecimal(8, handlingCost);
            ps.setBigDecimal(9, disposalCost);
            ps.setBigDecimal(10, geoLat);
            ps.setBigDecimal(11, geoLng);
            ps.setInt(12, countryId);

            return ps;
        }, keyHolder);

        return keyHolder.getKey() != null ? keyHolder.getKey().intValue() : null;
    }

    private List<Map<Integer, Integer>> getPredecessorsOf(Integer id) {

        String queryChains = """
                SELECT chain.id AS id FROM node_predecessor_chain AS chain WHERE chain.node_id = ? ORDER BY chain.id
                """;

        return jdbcTemplate.query(queryChains, (chainRs, rowNum) -> {

            Integer chainId = chainRs.getInt("id");

            String query = """
                    SELECT entry.node_id AS predecessor_node_id , entry.sequence_number as sequence_number
                    FROM node_predecessor_entry AS entry
                    WHERE entry.node_predecessor_chain_id = ? ORDER BY entry.sequence_number""";

            return jdbcTemplate.query(query, rs -> {
                Map<Integer, Integer> predecessors = new HashMap<>();

                while (rs.next()) {
                    predecessors.put(rs.getInt("sequence_number"), rs.getInt("predecessor_node_id"));
                }

                return predecessors;
            }, chainId);
        }, id);
    }

    private Collection<Integer> getOutboundCountriesOf(Integer id) {
        String query = """
                SELECT outbound_country_mapping.country_id
                FROM outbound_country_mapping
                WHERE outbound_country_mapping.node_id = ?""";

        return jdbcTemplate.queryForList(query, Integer.class, id);
    }

    private class NodeMapper implements RowMapper<Node> {

        @Override
        public Node mapRow(ResultSet rs, int rowNum) throws SQLException {
            var data = new Node();

            data.setId(rs.getInt("id"));
            data.setName(rs.getString("name"));
            data.setAddress(rs.getString("address"));
            data.setCountryId(rs.getInt("country_id"));
            data.setDestination(rs.getBoolean("is_destination"));
            data.setSource(rs.getBoolean("is_source"));
            data.setIntermediate(rs.getBoolean("is_intermediate"));
            data.setPredecessorRequired(rs.getBoolean("predecessor_required"));

            data.setGeoLat(rs.getBigDecimal("geo_lat"));
            data.setGeoLng(rs.getBigDecimal("geo_lng"));

            data.setDeprecated(rs.getBoolean("is_deprecated"));

            data.setNodePredecessors(getPredecessorsOf(data.getId()));
            data.setOutboundCountries(getOutboundCountriesOf(data.getId()));

            return data;
        }
    }

}
