package de.avatic.lcc.repositories.rates;

import de.avatic.lcc.dto.generic.TransportType;
import de.avatic.lcc.model.db.rates.ContainerRate;
import de.avatic.lcc.model.db.rates.ValidityPeriodState;
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
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.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

@Repository
public class ContainerRateRepository {

    private final JdbcTemplate jdbcTemplate;

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

    /**
     * Retrieves a paginated list of {@link ContainerRate} entries filtered by a specific validity period ID.
     * Optionally filters results by searching node names and external mapping IDs for both source and destination nodes.
     *
     * @param filter     optional search term to filter by node names or external mapping IDs (case-insensitive partial match).
     *                   Searches across from_node and to_node names and external_mapping_ids. Can be null or empty.
     * @param pagination the {@link SearchQueryPagination} object containing limit, offset, and page details
     * @param periodId   the ID of the validity period to filter the rates by
     * @return a {@link SearchQueryResult} containing a list of filtered {@link ContainerRate} entities,
     *         total count, and pagination details
     */
    public SearchQueryResult<ContainerRate> listRatesByPeriodId(String filter, SearchQueryPagination pagination, Integer periodId) {
        StringBuilder queryBuilder = new StringBuilder();
        StringBuilder countQueryBuilder = new StringBuilder();
        List<Object> params = new ArrayList<>();
        List<Object> countParams = new ArrayList<>();

        // Base query with node joins
        String baseQuery = """
        FROM container_rate cr
        JOIN node fn ON cr.from_node_id = fn.id
        JOIN node tn ON cr.to_node_id = tn.id
        WHERE cr.validity_period_id = ?
        """;

        queryBuilder.append("SELECT cr.* ").append(baseQuery);
        countQueryBuilder.append("SELECT COUNT(*) ").append(baseQuery);

        params.add(periodId);
        countParams.add(periodId);

        // Add filter conditions if filter is provided
        if (filter != null && !filter.trim().isEmpty()) {
            String filterCondition = """
            AND (fn.name LIKE ? OR fn.external_mapping_id LIKE ?
                 OR tn.name LIKE ? OR tn.external_mapping_id LIKE ?)
            """;

            queryBuilder.append(filterCondition);
            countQueryBuilder.append(filterCondition);

            String filterParam = "%" + filter.trim() + "%";
            for (int i = 0; i < 4; i++) {
                params.add(filterParam);
                countParams.add(filterParam);
            }
        }

        queryBuilder.append(" ORDER BY cr.id LIMIT ? OFFSET ?");
        params.add(pagination.getLimit());
        params.add(pagination.getOffset());

        Integer totalCount = jdbcTemplate.queryForObject(countQueryBuilder.toString(), Integer.class, countParams.toArray());
        var results = jdbcTemplate.query(queryBuilder.toString(), new ContainerRateMapper(), params.toArray());

        return new SearchQueryResult<>(results, pagination.getPage(), totalCount, pagination.getLimit());
    }

    public Optional<ContainerRate> getById(Integer id) {
        String query = "SELECT * FROM container_rate WHERE id = ?";

        var rate = jdbcTemplate.query(query, new ContainerRateMapper(), id);

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

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

    public List<ContainerRate> listAllRatesByPeriodId(Integer periodId) {
        String query = "SELECT * FROM container_rate WHERE validity_period_id = ?";

        return jdbcTemplate.query(query, new ContainerRateMapper(), periodId);
    }

    @Transactional
    public List<ContainerRate> findRoutesByStartNodeIdAndDestinationCountryId(Integer startNodeId, List<Integer> destinationCountryIds) {

        if (startNodeId == null || destinationCountryIds == null || destinationCountryIds.isEmpty()) {
            return Collections.emptyList();
        }


        String destinationCountryPlaceholders = String.join(",", Collections.nCopies(destinationCountryIds.size(), "?"));

        String query = """
                SELECT container_rate.id AS id,
                       container_rate.validity_period_id AS validity_period_id,
                       container_rate.container_rate_type AS container_rate_type,
                       container_rate.from_node_id AS from_node_id,
                       container_rate.to_node_id AS to_node_id,
                       container_rate.rate_feu AS rate_feu,
                       container_rate.rate_teu AS rate_teu,
                       container_rate.rate_hc AS rate_hc,
                       container_rate.lead_time AS lead_time,
                       to_node.country_id as to_country_id,
                       from_node.country_id as from_country_id
                FROM container_rate
                LEFT JOIN node AS to_node ON to_node.id = container_rate.to_node_id
                LEFT JOIN  node AS from_node ON from_node.id = container_rate.from_node_id
                LEFT JOIN validity_period ON validity_period.id = container_rate.validity_period_id
                WHERE validity_period.state = ?
                AND to_node.is_deprecated = FALSE
                AND from_node.is_deprecated = FALSE
                AND (container_rate.container_rate_type = ? OR container_rate.container_rate_type = ?)
                AND container_rate.from_node_id = ? AND to_node.country_id IN (%s)""".formatted(
                destinationCountryPlaceholders);

        List<Object> params = new ArrayList<>();
        params.add(ValidityPeriodState.VALID.name());
        params.add(TransportType.SEA.name());
        params.add(TransportType.RAIL.name());
        params.add(startNodeId);
        params.addAll(destinationCountryIds);

        return jdbcTemplate.query(query, new ContainerRateMapper(true), params.toArray());
    }

    @Transactional
    public List<ContainerRate> getPostRunsFor(ContainerRate mainRun) {

        String query = """
                SELECT container_rate.id AS id,
                container_rate.validity_period_id AS validity_period_id,
                container_rate.container_rate_type AS container_rate_type,
                container_rate.from_node_id AS from_node_id,
                container_rate.to_node_id AS to_node_id,
                container_rate.rate_feu AS rate_feu,
                container_rate.rate_teu AS rate_teu,
                container_rate.rate_hc AS rate_hc,
                container_rate.lead_time AS lead_time,
                to_node.country_id as to_country_id,
                from_node.country_id as from_country_id
                FROM container_rate
                LEFT JOIN node AS to_node ON to_node.id = container_rate.to_node_id
                LEFT JOIN  node AS from_node ON from_node.id = container_rate.from_node_id
                LEFT JOIN validity_period ON validity_period.id = container_rate.validity_period_id
                WHERE validity_period.state = ?
                AND to_node.is_deprecated = FALSE
                AND from_node.is_deprecated = FALSE
                AND container_rate.from_node_id = ? AND container_rate.container_rate_type = ?""";

        return jdbcTemplate.query(query, new ContainerRateMapper(true), ValidityPeriodState.VALID.name(), mainRun.getToNodeId(), TransportType.POST_RUN.name());
    }


    @Transactional
    public Optional<ContainerRate> findRoute(Integer fromNodeId, Integer toNodeId, Integer periodId, TransportType type) {
        String query = """
                SELECT * FROM container_rate WHERE from_node_id = ? AND to_node_id = ? AND container_rate_type = ? AND validity_period_id = ?
                """;

        var route = jdbcTemplate.query(query, new ContainerRateMapper(), fromNodeId, toNodeId, type.name(), periodId);

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

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

    @Transactional
    public Optional<ContainerRate> findRoute(Integer fromNodeId, Integer toNodeId, TransportType type) {

        String query = """
                SELECT cr.id AS id,
                       cr.validity_period_id AS validity_period_id,
                       cr.container_rate_type AS container_rate_type,
                       cr.from_node_id AS from_node_id,
                       cr.to_node_id AS to_node_id,
                       cr.rate_feu AS rate_feu,
                       cr.rate_teu AS rate_teu,
                       cr.rate_hc AS rate_hc,
                       cr.lead_time AS lead_time
                FROM container_rate AS cr LEFT JOIN validity_period AS vp ON cr.validity_period_id = vp.id
                WHERE cr.from_node_id = ? AND cr.to_node_id = ? AND cr.container_rate_type = ? AND vp.state = ?
                """;

        var route = jdbcTemplate.query(query, new ContainerRateMapper(), fromNodeId, toNodeId, type.name(), ValidityPeriodState.VALID.name());

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

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

    @Transactional
    public void insert(ContainerRate containerRate) {
        String sql = """
        INSERT INTO container_rate
        (from_node_id, to_node_id, container_rate_type, rate_teu, rate_feu, rate_hc, lead_time, validity_period_id)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?)
        ON DUPLICATE KEY UPDATE
        container_rate_type = VALUES(container_rate_type),
        rate_teu = VALUES(rate_teu),
        rate_feu = VALUES(rate_feu),
        rate_hc = VALUES(rate_hc),
        lead_time = VALUES(lead_time)
        """;

        jdbcTemplate.update(sql,
                containerRate.getFromNodeId(),
                containerRate.getToNodeId(),
                containerRate.getType().name(),
                containerRate.getRateTeu(),
                containerRate.getRateFeu(),
                containerRate.getRateHc(),
                containerRate.getLeadTime(),
                containerRate.getValidityPeriodId()
        );
    }

    @Transactional
    public boolean hasMainRun(Integer nodeId) {
        String query = """
            SELECT EXISTS(
                SELECT 1 FROM container_rate
                WHERE (from_node_id = ? OR to_node_id = ?) 
                AND (container_rate_type = ? OR container_rate_type = ?)
            )
    """;

        return Boolean.TRUE.equals(jdbcTemplate.queryForObject(query, Boolean.class,
                nodeId, nodeId, TransportType.SEA.name(), TransportType.RAIL.name()));
    }

    @Transactional
    public void clearDraft() {
        var sql = "DELETE FROM container_rate WHERE container_rate.validity_period_id = (SELECT id FROM validity_period WHERE state = ?)";
        jdbcTemplate.update(sql,  ValidityPeriodState.DRAFT.name());
    }

    @Transactional
    public void copyCurrentToDraft() {
        String sql = """
        INSERT INTO container_rate (
            from_node_id,
            to_node_id,
            container_rate_type,
            rate_teu,
            rate_feu,
            rate_hc,
            lead_time,
            validity_period_id
        )
        SELECT
            cr.from_node_id,
            cr.to_node_id,
            cr.container_rate_type,
            cr.rate_teu,
            cr.rate_feu,
            cr.rate_hc,
            cr.lead_time,
            (SELECT id FROM validity_period WHERE state = 'DRAFT' LIMIT 1) as validity_period_id
        FROM container_rate cr
        INNER JOIN validity_period vp ON cr.validity_period_id = vp.id
        WHERE vp.state = 'VALID'
        """;

        jdbcTemplate.update(sql);
    }


    private record ContainerRateMapper(boolean fetchCountryIds) implements RowMapper<ContainerRate> {

        public ContainerRateMapper() {
                this(false);
            }

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

                entity.setId(rs.getInt("id"));
                entity.setValidityPeriodId(rs.getInt("validity_period_id"));
                entity.setFromNodeId(rs.getInt("from_node_id"));
                entity.setToNodeId(rs.getInt("to_node_id"));
                entity.setType(TransportType.valueOf(rs.getString("container_rate_type")));
                entity.setLeadTime(rs.getInt("lead_time"));
                entity.setRateFeu(rs.getBigDecimal("rate_feu"));
                entity.setRateTeu(rs.getBigDecimal("rate_teu"));
                entity.setRateHc(rs.getBigDecimal("rate_hc"));

                if (fetchCountryIds) {
                    entity.setToCountryId(rs.getInt("to_country_id"));
                    entity.setFromCountryId(rs.getInt("from_country_id"));
                }

                return entity;
            }
        }
}
