import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.Instant;

/** JDBC issuance examples for schema.sql. Caller owns commit/rollback and publishes only after commit. */
public final class IssueCode {
    private static final int MAX_RETRIES = 32;
    public record Issued(long id, String code) {}

    public static Issued random(Connection db, Instant expiresAt) throws SQLException {
        String sql = """
            INSERT INTO redeem_code(code, method, expires_at)
            VALUES (?, 'RANDOM', ?)
            ON CONFLICT (code) DO NOTHING RETURNING id
            """;
        try (PreparedStatement insert = db.prepareStatement(sql)) {
            for (int attempt = 0; attempt < MAX_RETRIES; attempt++) {
                String code = VerifyCodes.randomCode();
                insert.setString(1, code);
                insert.setTimestamp(2, Timestamp.from(expiresAt));
                try (ResultSet rows = insert.executeQuery()) {
                    if (rows.next()) return new Issued(rows.getLong("id"), code);
                }
            }
        }
        throw new SQLException("random issuance retry limit reached");
    }

    public static Issued ff1(Connection db, RedeemCodeFf1 codec, short keyVersion, Instant expiresAt)
            throws SQLException {
        if (keyVersion <= 0) throw new IllegalArgumentException("positive key version required");
        String sql = """
            INSERT INTO redeem_code(id, code, method, key_version, expires_at)
            VALUES (?, ?, 'FF1', ?, ?)
            ON CONFLICT (code) DO NOTHING RETURNING id
            """;
        try (PreparedStatement reserve = db.prepareStatement("SELECT nextval('redeem_id_seq') AS id");
             PreparedStatement insert = db.prepareStatement(sql)) {
            for (int attempt = 0; attempt < MAX_RETRIES; attempt++) {
                long id;
                try (ResultSet rows = reserve.executeQuery()) {
                    if (!rows.next()) throw new SQLException("no reserved ID returned");
                    id = rows.getLong("id");
                }
                String code = codec.encode(id);
                insert.setLong(1, id);
                insert.setString(2, code);
                insert.setShort(3, keyVersion);
                insert.setTimestamp(4, Timestamp.from(expiresAt));
                try (ResultSet rows = insert.executeQuery()) {
                    if (rows.next()) return new Issued(rows.getLong("id"), code);
                }
                // A conflicting code requires a NEW reserved ID, never re-encryption of this ID.
            }
        }
        throw new SQLException("FF1 issuance retry limit reached");
    }
}
