package dev.quicproquo;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;

/**
 * High-level quicproquo client for JVM.
 *
 * <p>Wraps {@code libquicproquo_ffi} via JNI.
 *
 * <pre>{@code
 * try (QpqClient client = new QpqClient("127.0.0.1:5001", "ca.pem")) {
 *     client.login("alice", "secret");
 *     client.send("bob", "hello".getBytes());
 *     List<String> messages = client.receive(5000);
 * }
 * }</pre>
 */
public final class QpqClient implements AutoCloseable {
    private volatile long handle;
    private final Gson gson = new Gson();
    private static final Type STRING_LIST_TYPE =
            new TypeToken<List<String>>() {}.getType();

    /**
     * Connect to a quicproquo server.
     *
     * @param server    server address as {@code host:port}
     * @param caCertPath path to PEM-encoded CA certificate
     */
    public QpqClient(String server, String caCertPath) {
        this(server, caCertPath, server.split(":")[0]);
    }

    /**
     * Connect to a quicproquo server with explicit TLS server name.
     *
     * @param server     server address as {@code host:port}
     * @param caCertPath path to PEM-encoded CA certificate
     * @param serverName TLS SNI server name
     */
    public QpqClient(String server, String caCertPath, String serverName) {
        long h = NativeBridge.nativeConnect(server, caCertPath, serverName);
        if (h == 0) {
            throw new QpqException("qpq_connect failed for " + server);
        }
        this.handle = h;
    }

    /** Whether the client is connected. */
    public boolean isConnected() {
        return handle != 0;
    }

    /**
     * Authenticate with OPAQUE credentials.
     *
     * @throws QpqAuthException on bad credentials
     */
    public void login(String username, String password) {
        checkConnected();
        int code = NativeBridge.nativeLogin(handle, username, password);
        checkStatus(code);
    }

    /**
     * Send a message to a recipient by username.
     *
     * @param recipient recipient username
     * @param message   message payload (arbitrary bytes)
     */
    public void send(String recipient, byte[] message) {
        checkConnected();
        int code = NativeBridge.nativeSend(handle, recipient, message);
        checkStatus(code);
    }

    /**
     * Receive pending messages, blocking up to {@code timeoutMs} milliseconds.
     *
     * @return list of message strings (UTF-8)
     */
    public List<String> receive(int timeoutMs) {
        checkConnected();
        String json = NativeBridge.nativeReceive(handle, timeoutMs);
        if (json == null) return Collections.emptyList();
        return gson.fromJson(json, STRING_LIST_TYPE);
    }

    /** Disconnect from the server and release resources. */
    @Override
    public void close() {
        long h = handle;
        if (h != 0) {
            handle = 0;
            NativeBridge.nativeDisconnect(h);
        }
    }

    /** Alias for {@link #close()}. */
    public void disconnect() {
        close();
    }

    private void checkConnected() {
        if (handle == 0) throw new QpqException("not connected");
    }

    private void checkStatus(int code) {
        if (code == NativeBridge.QPQ_OK) return;
        String msg = NativeBridge.nativeLastError(handle);
        if (msg == null) msg = "unknown error";
        switch (code) {
            case NativeBridge.QPQ_AUTH_FAILED:
                throw new QpqAuthException(msg);
            case NativeBridge.QPQ_TIMEOUT:
                throw new QpqTimeoutException(msg);
            case NativeBridge.QPQ_NOT_CONNECTED:
                throw new QpqException("not connected: " + msg);
            default:
                throw new QpqException(msg);
        }
    }
}
