package de.avatic.lcc.repositories.rates;

import de.avatic.lcc.model.db.rates.MatrixRate;
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.List;
import java.util.Optional;

/**
 * Repository for managing {@link MatrixRate} entities, including retrieval and mapping of MatrixRate rows
 * from the database.
 */
@Repository
public class MatrixRateRepository {

    private final JdbcTemplate jdbcTemplate;

    /**
     * Instantiates the repository by injecting a {@link JdbcTemplate}.
     *
     * @param jdbcTemplate the {@link JdbcTemplate} to be used for database interactions
     */
    public MatrixRateRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    /**
     * Retrieves a paginated list of all {@link MatrixRate} entries from the database.
     *
     * @param pagination the {@link SearchQueryPagination} object containing limit, offset, and page details
     * @return a {@link SearchQueryResult} containing a list of {@link MatrixRate} entities, total count,
     * and pagination details
     */
    @Transactional
    public SearchQueryResult<MatrixRate> listRates(SearchQueryPagination pagination) {
        String query = "SELECT * FROM country_matrix_rate ORDER BY id LIMIT ? OFFSET ?";
        var totalCount = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM country_matrix_rate", Integer.class);
        return new SearchQueryResult<>(jdbcTemplate.query(query, new MatrixRateMapper(), pagination.getLimit(), pagination.getOffset()), pagination.getPage(), totalCount, pagination.getLimit());
    }

    /**
     * Retrieves a paginated list of {@link MatrixRate} entries filtered by a specific validity period ID.
     * Optionally filters results by searching country names and ISO codes for both source and destination countries.
     *
     * @param filter     optional search term to filter by country names or ISO codes (case-insensitive partial match).
     *                   Searches across from_country and to_country names and iso_codes. 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 MatrixRate} entities,
     *         total count, and pagination details
     */
    @Transactional
    public SearchQueryResult<MatrixRate> 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 country joins
        String baseQuery = """
        FROM country_matrix_rate cmr
        JOIN country fc ON cmr.from_country_id = fc.id
        JOIN country tc ON cmr.to_country_id = tc.id
        WHERE cmr.validity_period_id = ?
        """;

        queryBuilder.append("SELECT cmr.* ").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 (fc.name LIKE ? OR fc.iso_code LIKE ?
                 OR tc.name LIKE ? OR tc.iso_code 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 cmr.id LIMIT ? OFFSET ?");
        params.add(pagination.getLimit());
        params.add(pagination.getOffset());

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

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

    /**
     * Retrieves a {@link MatrixRate} entity from the database by its unique identifier.
     *
     * @param id the unique identifier of the {@link MatrixRate} entity to retrieve
     * @return the {@link MatrixRate} entity associated with the specified ID
     */
    @Transactional
    public MatrixRate getById(Integer id) {
        String query = "SELECT * FROM country_matrix_rate WHERE id = ?";
        return jdbcTemplate.queryForObject(query, new MatrixRateMapper(), id);
    }

    @Transactional
    public List<MatrixRate> listAllRatesByPeriodId(Integer periodId) {
        String query = "SELECT * FROM country_matrix_rate WHERE validity_period_id = ?";
        return jdbcTemplate.query(query, new MatrixRateMapper(), periodId);
    }

    @Transactional
    public Optional<MatrixRate> getByCountryIds(Integer fromCountryId, Integer toCountryId) {

        String query = """
            SELECT cmr.id AS id,
                   cmr.rate AS rate,
                   cmr.from_country_id AS from_country_id,
                   cmr.to_country_id AS to_country_id,
                   cmr.validity_period_id AS validity_period_id
            FROM country_matrix_rate AS cmr LEFT JOIN validity_period AS vp ON vp.id = cmr.validity_period_id
            WHERE cmr.from_country_id = ? AND cmr.to_country_id = ? AND vp.state = ?
            """;

        var rates = jdbcTemplate.query(query, new MatrixRateMapper(), fromCountryId, toCountryId, ValidityPeriodState.VALID.name());

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

    @Transactional
    public Optional<MatrixRate> getByCountryIds(Integer fromCountryId, Integer toCountryId, Integer periodId) {

        String query = """
            SELECT *
            FROM country_matrix_rate
            WHERE from_country_id = ? AND to_country_id = ? AND validity_period_id = ?
            """;

        var rates = jdbcTemplate.query(query, new MatrixRateMapper(), fromCountryId, toCountryId, periodId);

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

    @Transactional
    public void insert(MatrixRate rate) {
        String sql = """
        INSERT INTO country_matrix_rate (from_country_id, to_country_id, rate, validity_period_id)
        VALUES (?, ?, ?, ?)
        ON DUPLICATE KEY UPDATE
        rate = VALUES(rate)
        """;

        jdbcTemplate.update(sql,
                rate.getFromCountry(),
                rate.getToCountry(),
                rate.getRate(),
                rate.getValidityPeriodId());
    }

    @Transactional
    public void copyCurrentToDraft() {
        String sql = """
        INSERT INTO country_matrix_rate (from_country_id, to_country_id, rate, validity_period_id)
        SELECT
            cmr.from_country_id,
            cmr.to_country_id,
            cmr.rate,
            (SELECT id FROM validity_period WHERE state = 'DRAFT' LIMIT 1) AS validity_period_id
        FROM country_matrix_rate cmr
        INNER JOIN validity_period vp ON cmr.validity_period_id = vp.id
        WHERE vp.state = 'VALID'
    """;

        jdbcTemplate.update(sql);
    }

    /**
     * Maps rows of a {@link ResultSet} to {@link MatrixRate} objects as required by
     * the {@link JdbcTemplate}.
     */
    private static class MatrixRateMapper implements RowMapper<MatrixRate> {
        /**
         * Maps a single row from the {@link ResultSet} to a {@link MatrixRate} entity.
         *
         * @param rs     the {@link ResultSet} containing rows from the database
         * @param rowNum the index of the current row
         * @return the mapped {@link MatrixRate} entity
         * @throws SQLException if an SQL exception occurs while accessing the {@link ResultSet}
         */
        @Override
        public MatrixRate mapRow(ResultSet rs, int rowNum) throws SQLException {
            MatrixRate entity = new MatrixRate();

            entity.setId(rs.getInt("id"));
            entity.setRate(rs.getBigDecimal("rate"));
            entity.setFromCountry(rs.getInt("from_country_id"));
            entity.setToCountry(rs.getInt("to_country_id"));
            entity.setValidityPeriodId(rs.getInt("validity_period_id"));

            return entity;
        }
    }
}
