package com.cameleer.server.app.storage;

import com.cameleer.common.model.ApplicationConfig;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public class PostgresApplicationConfigRepository {

    private final JdbcTemplate jdbc;
    private final ObjectMapper objectMapper;

    public PostgresApplicationConfigRepository(JdbcTemplate jdbc, ObjectMapper objectMapper) {
        this.jdbc = jdbc;
        this.objectMapper = objectMapper;
    }

    /**
     * Row mapper — columns (application, environment) are authoritative and always
     * overwrite whatever was in the stored JSON body. Callers must SELECT them.
     */
    private RowMapper<ApplicationConfig> rowMapper() {
        return (rs, rowNum) -> {
            try {
                ApplicationConfig cfg = objectMapper.readValue(rs.getString("config_val"), ApplicationConfig.class);
                cfg.setApplication(rs.getString("application"));
                cfg.setEnvironment(rs.getString("environment"));
                cfg.setVersion(rs.getInt("version"));
                cfg.setUpdatedAt(rs.getTimestamp("updated_at").toInstant());
                return cfg;
            } catch (JsonProcessingException e) {
                throw new RuntimeException("Failed to deserialize application config", e);
            }
        };
    }

    private static final String SELECT_FIELDS =
            "application, environment, config_val, version, updated_at";

    public List<ApplicationConfig> findByEnvironment(String environment) {
        return jdbc.query(
                "SELECT " + SELECT_FIELDS + " FROM application_config "
                        + "WHERE environment = ? ORDER BY application",
                rowMapper(), environment);
    }

    public List<ApplicationConfig> findAll() {
        return jdbc.query(
                "SELECT " + SELECT_FIELDS + " FROM application_config "
                        + "ORDER BY application, environment",
                rowMapper());
    }

    public Optional<ApplicationConfig> findByApplicationAndEnvironment(String application, String environment) {
        List<ApplicationConfig> results = jdbc.query(
                "SELECT " + SELECT_FIELDS + " FROM application_config "
                        + "WHERE application = ? AND environment = ?",
                rowMapper(), application, environment);
        return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0));
    }

    public ApplicationConfig save(String application, String environment, ApplicationConfig config, String updatedBy) {
        // Authoritative fields stamped on the DTO before serialization — guarantees the stored
        // JSON agrees with the row's columns.
        config.setApplication(application);
        config.setEnvironment(environment);
        String json;
        try {
            json = objectMapper.writeValueAsString(config);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Failed to serialize application config", e);
        }

        jdbc.update("""
                INSERT INTO application_config (application, environment, config_val, version, updated_at, updated_by)
                VALUES (?, ?, ?::jsonb, 1, now(), ?)
                ON CONFLICT (application, environment) DO UPDATE SET
                    config_val = EXCLUDED.config_val,
                    version = application_config.version + 1,
                    updated_at = now(),
                    updated_by = EXCLUDED.updated_by
                """,
                application, environment, json, updatedBy);

        return findByApplicationAndEnvironment(application, environment).orElseThrow();
    }

    public void delete(String application, String environment) {
        jdbc.update("DELETE FROM application_config WHERE application = ? AND environment = ?",
                application, environment);
    }
}
