package Zookeeper;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ServiceRegistry implements Watcher {
    private static final String REGISTRY_ZNODE = "/service_registry";
    private final Logger logger = LoggerFactory.getLogger(ServiceRegistry.class);
    private final ZooKeeper zooKeeper;
    private String currentZnode = null;
    private List<String> allServiceAddresses = null;
    public List<String> getAllServiceAddresses() {
        return allServiceAddresses;
    }
    public ServiceRegistry(ZooKeeper zooKeeper) {
        this.zooKeeper = zooKeeper;
        createServiceRegistryZnode();
    }

    private void createServiceRegistryZnode() {
        try {
            if (zooKeeper.exists(REGISTRY_ZNODE, false) == null) {
                zooKeeper.create(REGISTRY_ZNODE, new byte[]{}, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        } catch (KeeperException e) {
            e.printStackTrace();
            logger.error("Error creating service registry znode: {}", e.getMessage());

        } catch (InterruptedException e) {
            e.printStackTrace();
            logger.error("Interrupted while creating service registry znode: {}", e.getMessage());
        }
    }

    public void registerToCluster(String metadata) throws KeeperException, InterruptedException {
        if (this.currentZnode != null) {
            System.out.println("Already registered to service registry");
            logger.info("Already registered to service registry");

            return;
        }
        this.currentZnode = zooKeeper.create(REGISTRY_ZNODE + "/n_", metadata.getBytes(),
                ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        System.out.println("Registered to service registry");
        logger.info("Registered to service registry");
    }
    public void registerForUpdates(Autohealer autohealer) {
        try {
            updateAddresses(autohealer);
        } catch (KeeperException e) {
            e.printStackTrace();
            logger.error("Error registering for updates: {}", e.getMessage());

        } catch (InterruptedException e) {
            e.printStackTrace();
            logger.error("Interrupted while registering for updates: {}", e.getMessage());
        }
    }

    public void unregisterFromCluster() {
        try {
            if (currentZnode != null && zooKeeper.exists(currentZnode, false) != null) {
                zooKeeper.delete(currentZnode, -1);
            }
        } catch (KeeperException e) {
            e.printStackTrace();
            logger.error("Error unregistering from cluster: {}", e.getMessage());

        } catch (InterruptedException e) {
            e.printStackTrace();
            logger.error("Interrupted while unregistering from cluster: {}", e.getMessage());
        }
    }
    Autohealer autohealer1;
    private synchronized void updateAddresses(Autohealer autohealer) throws KeeperException, InterruptedException {
        autohealer1 = autohealer;
        List<String> workerZnodes = zooKeeper.getChildren(REGISTRY_ZNODE, this);

        List<String> addresses = new ArrayList<>(workerZnodes.size());

        for (String workerZnode : workerZnodes) {
            String workerFullPath = REGISTRY_ZNODE + "/" + workerZnode;
            Stat stat = zooKeeper.exists(workerFullPath, false);
            if (stat == null) {
                continue;
            }

            byte[] addressBytes = zooKeeper.getData(workerFullPath, false, stat);
            String address = new String(addressBytes);
            addresses.add(address);
        }

        this.allServiceAddresses = Collections.unmodifiableList(addresses);
        System.out.println("The cluster addresses are: " + this.allServiceAddresses);
        logger.info("The cluster addresses are: {}", this.allServiceAddresses);

        autohealer.startWatchingWorkers();
    }

    @Override
    public void process(WatchedEvent watchedEvent) {
        try {
            updateAddresses(autohealer1);
        } catch (KeeperException e) {
            logger.error("Error processing watched event: {}", e.getMessage());
            throw new RuntimeException(e);
        } catch (InterruptedException e) {
            logger.error("Interrupted while processing watched event: {}", e.getMessage());
            throw new RuntimeException(e);
        }
    }
}
