package de.avatic.lcc.repositories.users;

import de.avatic.lcc.model.db.ValidityTuple;
import de.avatic.lcc.model.db.nodes.Node;
import de.avatic.lcc.util.exception.base.ForbiddenException;
import de.avatic.lcc.util.exception.internalerror.DatabaseException;
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.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;

@Repository
public class UserNodeRepository {


    private final JdbcTemplate jdbcTemplate;

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

    @Transactional
    public Collection<Node> searchNode(String filter, int limit, int userId, boolean excludeDeprecated) {

        StringBuilder queryBuilder = new StringBuilder().append("SELECT * FROM sys_user_node WHERE user_id = ?");

        List<Object> params = new ArrayList<>();
        params.add(userId);

        // Add filter conditions only if filter is not null
        if (filter != null) {
            queryBuilder.append(" AND (name LIKE ? OR address LIKE ?)");
            params.add('%' + filter + '%');
            params.add('%' + filter + '%');
        }

        if (excludeDeprecated) {
            queryBuilder.append(" AND is_deprecated = FALSE");
        }

        queryBuilder.append(" LIMIT ?");
        params.add(limit);

        return jdbcTemplate.query(queryBuilder.toString(), new NodeMapper(), params.toArray());
    }

    @Transactional
    public Integer add(Integer userId, Node node) {
        String sql = """
            INSERT INTO sys_user_node (
                name, address, geo_lat, geo_lng,
                is_deprecated, country_id, user_id
            ) VALUES (?, ?, ?, ?, ?, ?, ?)
            """;

        KeyHolder keyHolder = new GeneratedKeyHolder();

        int affectedRows = jdbcTemplate.update(connection -> {
            PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            ps.setString(1, node.getName());
            ps.setString(2, node.getAddress());
            ps.setBigDecimal(3, node.getGeoLat());
            ps.setBigDecimal(4, node.getGeoLng());
            ps.setBoolean(5, node.getDeprecated());
            ps.setInt(6, node.getCountryId());
            ps.setInt(7, userId);
            return ps;
        }, keyHolder);

        if (affectedRows != 1)
            throw new DatabaseException("Could not add node to user");

        return Objects.requireNonNull(keyHolder.getKey()).intValue();
    }

    @Transactional
    public Optional<Node> getById(Integer id) {
        String query = "SELECT * FROM sys_user_node WHERE id = ?";

        var nodes = jdbcTemplate.query(query, new NodeMapper(), id);

        if(nodes.isEmpty())
            return Optional.empty();
        else
            return Optional.of(nodes.getFirst());
    }

    @Transactional
    public List<Node> findNodeListsForReportingByPeriodId(Integer materialId, ValidityTuple tuple) {
        String userSuppliersSql = """
                    SELECT DISTINCT un.*
                    FROM premise p
                    INNER JOIN calculation_job cj ON p.id = cj.premise_id
                    INNER JOIN sys_user_node un ON p.user_supplier_node_id = un.id
                    WHERE p.material_id = ?
                    AND cj.validity_period_id = ?
                    AND cj.property_set_id = ?
                    AND p.user_supplier_node_id IS NOT NULL
                    AND cj.job_state = 'VALID'
                    """;

        return jdbcTemplate.query(userSuppliersSql, new NodeMapper(), materialId, tuple.periodId(), tuple.propertySetId());

    }

    @Transactional
    public Collection<Node> getByIds(List<Integer> nodeIds) {
        String placeholders = String.join(",", Collections.nCopies(nodeIds.size(), "?"));
        String query = """
                SELECT *
                FROM sys_user_node
                WHERE sys_user_node.id IN (""" + placeholders + ")";

        return nodeIds.isEmpty() ? Collections.emptyList() : jdbcTemplate.query(query, new NodeMapper(), nodeIds.toArray());
    }

    @Transactional
    public Optional<Integer> getOwnerById(Integer userSupplierId) {

        String query = """
                SELECT user_id FROM sys_user_node WHERE id = ?;
                """;

        var ids = jdbcTemplate.queryForList(query, Integer.class, userSupplierId);

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

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

    @Transactional
    public void checkOwner(List<Integer> userNodeIds, Integer userId) {
        String query = """
                SELECT id FROM sys_user_node WHERE id IN (?) AND user_id <> ?
                """;

        var otherIds = jdbcTemplate.queryForList(query, Integer.class, userNodeIds, userId);

        if(!otherIds.isEmpty()) {
            throw new ForbiddenException("Access violation. Cannot open user nodes with ids = " + otherIds);
        }
    }

    private static class NodeMapper implements RowMapper<Node> {

        @Override
        public Node mapRow(ResultSet rs, int rowNum) throws SQLException {

            var node = new Node();

            node.setId(rs.getInt("id"));
            node.setName(rs.getString("name"));
            node.setAddress(rs.getString("address"));
            node.setGeoLat(rs.getBigDecimal("geo_lat"));
            node.setGeoLng(rs.getBigDecimal("geo_lng"));
            node.setDeprecated(rs.getBoolean("is_deprecated"));
            node.setCountryId(rs.getInt("country_id"));

            node.setDestination(false);
            node.setIntermediate(false);
            node.setSource(true);

            node.setUserNode(true);

            return node;

        }
    }
}
