package de.avatic.lcc.repositories;

import de.avatic.lcc.model.db.nodes.Distance;
import de.avatic.lcc.model.db.nodes.DistanceMatrixState;
import de.avatic.lcc.model.db.nodes.Node;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Optional;

@Repository
public class DistanceMatrixRepository {

    private static final Logger logger = LoggerFactory.getLogger(DistanceMatrixRepository.class);
    private final JdbcTemplate jdbcTemplate;

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

    @Transactional
    public Optional<Distance> getDistance(Node src, boolean isUsrFrom, Node dest, boolean isUsrTo) {
        String fromCol = isUsrFrom ? "from_user_node_id" : "from_node_id";
        String toCol = isUsrTo ? "to_user_node_id" : "to_node_id";

        String query = "SELECT * FROM distance_matrix WHERE " + fromCol + " = ? AND " + toCol + " = ? AND state = ?";

        var distance = jdbcTemplate.query(query, new DistanceMapper(),
                src.getId(), dest.getId(), DistanceMatrixState.VALID.name());

        if (distance.isEmpty())
            return Optional.empty();

        return Optional.of(distance.getFirst());
    }

    @Transactional
    public void saveDistance(Distance distance) {
        try {
            // Determine which columns to use
            String fromCol = distance.getFromUserNodeId() != null ? "from_user_node_id" : "from_node_id";
            String toCol = distance.getToUserNodeId() != null ? "to_user_node_id" : "to_node_id";

            Integer fromId = distance.getFromUserNodeId() != null ? distance.getFromUserNodeId() : distance.getFromNodeId();
            Integer toId = distance.getToUserNodeId() != null ? distance.getToUserNodeId() : distance.getToNodeId();

            // First, check if an entry already exists
            String checkQuery = "SELECT id FROM distance_matrix WHERE " + fromCol + " = ? AND " + toCol + " = ?";
            var existingIds = jdbcTemplate.query(checkQuery,
                    (rs, rowNum) -> rs.getInt("id"),
                    fromId,
                    toId);

            if (!existingIds.isEmpty()) {
                // Update existing entry
                String updateQuery = """
                    UPDATE distance_matrix
                    SET from_geo_lat = ?,
                        from_geo_lng = ?,
                        to_geo_lat = ?,
                        to_geo_lng = ?,
                        distance = ?,
                        state = ?,
                        updated_at = ?
                    WHERE\s""" + fromCol + " = ? AND " + toCol + " = ?";

                jdbcTemplate.update(updateQuery,
                        distance.getFromGeoLat(),
                        distance.getFromGeoLng(),
                        distance.getToGeoLat(),
                        distance.getToGeoLng(),
                        distance.getDistance(),
                        distance.getState().name(),
                        distance.getUpdatedAt(),
                        fromId,
                        toId);

                logger.info("Updated existing distance entry for nodes {} -> {}",
                        distance.getFromNodeId(), distance.getToNodeId());
            } else {
                // Insert new entry
                String insertQuery = """
                    INSERT INTO distance_matrix
                    (from_node_id, to_node_id, from_user_node_id, to_user_node_id, from_geo_lat, from_geo_lng, to_geo_lat, to_geo_lng, distance, state, updated_at)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    """;

                jdbcTemplate.update(insertQuery,
                        distance.getFromNodeId(),
                        distance.getToNodeId(),
                        distance.getFromUserNodeId(),
                        distance.getToUserNodeId(),
                        distance.getFromGeoLat(),
                        distance.getFromGeoLng(),
                        distance.getToGeoLat(),
                        distance.getToGeoLng(),
                        distance.getDistance(),
                        distance.getState().name(),
                        distance.getUpdatedAt());

                logger.info("Inserted new distance entry for nodes {} -> {}",
                        distance.getFromNodeId(), distance.getToNodeId());
            }
        } catch (Exception e) {
            logger.error("Error saving distance to database for nodes {} -> {}",
                    distance.getFromNodeId(), distance.getToNodeId(), e);
            throw e;
        }
    }

    private static class DistanceMapper implements RowMapper<Distance> {
        @Override
        public Distance mapRow(ResultSet rs, int rowNum) throws SQLException {
            Distance entity = new Distance();

            entity.setFromNodeId(rs.getInt("from_node_id"));
            entity.setToNodeId(rs.getInt("to_node_id"));
            entity.setFromNodeId(rs.getInt("from_user_node_id"));
            entity.setToNodeId(rs.getInt("to_user_node_id"));
            entity.setDistance(rs.getBigDecimal("distance"));
            entity.setFromGeoLng(rs.getBigDecimal("from_geo_lng"));
            entity.setFromGeoLat(rs.getBigDecimal("from_geo_lat"));
            entity.setToGeoLng(rs.getBigDecimal("to_geo_lng"));
            entity.setToGeoLat(rs.getBigDecimal("to_geo_lat"));
            entity.setState(DistanceMatrixState.valueOf(rs.getString("state")));
            entity.setUpdatedAt(rs.getTimestamp("updated_at").toLocalDateTime());

            return entity;
        }
    }
}