package com.steerinfo.meterwork.rtdb;

import com.steerinfo.rtdb.Client;
import com.steerinfo.rtdb.ClientConfig;
import com.steerinfo.rtdb.ClientImpl;
import com.steerinfo.rtdb.SingleSocketChannelProviderImpl;
import com.steerinfo.rtdb.SocketChannelProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 基于 icore-rtdb 的多 Client 连接池（借用/归还 + 故障重建）。
 */
public class RtdbClientPool implements AutoCloseable {

    private static final Logger log = LoggerFactory.getLogger(RtdbClientPool.class);

    private final RtdbPoolProperties properties;
    private final BlockingQueue<Client> idle;
    private final AtomicBoolean closed = new AtomicBoolean(false);
    private final int capacity;
    private final java.util.concurrent.atomic.AtomicInteger created = new java.util.concurrent.atomic.AtomicInteger(0);

    public RtdbClientPool(RtdbPoolProperties properties) {
        this.properties = properties;
        this.capacity = Math.max(1, properties.getPool().getSize());
        this.idle = new ArrayBlockingQueue<>(capacity);
        // 启动时尝试预创建，失败不阻断应用（借用时再创建）
        for (int i = 0; i < capacity; i++) {
            try {
                idle.offer(createClient());
                created.incrementAndGet();
            } catch (Exception e) {
                log.warn("RTDB pre-create client failed ({}/{}): {}", i + 1, capacity, e.getMessage());
                break;
            }
        }
        log.info("RtdbClientPool initialized: address={}, preCreated={}/{}",
                properties.address(), created.get(), capacity);
    }

    public Client borrow() throws InterruptedException {
        ensureOpen();
        Client client = idle.poll(properties.getPool().getBorrowTimeoutMs(), TimeUnit.MILLISECONDS);
        if (client == null) {
            if (created.get() < capacity) {
                try {
                    client = createClient();
                    created.incrementAndGet();
                } catch (Exception e) {
                    throw new IllegalStateException("RTDB pool empty and create failed: " + e.getMessage(), e);
                }
            } else {
                throw new IllegalStateException("RTDB pool borrow timeout after "
                        + properties.getPool().getBorrowTimeoutMs() + "ms");
            }
        }
        if (!isHealthy(client)) {
            closeQuietly(client);
            created.decrementAndGet();
            client = createClient();
            created.incrementAndGet();
        }
        return client;
    }

    public void release(Client client) {
        if (client == null || closed.get()) {
            closeQuietly(client);
            if (client != null) {
                created.decrementAndGet();
            }
            return;
        }
        if (!idle.offer(client)) {
            closeQuietly(client);
            created.decrementAndGet();
        }
    }

    /**
     * 业务失败时丢弃坏连接并补一个新连接回池。
     */
    public void invalidate(Client client) {
        closeQuietly(client);
        created.decrementAndGet();
        if (closed.get()) {
            return;
        }
        try {
            idle.offer(createClient());
            created.incrementAndGet();
        } catch (Exception e) {
            log.warn("Failed to recreate RTDB client after invalidate: {}", e.getMessage());
        }
    }

    public <T> T execute(RtdbCallback<T> callback) {
        Client client = null;
        boolean borrowed = false;
        try {
            client = borrow();
            borrowed = true;
            return callback.doInRtdb(client);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new IllegalStateException("Interrupted while borrowing RTDB client", e);
        } catch (RuntimeException e) {
            if (borrowed && client != null) {
                invalidate(client);
                borrowed = false;
                client = null;
            }
            throw e;
        } catch (Exception e) {
            if (borrowed && client != null) {
                invalidate(client);
                borrowed = false;
                client = null;
            }
            throw new IllegalStateException("RTDB operation failed", e);
        } finally {
            if (borrowed && client != null) {
                release(client);
            }
        }
    }

    private Client createClient() {
        ClientConfig config = new ClientConfig();
        config.username = properties.getUsername();
        config.password = properties.getPassword() == null ? "" : properties.getPassword();
        SocketChannelProvider socket =
                new SingleSocketChannelProviderImpl(properties.address());
        return new ClientImpl(socket, config);
    }

    private boolean isHealthy(Client client) {
        try {
            if (client == null || client.isClosed() || !client.isAlive()) {
                return false;
            }
            client.syncOps().ping();
            return true;
        } catch (Exception e) {
            log.warn("RTDB client ping failed: {}", e.getMessage());
            return false;
        }
    }

    private void ensureOpen() {
        if (closed.get()) {
            throw new IllegalStateException("RtdbClientPool is closed");
        }
    }

    private void closeQuietly(Client client) {
        if (client == null) {
            return;
        }
        try {
            client.close();
        } catch (Exception ignored) {
            // ignore
        }
    }

    @Override
    public void close() {
        if (!closed.compareAndSet(false, true)) {
            return;
        }
        Client client;
        while ((client = idle.poll()) != null) {
            closeQuietly(client);
        }
        log.info("RtdbClientPool closed");
    }

    @FunctionalInterface
    public interface RtdbCallback<T> {
        T doInRtdb(Client client) throws Exception;
    }
}
