package be.iminds.ilabt.jfed.lowlevel.api_scripts;

import be.iminds.ilabt.jfed.log.Logger;
import be.iminds.ilabt.jfed.lowlevel.*;
import be.iminds.ilabt.jfed.lowlevel.api.user_spec.UserSpec;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.UserAndSliceApiWrapper;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.impl.AutomaticUserAndSliceApiWrapper;
import be.iminds.ilabt.jfed.util.GeniUrn;
import be.iminds.ilabt.jfed.util.IOUtils;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.inject.Inject;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

/**
 * GetUsersInProject
 * <p/>
 * A script to fetch all users in a project + their public keys
 */
public class GetUsersInProject {
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(GetUsersInProject.class);

    private final Logger logger;

    private final UserAndSliceApiWrapper samaWrapper;
    private final GeniUserProvider geniUserProvider;

    @Inject
    public GetUsersInProject(Logger logger,
                             AutomaticUserAndSliceApiWrapper.AutomaticUserAndSliceApiWrapperFactory automaticUserAndSliceApiWrapperFactory,
                             GeniUserProvider geniUserProvider) {
        this.logger = logger;
        this.geniUserProvider = geniUserProvider;

        this.samaWrapper = automaticUserAndSliceApiWrapperFactory.create();
    }

    private boolean sliceChecked = false;
    private boolean sliceOk = false;
    private UserAndSliceApiWrapper.SliceInfo sliceInfo = null;

    /**
     * check and if not existing, create dummy slice.
     */
    public UserAndSliceApiWrapper.SliceInfo checkDummySlice(@Nonnull String projectName) throws JFedException {
        String authUrnPart =
                geniUserProvider.getLoggedInGeniUser().getUserUrn().getEncodedTopLevelAuthority_withoutSubAuth();
        GeniUrn sliceUrn = GeniUrn.createGeniUrnFromEncodedParts(authUrnPart + ":" + projectName, "slice", "dummy");

        if (sliceChecked) {
            if (sliceOk && sliceInfo != null && !sliceInfo.getCredentials().isEmpty())
                return sliceInfo;
            else
                throw new RuntimeException("Something went wrong with slice last time. Not trying again.");
        }

        sliceOk = false;
        try {
            samaWrapper.getLocalUserCredentials(logger);
            List<AnyCredential> sliceCredentials = samaWrapper.getSliceCredentials(logger, sliceUrn);
            if (sliceCredentials != null && !sliceCredentials.isEmpty()) {
                sliceInfo =
                        new UserAndSliceApiWrapper.SliceInfo(sliceUrn.getEncodedResourceName(), sliceUrn, sliceCredentials);
                sliceOk = true;
            } else {
                sliceInfo = null;
                LOG.error("getSliceCredentials problem: sliceCredentials='{}'", sliceCredentials);
            }
        } catch (JFedException e) {
            LOG.info("Could not get slice credential. Probably slice does not exist.", e);
            sliceInfo = null;
        } finally {
            sliceChecked = true;
        }

        if (sliceInfo == null) {
            Date expirationDate = new Date(System.currentTimeMillis() + (2 * 24 * 60 * 60 * 1000L)); //2 days
            sliceInfo =
                    samaWrapper.createSlice(logger, sliceUrn.getEncodedResourceName(), expirationDate, sliceUrn.getEncodedSubAuthName());
            sliceOk = sliceInfo != null && !sliceInfo.getCredentials().isEmpty();
            if (!sliceOk) {
                LOG.error("Slice creation problem: sliceInfo='{}'", sliceInfo);
            }
        } else {
            LOG.info("Got sliceInfo='{}'", sliceInfo);
        }

        if (sliceOk)
            return sliceInfo;
        else
            throw new RuntimeException("Something went wrong with slice creation.");
    }

    @ApiMethod(order = 1, hint = "Get All SSH Keys for all users in a specified project")
    public List<UserSpec> getUsersInProject(@ApiMethodParameter(name = "projectName", hint = "Name of the project/subauthority", parameterType = ApiMethodParameterType.STRING)
                                            @Nonnull String projectName) throws JFedException {
        samaWrapper.getLocalUserCredentials(logger);
        LOG.info("Got credential for user");

        List<GeniUrn> users = samaWrapper.getUsersForSubAuthority(logger, projectName);
        LOG.info("Got users in project {}: {}", projectName, users);


        List<UserSpec> res = new ArrayList<>();
        for (GeniUrn user : users) {
            boolean mustGetKeys = true;
            boolean triedAddingSingleToSlice = false;
            int tryCount = 3;
            while (mustGetKeys) {
                if (tryCount-- <= 0)
                    throw new RuntimeException("Implementation error: loop safety triggered"); //just a simple precaution to break infinite loops
                try {
                    List<String> sshKeys = samaWrapper.getSshKeysForUser(logger, user);
                    String keyInfo = "   Got user " + user + " SSH keys:";
                    for (String sshKey : sshKeys)
                        if (sshKey.length() > 30)
                            keyInfo +=
                                    "\n      - " + sshKey.substring(0, 15) + " ... " + sshKey.substring(sshKey.length() - 10);
                        else
                            keyInfo += "\n      - " + sshKey;
                    LOG.info(keyInfo);
                    res.add(new UserSpec(user.getValue(), sshKeys));
                    mustGetKeys = false;
                } catch (Exception e) {
                    mustGetKeys = false;

                    if (!triedAddingSingleToSlice) {
                        LOG.error("Failed to get SSH key for user " + user + ". Will try to add them to slice first.", e);

                        try {
                            UserAndSliceApiWrapper.SliceInfo slice = checkDummySlice(projectName);
                            samaWrapper.addUsersToSlice(logger, slice.getUrn(), slice.getCredentials(), Collections.singletonList(user), "MEMBER");
                            mustGetKeys = true;
                        } catch (Exception e2) {
                            LOG.error("Failed to add user to slice.", e2);
                            System.err.println("Failed to add user to slice.");
                            e2.printStackTrace();
                        } finally {
                            triedAddingSingleToSlice = true;
                        }
                    } else {
                        LOG.error("Failed to get SSH key for user " + user + " despite adding user to slice first.", e);
                    }
                }
            }
        }

        return res;
    }

    /* create a dir for each user, in the target dir.
     * The dirname is: user_{USERNAME}
      *Each dir contains the following files:
      * - public_key_openssh.pub
      * - username  (file containing username)
      * - public_key.pem  (optional, if public_key_openssh.pub is not provided, bastion will create it from this)
      * - cert.pem (optional, not used by bastion scripts)
      */
    public void save(String projectName, File dir) throws JFedException {
        List<UserSpec> users = getUsersInProject(projectName);
        for (UserSpec user : users) {
            GeniUrn urn = GeniUrn.parse(user.getUrn());
            assert urn != null;
            String username = urn.getEncodedResourceName();
            if (username.length() > 8) {
                String shortUsername = username.replaceAll("[_ -]", "").toLowerCase();
                if (shortUsername.length() > 8)
                    shortUsername = shortUsername.substring(0, 8);
                username = shortUsername;
            }

            File userDir = new File(dir, "user_" + username);
            if (!userDir.exists() || !userDir.isDirectory()) {
                boolean success = userDir.mkdirs();
                if (!success) throw new RuntimeException("Failed to create dir \"" + userDir + "\"");
            }

            File usernameFile = new File(userDir, "username");
            File pubKeyFile = new File(userDir, "public_key_openssh.pub");
            String pubKeys = "";
            for (String pubKey : user.getSshKey())
                pubKeys += pubKey + "\n";
            IOUtils.stringToFile(usernameFile, username);
            IOUtils.stringToFile(pubKeyFile, pubKeys);
        }
    }
}
