package de.avatic.lcc.repositories.properties;


import de.avatic.lcc.model.db.properties.PropertySet;
import de.avatic.lcc.model.db.rates.ValidityPeriodState;
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.sql.Timestamp;
import java.util.List;

/**
 * Repository class for managing and querying property sets.
 * Provides methods for fetching, updating, and creating property set records in the database.
 */
@Repository
public class PropertySetRepository {

    private final JdbcTemplate jdbcTemplate;

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

    /**
     * Retrieves a list of all property sets from the database.
     *
     * @return A list of {@link PropertySet} objects containing property set information.
     */
    @Transactional
    public List<PropertySet> listPropertySets() {
        String query = "SELECT id, start_date, end_date, state FROM property_set";

        return jdbcTemplate.query(query, new PropertySetMapper());
    }

    /**
     * Retrieves the ID of the draft property set.
     * If no draft property set exists, a new one is created.
     *
     * @return The ID of the draft {@link PropertySet}.
     */
    @Transactional
    public Integer getDraftSetId() {
        return getDraftSet().getId();
    }

    /**
     * Retrieves the draft property set. If no draft exists, it creates a new one.
     *
     * @return The {@link PropertySet} object representing the draft state.
     */
    @Transactional
    public PropertySet getDraftSet() {
        createSet();
        return jdbcTemplate.queryForObject("SELECT id, start_date, end_date, state FROM property_set WHERE state = ?", new PropertySetMapper(), ValidityPeriodState.DRAFT.name());
    }

    private void createSet() {

        final Timestamp currentTimestamp = new Timestamp(System.currentTimeMillis());

        jdbcTemplate.update("INSERT INTO property_set (state, start_date) SELECT ?, ? WHERE NOT EXISTS (SELECT 1 FROM property_set WHERE state = ?)", ValidityPeriodState.DRAFT.name(), currentTimestamp, ValidityPeriodState.DRAFT.name());
    }

    /**
     * Retrieves the currently valid property set.
     *
     * @return The {@link PropertySet} object in the valid state, or null if none exists.
     */
    public PropertySet getValidSet() {
        return jdbcTemplate.queryForObject("SELECT id, start_date, end_date, state FROM property_set WHERE state = ?", new PropertySetMapper(), ValidityPeriodState.VALID.name());
    }

    /**
     * Retrieves the currently valid property set id.
     *
     * @return the ID of the valid {@link PropertySet}.
     */
    public Integer getValidSetId() {
        return getValidSet().getId();
    }


    /**
     * Applies the draft property set, marking it as valid and expiring the current valid set.
     * Updates the timestamps for the draft and valid states accordingly.
     */
    public void applyDraft() {

        final Timestamp currentTimestamp = new Timestamp(System.currentTimeMillis());

        /* set current to expired */
        jdbcTemplate.update("UPDATE property_set SET state = ?, end_date = ? WHERE state = ? ", ValidityPeriodState.EXPIRED.name(), currentTimestamp, ValidityPeriodState.VALID.name());
        jdbcTemplate.update("UPDATE property_set SET state = ?, start_date = ?  WHERE state = ? ", ValidityPeriodState.VALID.name(), currentTimestamp, ValidityPeriodState.DRAFT.name());

        /* create a new draft */
        createSet();
    }

    /**
     * Invalidates a specific property set by its ID if its current state is expired.
     *
     * @param id The ID of the property set to invalidate.
     */
    @Transactional
    public boolean invalidateById(Integer id) {
        int rowsUpdated = jdbcTemplate.update(
                "UPDATE property_set SET state = ? WHERE id = ? AND state = ?",
                ValidityPeriodState.INVALID.name(), id, ValidityPeriodState.EXPIRED.name());
        return rowsUpdated > 0;

    }

    /**
     * Checks if the current draft property set has any associated properties.
     *
     * @return {@code true} if properties exist for the draft set, {@code false} otherwise.
     */
    @Transactional
    public Boolean hasPropertiesDraft() {
        var setId = getDraftSetId();
        String query = """ 
                SELECT (SELECT COUNT(*) FROM system_property WHERE property_set_id = ?) + (SELECT COUNT(*) FROM country_property WHERE property_set_id = ?) AS total_count""";

        var totalCount = jdbcTemplate.queryForObject(query, Integer.class, setId, setId);
        return totalCount != null && totalCount > 0;
    }

    public ValidityPeriodState getState(Integer propertySetId) {
        String query = "SELECT state FROM property_set WHERE id = ?";
        String stateString = jdbcTemplate.queryForObject(query, String.class, propertySetId);
        return stateString != null ? ValidityPeriodState.valueOf(stateString) : null;
    }

    public PropertySet getById(Integer propertySetId) {
        String query = "SELECT id, start_date, end_date, state FROM property_set WHERE id = ?";

        var set = jdbcTemplate.query(query, new PropertySetMapper(), propertySetId);

        if(set.isEmpty())
            throw new IllegalArgumentException("Property set with id " + propertySetId + " does not exist");

        return set.getFirst();
    }

    /**
     * Mapper class for converting SQL query results into {@link PropertySet} objects.
     */
    private static class PropertySetMapper implements RowMapper<PropertySet> {

        @Override
        public PropertySet mapRow(ResultSet rs, int rowNum) throws SQLException {
            PropertySet propertySet = new PropertySet();

            propertySet.setId(rs.getInt("id"));

            Timestamp startDate = rs.getTimestamp("start_date");
            propertySet.setStartDate(startDate != null ? startDate.toLocalDateTime() : null);

            Timestamp endDate = rs.getTimestamp("end_date");
            propertySet.setEndDate(endDate != null ? endDate.toLocalDateTime() : null);

            propertySet.setState(ValidityPeriodState.valueOf(rs.getString("state")));

            return propertySet;
        }
    }
}
