package de.avatic.lcc.repositories.country;

import de.avatic.lcc.dto.generic.PropertyDTO;
import de.avatic.lcc.model.db.properties.CountryPropertyMappingId;
import de.avatic.lcc.model.db.rates.ValidityPeriodState;
import de.avatic.lcc.util.exception.internalerror.DatabaseException;
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.Collection;
import java.util.List;
import java.util.Optional;

@Repository
public class CountryPropertyRepository {


    private final JdbcTemplate jdbcTemplate;

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

    @Transactional
    public void setProperty(Integer setId, Integer countryId, String mappingId, String value) {
        Integer typeId = getTypeIdByMappingId(mappingId);

        String validValueQuery = """
                SELECT cp.property_value
                FROM country_property cp
                JOIN property_set ps ON ps.id = cp.property_set_id
                WHERE ps.state = ? AND cp.country_property_type_id = ? AND cp.country_id = ?""";

        String validValue = jdbcTemplate.queryForObject(validValueQuery, String.class,
                ValidityPeriodState.VALID.name(), typeId, countryId);

        if (value.equals(validValue)) {
            String deleteQuery = "DELETE FROM country_property WHERE property_set_id = ? AND country_property_type_id = ? AND country_id = ?";
            jdbcTemplate.update(deleteQuery, setId, typeId, countryId);
            return;
        }

        String query = """
                INSERT INTO country_property (property_value, country_id, country_property_type_id, property_set_id) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE property_value = ?
                """;

        int affectedRows = jdbcTemplate.update(query, value, countryId, typeId, setId, value);

        if(!(affectedRows > 0))
            throw new DatabaseException("Could not update property value for country " + countryId + " and property type " + mappingId);
    }

    @Transactional
    public Optional<PropertyDTO> getByMappingIdAndCountryId(CountryPropertyMappingId mappingId, Integer countryId) {
        return getByMappingIdAndCountryId(mappingId.name(), countryId);
    }

    @Transactional
    public Optional<PropertyDTO> getByMappingIdAndCountryId(CountryPropertyMappingId mappingId, Integer setId, Integer countryId) {
        return getByMappingIdAndCountryId(mappingId.name(), setId, countryId);
    }

    public Optional<PropertyDTO> getByMappingIdAndCountryId(String mappingId, Integer countryId) {
        String query = """
            SELECT type.name as name,
                   type.data_type as dataType,
                   type.external_mapping_id as externalMappingId,
                   type.validation_rule as validationRule,
                   type.is_required as is_required,
                   type.description as description,
                   type.property_group as propertyGroup,
                   type.sequence_number as sequenceNumber,
                   draft.property_value as draftValue,
                   valid.property_value as validValue
            
            FROM country_property_type AS type
            LEFT JOIN (
                SELECT cp.property_value, cp.country_property_type_id
                FROM country_property cp
                JOIN property_set ps ON ps.id = cp.property_set_id
                WHERE cp.country_id = ? AND ps.state = 'DRAFT'
            ) AS draft ON draft.country_property_type_id = type.id
            LEFT JOIN (
                SELECT cp.property_value, cp.country_property_type_id
                FROM country_property cp
                JOIN property_set ps ON ps.id = cp.property_set_id
                WHERE cp.country_id = ? AND ps.state = 'VALID'
            ) AS valid ON valid.country_property_type_id = type.id
            WHERE type.external_mapping_id = ?
            """;

        var property = jdbcTemplate.query(query, new PropertyMapper(), countryId, countryId, mappingId);

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

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

    public Optional<PropertyDTO> getByMappingIdAndCountryId(String mappingId, Integer setId, Integer countryId) {
        String query = """
            SELECT type.name as name,
                   type.data_type as dataType,
                   type.external_mapping_id as externalMappingId,
                   type.validation_rule as validationRule,
                   type.is_required as is_required,
                   type.description as description,
                   type.property_group as propertyGroup,
                   type.sequence_number as sequenceNumber,
                   valid.property_value as draftValue,
                   valid.property_value as validValue
            FROM country_property_type AS type
            LEFT JOIN (
                SELECT cp.property_value, cp.country_property_type_id
                FROM country_property cp
                JOIN property_set ps ON ps.id = cp.property_set_id
                WHERE cp.country_id = ? AND ps.id = ?
            ) AS valid ON valid.country_property_type_id = type.id
            WHERE type.external_mapping_id = ?
            """;

        var property = jdbcTemplate.query(query, new PropertyMapper(), countryId, setId, mappingId);

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

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

    private Integer getTypeIdByMappingId(String mappingId) {
        String query = "SELECT id FROM country_property_type WHERE external_mapping_id = ?";
        return jdbcTemplate.queryForObject(query, Integer.class, mappingId);
    }

    @Transactional
    public List<PropertyDTO> listPropertiesByCountryId(Integer id) {

        String query = """                
                SELECT type.name as name, type.data_type as dataType,
                    type.external_mapping_id as externalMappingId,
                    type.validation_rule as validationRule,
                    type.is_required as is_required,
                    type.is_required as is_required,
                    type.description as description,
                    type.property_group as propertyGroup,
                    type.sequence_number as sequenceNumber,
                    MAX(CASE WHEN ps.state = 'DRAFT' THEN cp.property_value END) as draftValue,
                    MAX(CASE WHEN ps.state = 'VALID' THEN cp.property_value END) as validValue
                FROM country_property_type AS type
                LEFT JOIN country_property AS cp ON cp.country_property_type_id = type.id AND cp.country_id = ?
                LEFT JOIN property_set AS ps ON ps.id = cp.property_set_id AND ps.state IN ('DRAFT', 'VALID')
                GROUP BY type.id, type.name, type.data_type, type.external_mapping_id, type.validation_rule
                HAVING draftValue IS NOT NULL OR validValue IS NOT NULL;
                """;


        return jdbcTemplate.query(query, new PropertyMapper(), id);
    }

    public Collection<PropertyDTO> listPropertiesByCountryIdAndPropertySetId(Integer id, Integer propertySetId) {

        String query = """
                SELECT type.name as name, type.data_type as dataType, type.external_mapping_id as externalMappingId, type.validation_rule as validationRule, type.is_required as is_required,
                   type.description as description,
                   type.property_group as propertyGroup,
                   type.sequence_number as sequenceNumber,
                property.property_value as draftValue, property.property_value as validValue
                FROM country_property_type AS type
                LEFT JOIN country_property AS property ON property.country_property_type_id = type.id
                LEFT JOIN property_set AS propertySet ON propertySet.id = property.property_set_id
                WHERE property.country_id = ? AND propertySet.id = ?
                """;

        return jdbcTemplate.query(query, new PropertyMapper(), id, propertySetId);
    }

    public void fillDraft(Integer setId) {
        String query = """
                SELECT type.id AS typeId, property.property_value as value, property.country_id AS country_id FROM country_property_type AS type
                LEFT JOIN country_property AS property ON property.country_property_type_id = type.id
                LEFT JOIN property_set AS propertySet ON propertySet.id = property.property_set_id WHERE propertySet.state = 'VALID'""";


        jdbcTemplate.query(query, (rs, rowNum) -> {
            String insertQuery = "INSERT IGNORE INTO country_property (property_value, country_id, country_property_type_id, property_set_id) VALUES (?, ?, ?, ?)";
            jdbcTemplate.update(insertQuery, rs.getString("value"), rs.getInt("country_id"), rs.getInt("typeId"), setId);
            return null;
        });


    }

    private static class PropertyMapper implements RowMapper<PropertyDTO> {

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

            dto.setName(rs.getString("name"));
            dto.setDraftValue(rs.getString("draftValue"));
            dto.setCurrentValue(rs.getString("validValue"));
            dto.setValidationRule(rs.getString("validationRule"));
            dto.setExternalMappingId(rs.getString("externalMappingId"));
            dto.setRequired(rs.getBoolean("is_required"));
            dto.setDataType(rs.getString("dataType"));

            dto.setDescription(rs.getString("description"));
            dto.setPropertyGroup(rs.getString("propertyGroup"));
            dto.setSequenceNumber(rs.getInt("sequenceNumber"));

            return dto;
        }
    }
}
