package com.codebyte.api.support;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.time.Duration;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;

/**
 * Base class for integration tests that need a real PostgreSQL.
 *
 * <p>A single container is started once for the whole JVM (the "singleton container" pattern) and
 * shared across every test class that extends this base, rather than one container per class — this
 * keeps the suite fast. The container is never explicitly stopped; the JVM shutdown (and Ryuk, when
 * enabled) reaps it.
 *
 * <p>Data isolation is the individual test's responsibility. Persistence/repository tests annotate
 * with {@code @Transactional} so each test rolls back; full-HTTP web tests (where a rollback cannot
 * span the request boundary) clean up explicitly.
 */
public abstract class AbstractPostgresIT {

    protected static final PostgreSQLContainer<?> POSTGRES =
            new PostgreSQLContainer<>("postgres:16-alpine");

    static {
        POSTGRES.start();
        // Testcontainers waits for Postgres inside the container, but some Docker hosts
        // (e.g. Colima) establish the host-side port forward slightly later. Wait until the
        // mapped port is actually reachable from the host so the first test doesn't race it.
        awaitHostPortReachable(POSTGRES.getHost(), POSTGRES.getFirstMappedPort());
    }

    private static void awaitHostPortReachable(String host, int port) {
        Duration timeout = Duration.ofSeconds(30);
        long deadline = System.nanoTime() + timeout.toNanos();
        IOException last = null;
        while (System.nanoTime() < deadline) {
            try (Socket socket = new Socket()) {
                socket.connect(new InetSocketAddress(host, port), 1000);
                return;
            } catch (IOException e) {
                last = e;
                try {
                    Thread.sleep(250);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new IllegalStateException(
                            "Interrupted waiting for " + host + ":" + port, ie);
                }
            }
        }
        throw new IllegalStateException(
                "Database port " + host + ":" + port + " not reachable within " + timeout, last);
    }

    @DynamicPropertySource
    static void datasourceProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", POSTGRES::getJdbcUrl);
        registry.add("spring.datasource.username", POSTGRES::getUsername);
        registry.add("spring.datasource.password", POSTGRES::getPassword);
        // A fixed >=32-byte HS256 secret for tests (never a real deployment secret).
        registry.add("app.jwt.secret", () -> "test-only-hs256-signing-secret-0123456789-abcdef");
    }
}
