package fr.titionfire.ffsaf.net2;

import com.fasterxml.jackson.databind.JsonNode;
import fr.titionfire.ffsaf.domain.service.ClubService;
import fr.titionfire.ffsaf.domain.service.MembreService;
import fr.titionfire.ffsaf.net2.packet.IAction;
import fr.titionfire.ffsaf.net2.packet.RegisterAction;
import fr.titionfire.ffsaf.utils.Pair;
import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.StartupEvent;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;
import org.quartz.Scheduler;

import javax.net.ssl.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.security.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;

@Singleton
public class ServerCustom extends Thread {
    private static final Logger LOGGER = Logger.getLogger("SocketServer");

    protected ConcurrentHashMap<UUID, Pair<Client_Thread, Message<JsonNode>>> jobData = new ConcurrentHashMap<>();
    protected HashMap<String, IAction> iMap = new HashMap<>();
    public final ArrayList<Client_Thread> clients = new ArrayList<>();

    private SSLServerSocket server;
    private boolean run;

    private static ServerCustom serverCustom;

    @ConfigProperty(name = "internal-port")
    int port;

    @ConfigProperty(name = "certificate_password")
    String certificate_password;

    @ConfigProperty(name = "certificate_file")
    String certificate_file;

    @Inject
    protected Scheduler quartz;

    @Inject
    public MembreService membreService;

    @Inject
    public ClubService clubService;

    private PublicKey publicKey;

    private X509TrustManager tm(KeyStore keystore) throws NoSuchAlgorithmException, KeyStoreException {
        TrustManagerFactory trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustMgrFactory.init(keystore);
        TrustManager[] trustManagers = trustMgrFactory.getTrustManagers();
        for (TrustManager trustManager : trustManagers) {
            if (trustManager instanceof X509TrustManager) {
                return (X509TrustManager) trustManager;
            }
        }
        return null;
    }

    private X509KeyManager km(KeyStore keystore) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
        KeyManagerFactory keyMgrFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyMgrFactory.init(keystore, certificate_password.toCharArray());
        KeyManager[] keyManagers = keyMgrFactory.getKeyManagers();
        for (KeyManager keyManager : keyManagers) {
            if (keyManager instanceof X509KeyManager) {
                return (X509KeyManager) keyManager;
            }
        }
        return null;
    }

    private SSLServerSocket getServerSocketWithCert(int port, InputStream pathToCert) throws Exception {
        TrustManager[] tmm = new TrustManager[1];
        KeyManager[] kmm = new KeyManager[1];

        KeyStore ks = KeyStore.getInstance("JKS");
        ks.load(pathToCert, certificate_password.toCharArray());
        tmm[0] = tm(ks);
        kmm[0] = km(ks);

        SSLContext ctx = SSLContext.getInstance("TLSv1.2");
        ctx.init(kmm, tmm, null);

        publicKey = ks.getCertificate("ffsaf").getPublicKey();

        SSLServerSocketFactory socketFactory = ctx.getServerSocketFactory();
        return (SSLServerSocket) socketFactory.createServerSocket(port);
    }

    @Override
    public void run() {
        try {
            this.run = true;
            this.server = getServerSocketWithCert(this.port, new FileInputStream(new File(this.certificate_file)));
            RegisterAction.register(iMap);
            LOGGER.info("Starting socket server!!");
            while (this.run) {
                Socket socket = this.server.accept();
                Client_Thread cThread = new Client_Thread(this, socket, publicKey);
                synchronized (clients) {
                    this.clients.add(cThread);
                }
                cThread.start();
            }
            LOGGER.info("Shutting down Socket server!!");
        } catch (Exception e) {
            LOGGER.info("Server start failed! (" + e.getMessage() + ")");
            this.interrupt();
        }
    }

    public void onCloseClient(Client_Thread cThread) {
        synchronized (clients) {
            clients.remove(cThread);
        }
    }

    public void stop2() {
        this.run = false;

        try {
            this.server.close();
            clients.forEach(Client_Thread::stopSoket);
        } catch (IOException ignored) {
        }
    }

    void onStart(@Observes StartupEvent ev) {
        LOGGER.info("The server is starting...");
        serverCustom = this;
        this.start();
    }

    void onStop(@Observes ShutdownEvent ev) {
        LOGGER.info("The server is stopping...");
        this.stop2();
    }

    public static ServerCustom getInstance(){
        return serverCustom;
    }

    public Stream<Client_Thread> get_clients() {
        return clients.stream();
    }
}
