package com.cameleer.server.app.outbound.storage;

import com.cameleer.server.core.http.TrustMode;
import com.cameleer.server.core.outbound.OutboundAuth;
import com.cameleer.server.core.outbound.OutboundAuthKind;
import com.cameleer.server.core.outbound.OutboundConnection;
import com.cameleer.server.core.outbound.OutboundConnectionRepository;
import com.cameleer.server.core.outbound.OutboundMethod;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.jdbc.core.ConnectionCallback;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import java.sql.Array;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

public class PostgresOutboundConnectionRepository implements OutboundConnectionRepository {

    private final JdbcTemplate jdbc;
    private final ObjectMapper mapper;

    public PostgresOutboundConnectionRepository(JdbcTemplate jdbc, ObjectMapper mapper) {
        this.jdbc = jdbc;
        this.mapper = mapper;
    }

    @Override
    public OutboundConnection save(OutboundConnection c) {
        boolean exists = findById(c.tenantId(), c.id()).isPresent();
        if (exists) {
            jdbc.update("""
                UPDATE outbound_connections
                   SET name = ?, description = ?, url = ?, method = ?::outbound_method_enum,
                       default_headers = ?::jsonb, default_body_tmpl = ?,
                       tls_trust_mode = ?::trust_mode_enum, tls_ca_pem_paths = ?::jsonb,
                       hmac_secret_ciphertext = ?, auth_kind = ?::outbound_auth_kind_enum,
                       auth_config = ?::jsonb, allowed_environment_ids = ?,
                       updated_at = now(), updated_by = ?
                 WHERE tenant_id = ? AND id = ?""",
                c.name(), c.description(), c.url(), c.method().name(),
                writeJson(c.defaultHeaders()), c.defaultBodyTmpl(),
                c.tlsTrustMode().name(), writeJson(c.tlsCaPemPaths()),
                c.hmacSecretCiphertext(), c.auth().kind().name(),
                writeJson(c.auth()), toUuidArray(c.allowedEnvironmentIds()),
                c.updatedBy(), c.tenantId(), c.id());
        } else {
            jdbc.update("""
                INSERT INTO outbound_connections (
                    id, tenant_id, name, description, url, method,
                    default_headers, default_body_tmpl,
                    tls_trust_mode, tls_ca_pem_paths,
                    hmac_secret_ciphertext, auth_kind, auth_config,
                    allowed_environment_ids, created_by, updated_by)
                VALUES (?,?,?,?,?,?::outbound_method_enum,
                        ?::jsonb,?,
                        ?::trust_mode_enum,?::jsonb,
                        ?,?::outbound_auth_kind_enum,?::jsonb,
                        ?,?,?)""",
                c.id(), c.tenantId(), c.name(), c.description(), c.url(), c.method().name(),
                writeJson(c.defaultHeaders()), c.defaultBodyTmpl(),
                c.tlsTrustMode().name(), writeJson(c.tlsCaPemPaths()),
                c.hmacSecretCiphertext(), c.auth().kind().name(), writeJson(c.auth()),
                toUuidArray(c.allowedEnvironmentIds()), c.createdBy(), c.updatedBy());
        }
        return findById(c.tenantId(), c.id()).orElseThrow();
    }

    @Override
    public Optional<OutboundConnection> findById(String tenantId, UUID id) {
        return jdbc.query("SELECT * FROM outbound_connections WHERE tenant_id = ? AND id = ?",
                rowMapper, tenantId, id).stream().findFirst();
    }

    @Override
    public Optional<OutboundConnection> findByName(String tenantId, String name) {
        return jdbc.query("SELECT * FROM outbound_connections WHERE tenant_id = ? AND name = ?",
                rowMapper, tenantId, name).stream().findFirst();
    }

    @Override
    public List<OutboundConnection> listByTenant(String tenantId) {
        return jdbc.query("SELECT * FROM outbound_connections WHERE tenant_id = ? ORDER BY name",
                rowMapper, tenantId);
    }

    @Override
    public void delete(String tenantId, UUID id) {
        jdbc.update("DELETE FROM outbound_connections WHERE tenant_id = ? AND id = ?", tenantId, id);
    }

    private final RowMapper<OutboundConnection> rowMapper = (rs, i) -> new OutboundConnection(
            rs.getObject("id", UUID.class), rs.getString("tenant_id"),
            rs.getString("name"), rs.getString("description"),
            rs.getString("url"), OutboundMethod.valueOf(rs.getString("method")),
            readMapString(rs.getString("default_headers")), rs.getString("default_body_tmpl"),
            TrustMode.valueOf(rs.getString("tls_trust_mode")),
            readListString(rs.getString("tls_ca_pem_paths")),
            rs.getString("hmac_secret_ciphertext"),
            readAuth(OutboundAuthKind.valueOf(rs.getString("auth_kind")), rs.getString("auth_config")),
            readUuidArray(rs.getArray("allowed_environment_ids")),
            rs.getTimestamp("created_at").toInstant(), rs.getString("created_by"),
            rs.getTimestamp("updated_at").toInstant(), rs.getString("updated_by"));

    private String writeJson(Object v) {
        try { return mapper.writeValueAsString(v); }
        catch (Exception e) { throw new IllegalStateException(e); }
    }

    private Map<String, String> readMapString(String json) {
        try { return mapper.readValue(json, new TypeReference<>() {}); }
        catch (Exception e) { throw new IllegalStateException(e); }
    }

    private List<String> readListString(String json) {
        try { return mapper.readValue(json, new TypeReference<>() {}); }
        catch (Exception e) { throw new IllegalStateException(e); }
    }

    private OutboundAuth readAuth(OutboundAuthKind kind, String cfg) {
        try {
            return switch (kind) {
                case NONE   -> new OutboundAuth.None();
                case BEARER -> mapper.readValue(cfg, OutboundAuth.Bearer.class);
                case BASIC  -> mapper.readValue(cfg, OutboundAuth.Basic.class);
            };
        } catch (Exception e) { throw new IllegalStateException(e); }
    }

    private Array toUuidArray(List<UUID> ids) {
        return jdbc.execute((ConnectionCallback<Array>) conn ->
                conn.createArrayOf("uuid", ids.toArray()));
    }

    private List<UUID> readUuidArray(Array arr) throws SQLException {
        if (arr == null) return List.of();
        Object[] raw = (Object[]) arr.getArray();
        List<UUID> out = new ArrayList<>(raw.length);
        for (Object o : raw) out.add((UUID) o);
        return out;
    }
}
