package de.avatic.lcc.repositories.country;

import de.avatic.lcc.model.db.country.Country;
import de.avatic.lcc.model.db.country.IsoCode;
import de.avatic.lcc.model.db.country.RegionCode;
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.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

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

@Repository
public class CountryRepository {


    private final JdbcTemplate jdbcTemplate;
    private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    public CountryRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
        this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
    }

    @Transactional
    public Optional<Country> getById(Integer id) {

        String query = """
                SELECT country.id AS id, country.iso_code AS iso_code, country.region_code AS region_code, country.name AS name, country.is_deprecated AS is_deprecated
                FROM country
                WHERE country.id = ?
                """;

        List<Country> countries = jdbcTemplate.query(query, new CountryMapper(), id);

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

    @Transactional
    public Optional<Country> getByIsoCode(IsoCode isoCode) {

        String query = """
            SELECT country.id AS id, country.iso_code AS iso_code, country.region_code AS region_code, country.is_deprecated AS is_deprecated, country.name AS name
            FROM country
            WHERE country.iso_code = ?
            """;

        List<Country> countries = jdbcTemplate.query(query, new CountryMapper(), isoCode.name());

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

    @Transactional
    public SearchQueryResult<Country> listCountries(Optional<String> filter, boolean excludeDeprecated, SearchQueryPagination pagination) {
        String query = buildQuery(filter.orElse(null), excludeDeprecated, true);

        var countries = filter.isPresent() ?
                jdbcTemplate.query(query, new CountryMapper(),
                        "%" + filter.get() + "%", "%" + filter.get() + "%", "%" + filter.get() + "%", pagination.getLimit(), pagination.getOffset()) :
                jdbcTemplate.query(query, new CountryMapper()
                        , pagination.getLimit(), pagination.getOffset());

        Integer totalCount = filter.isPresent() ?
                jdbcTemplate.queryForObject(
                        buildCountQuery(filter.get(), excludeDeprecated),
                        Integer.class, "%" + filter.get() + "%", "%" + filter.get() + "%", "%" + filter.get() + "%"
                ) :
                jdbcTemplate.queryForObject(
                        buildCountQuery(null, excludeDeprecated),
                        Integer.class
                );

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

    @Transactional
    public SearchQueryResult<Country> listCountries(Optional<String> filter, boolean excludeDeprecated) {
        String query = buildQuery(filter.orElse(null), excludeDeprecated, false);

        var countries = filter.map(f -> jdbcTemplate.query(query, new CountryMapper(),
                        "%" + f + "%", "%" + f + "%", "%" + f + "%"))
                .orElseGet(() -> jdbcTemplate.query(query, new CountryMapper()));

        Integer totalCount = filter.map(f -> jdbcTemplate.queryForObject(
                        buildCountQuery(f, excludeDeprecated),
                        Integer.class, "%" + f + "%", "%" + f + "%", "%" + f + "%"))
                .orElseGet(() -> jdbcTemplate.queryForObject(
                        buildCountQuery(null, excludeDeprecated),
                        Integer.class));

        return new SearchQueryResult<>(countries, 1, totalCount, totalCount);
    }

    private String buildCountQuery(String filter, boolean excludeDeprecated) {
        StringBuilder queryBuilder = new StringBuilder("""
                SELECT COUNT(*)
                        FROM country  WHERE 1=1""");

        if (excludeDeprecated) {
            queryBuilder.append(" AND is_deprecated = FALSE");
        }
        if (filter != null) {
            queryBuilder.append(" AND (iso_code LIKE ? OR region_code LIKE ? or name LIKE ?) ");
        }

        return queryBuilder.toString();
    }

    private String buildQuery(String filter, boolean excludeDeprecated, boolean hasLimit) {
        StringBuilder queryBuilder = new StringBuilder("""
                SELECT id, iso_code, region_code, is_deprecated, name
                        FROM country WHERE 1=1""");

        if (excludeDeprecated) {
            queryBuilder.append(" AND is_deprecated = FALSE ");
        }
        if (filter != null) {
            queryBuilder.append(" AND (iso_code LIKE ? OR region_code LIKE ? OR name LIKE ?) ");
        }
        if (hasLimit) {
            queryBuilder.append(" ORDER BY iso_code LIMIT ? OFFSET ? ");
        } else {
            queryBuilder.append(" ORDER BY iso_code ");
        }
        return queryBuilder.toString();
    }

    public List<Country> listAllCountries() {
        String query = "SELECT * FROM country ORDER BY iso_code";
        return jdbcTemplate.query(query, new CountryMapper());
    }

    public List<Country> getByIsoCodes(List<IsoCode> outboundCountries) {
        if (outboundCountries == null || outboundCountries.isEmpty()) {
            return Collections.emptyList();
        }

        String sql = "SELECT * FROM country WHERE iso_code IN (:isoCodes)";

        MapSqlParameterSource parameters = new MapSqlParameterSource();
        parameters.addValue("isoCodes", outboundCountries.stream().map(Enum::name).toList());

        return namedParameterJdbcTemplate.query(sql, parameters, new CountryMapper());
    }


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

            entity.setId(rs.getInt("id"));
            entity.setIsoCode(IsoCode.valueOf(rs.getString("iso_code")));
            entity.setRegionCode(RegionCode.valueOf(rs.getString("region_code")));
            entity.setDeprecated(rs.getBoolean("is_deprecated"));
            entity.setName(rs.getString("name"));

            return entity;
        }
    }
}
