package be.iminds.ilabt.jfed.util;

import be.iminds.ilabt.jfed.lowlevel.GeniUser;
import be.iminds.ilabt.jfed.lowlevel.GeniUserWithSpeaksFor;
import be.iminds.ilabt.jfed.lowlevel.connection.JFedConnection;
import be.iminds.ilabt.jfed.rspec.model.BasicStringRspec;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * BestNodeLoginFinder
 */
public class BestNodeLoginFinder {
    private final BasicStringRspec basicStringRspec;
    private final String preferredUserName;
    private final Feedback feedback;
    private final GeniUser loggedInUser;

    public interface Feedback {
        void info(String s);
        void error(String s);
        void debug(String s);
    }

    public BestNodeLoginFinder(BasicStringRspec basicStringRspec, String preferredUserName, GeniUser loggedInUser, Feedback feedback) {
        this.basicStringRspec = basicStringRspec;
        this.preferredUserName = preferredUserName;
        this.loggedInUser = loggedInUser;
        this.feedback = feedback;
    }

    public BasicStringRspec.LoginService findBestLogin(String nodeUniqueId) {
        List<BasicStringRspec.BasicNodeInfo> basicNodeInfos = basicStringRspec.getBasicNodeInfo();
        int nodeCount = basicNodeInfos.size();
        BasicStringRspec.BasicNodeInfo basicNodeInfo = null;
        for (BasicStringRspec.BasicNodeInfo cur : basicNodeInfos) {
            if (Objects.equals(cur.getUniqueId(), nodeUniqueId))
                basicNodeInfo = cur;
        }
        String authUrnString = basicNodeInfo == null ? null : basicNodeInfo.getComponentManagerId();


        List<BasicStringRspec.LoginService> loginServiceList =
                basicStringRspec.findNodeLoginInfoByUniqueId(nodeUniqueId);


        if (preferredUserName != null) {
            feedback.info("Configuration: nodelogin_preferred_user=" + preferredUserName);
        }

        int loginCount = loginServiceList.size();
        if (loginCount > 0) {
            if (loginCount > 1) {
                List<JFedConnection.SshProxyInfo> proxies = new ArrayList<>();
                for (BasicStringRspec.LoginService loginService : loginServiceList) {
                    JFedConnection.SshProxyInfo sshProxy = loginService.getSshProxy();
                    if (sshProxy != null) {
                        proxies.add(sshProxy);
                        feedback.info("Found proxy " +
                                sshProxy.getUsername() + "@" + sshProxy.getHostname() + ":" + sshProxy.getPort() +
                                "  for login " +
                                loginService.getUsername() + "@" + loginService.getHostname() + ":" + loginService.getPort());
                    }
                }

                List<BasicStringRspec.LoginService> loginServicesMatchingLoginUsername = new ArrayList<>();
                List<BasicStringRspec.LoginService> loginServicesMatchingProxy = new ArrayList<>();
                List<BasicStringRspec.LoginService> loginServicesMatchingSpecialName = new ArrayList<>();
                List<BasicStringRspec.LoginService> loginServicesMatchingPreferredUser = new ArrayList<>();
                List<BasicStringRspec.LoginService> loginServicesMatchingNothing = new ArrayList<>();

                String loggedInUserName = null;
                if (loggedInUser != null) {
                    if (loggedInUser instanceof GeniUserWithSpeaksFor) {
                        GeniUserWithSpeaksFor geniUserWithSpeaksFor = ((GeniUserWithSpeaksFor) loggedInUser);
                        loggedInUserName = geniUserWithSpeaksFor.getSpeaksForUrn().getResourceName();
                    } else {
                        loggedInUserName = loggedInUser.getUserUrn().getResourceName();
                    }
                }

                for (BasicStringRspec.LoginService loginService : loginServiceList) {
                    String hostname = loginService.getHostname();
                    String username = loginService.getUsername();
                    int port = loginService.getPort();
                    JFedConnection.SshProxyInfo sshProxy = loginService.getSshProxy();
                    assert port > 0;
                    if (hostname == null) {
                        feedback.error("hostname is null for login service");
                        continue;
                    }
                    if (username == null) {
                        feedback.error("username is null for login service");
                        continue;
                    }

                    boolean isPreferredUser =
                            preferredUserName != null && Objects.equals(preferredUserName, username);
                    boolean isLoggedInUserName =
                            loggedInUserName != null && Objects.equals(username, loggedInUserName);

                    boolean isProxy = false;
                    for (JFedConnection.SshProxyInfo proxy : proxies) {
                        if (Objects.equals(proxy.getUsername(), username) &&
                                Objects.equals(proxy.getHostname(), hostname) &&
                                proxy.getPort() == port)
                            isProxy = true;
                    }

                    boolean isSpecialName = false;
                    if (username.equalsIgnoreCase("root"))
                        isSpecialName = true;
                    if (username.equalsIgnoreCase("admin"))
                        isSpecialName = true;

                    if (isProxy) {
                        loginServicesMatchingProxy.add(loginService);
                        feedback.info("The login " + username + "@" + hostname + ":" + port + " is a proxy login");
                    } else {
                        if (isPreferredUser) {
                            loginServicesMatchingPreferredUser.add(loginService);
                            feedback.info("The login " + username + "@" + hostname + ":" + port + " is a user preferred login");
                        } else {
                            if (isLoggedInUserName) {
                                loginServicesMatchingLoginUsername.add(loginService);
                                feedback.info("The login " + username + "@" + hostname + ":" + port + " matches the user account name");
                            } else {
                                if (isSpecialName) {
                                    loginServicesMatchingSpecialName.add(loginService);
                                    feedback.info("The login " + username + "@" + hostname + ":" + port + " is a super user login");
                                } else {
                                    loginServicesMatchingNothing.add(loginService);
                                    feedback.info("The login " + username + "@" + hostname + ":" + port + " is an normal login");
                                }
                            }
                        }
                    }
                }

                BasicStringRspec.LoginService chosenLoginService = null;
                if (!loginServicesMatchingPreferredUser.isEmpty()) {
                    chosenLoginService = loginServicesMatchingPreferredUser.get(0);
                    feedback.info("Found multiple node logins in manifest rspec (" + loginCount + " service logins for " + nodeCount + " nodes), " +
                            "using login info for preferred user \"" + chosenLoginService.getUsername() + "\"." + (authUrnString == null ? "" : " (for nodes of auth " + authUrnString + ")"));
                } else {
                    if (!loginServicesMatchingLoginUsername.isEmpty())
                        chosenLoginService = loginServicesMatchingLoginUsername.get(0);
                    if (chosenLoginService == null && !loginServicesMatchingNothing.isEmpty())
                        chosenLoginService = loginServicesMatchingNothing.get(0);
                    if (chosenLoginService == null && !loginServicesMatchingSpecialName.isEmpty())
                        chosenLoginService = loginServicesMatchingSpecialName.get(0);
                    if (chosenLoginService == null && !loginServicesMatchingProxy.isEmpty())
                        chosenLoginService = loginServicesMatchingProxy.get(0);

                    if (chosenLoginService != null) {
                        feedback.info("Found multiple node logins in manifest rspec (" + loginCount + " service logins for " + nodeCount + " nodes), " +
                                "Chose login info for user \"" + chosenLoginService.getUsername() + "\"." + (authUrnString == null ? "" : " (for nodes of auth " + authUrnString + ")"));
                    } else {
                        feedback.error("Found multiple node logins in manifest rspec (" + loginCount + " service logins for " + nodeCount + " nodes), " +
                                " but the code failed to choose one. This is a bug!");
                        return null;
                    }
                }

                return chosenLoginService;
            } else {
                BasicStringRspec.LoginService onlyLoginService = loginServiceList.get(0);
                if (onlyLoginService.getHostname() == null) {
                    feedback.error("hostname is null for login service");
                    return null;
                }
                if (onlyLoginService.getUsername() == null) {
                    feedback.error("username is null for login service");
                    return null;
                }

                return onlyLoginService;
            }
        } else {
            feedback.info("Found no node service login in manifest RSpec" + (authUrnString == null ? "" : " (for nodes of auth " + authUrnString + ")"));
            return null;
        }
    }
}
