package com.atlassian.stash.internal.bootstrap;

import com.atlassian.bitbucket.server.ApplicationMode;
import com.atlassian.crowd.embedded.api.Directory;
import com.atlassian.crowd.manager.application.ApplicationManager;
import com.atlassian.crowd.manager.directory.DirectoryManager;
import com.atlassian.extras.api.bitbucket.BitbucketServerLicense;
import com.atlassian.stash.internal.ApplicationConstants;
import com.atlassian.stash.internal.config.ConfigurationService;
import com.atlassian.stash.internal.config.RemoveSetupConfigurationRequest;
import com.atlassian.stash.internal.license.InternalLicenseService;
import com.atlassian.stash.internal.license.LicenseHelper;
import com.atlassian.stash.internal.profiles.Dev;
import com.atlassian.stash.internal.server.ApplicationPropertyDao;
import com.atlassian.stash.internal.server.InternalApplicationPropertiesService;
import com.atlassian.stash.internal.user.GlobalPermissionDao;
import com.atlassian.stash.internal.user.StashUserDao;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Locale;
import java.util.Properties;

import static com.atlassian.bitbucket.util.UrlUtils.uncheckedCreateURI;

/**
 * Operation that sets up all the basic parameters to create an empty working installation.
 *
 * @since 3.8
 */
@Dev
@Component("bootstrapOperation")
public class DevBootstrapOperation extends BootstrapOperation {

    /**
     * 3 Hour license for AMPS devs and testers, clustered
     */
    public static final String LICENSE_CLUSTERED =
            "AAABAA0ODAoPeNptUE1Lw0AUvPdXLHhOybZGsbCgpiE2aJrSDZ6f8dUuJJvy3ibYf2+M22LF63wxM\n" +
                    "1ev+C6yzgopRXi3iORiFopkq8UslNcTdsD7adJD3YEzrVU7qBk9HBOO4BIcqm95EN4EUp7Y1jqoX\n" +
                    "A4NqgdXA7MBKzSyQ/KSFzDWoQVbYfJ5MHQck4r5k+cHu+lROerwMjQZnLVyY9Y9nMKnVdt43bOp0\n" +
                    "DLq4wHHAnpYtMrTyRapR1ot1WNWboLNPNZBWmZRoKPb1FsLIGeRRpuH1vQB1vDPA+ctnhw6Q4zDD\n" +
                    "vpdNO+aN6T1rmQkVnJ22evftUVH1R4Y/975BcASkF4wLQIVAJHuXZz1SsymUm2B5V7p7Pap48xzA\n" +
                    "hROyzM1l9a1OqcWzxseRNmnZ4XqmQ==X02d9";
    private static final Logger log = LoggerFactory.getLogger(DevBootstrapOperation.class);
    private static final List<UserData> ADMINISTRATORS = ImmutableList.of(new UserData("admin", "Administrator", "admin@example.com"));
    private static final List<UserData> USERS = ImmutableList.of(new UserData("user", "User", "user@example.com"));

    @Autowired
    public DevBootstrapOperation(DirectoryManager directoryManager, InternalApplicationPropertiesService propertiesService,
                                 StashUserDao userDao, Properties applicationProperties, ApplicationManager appManager,
                                 LicenseHelper licenseHelper, ConfigurationService configurationService,
                                 GlobalPermissionDao globalPermissionDao, ApplicationPropertyDao applicationPropertyDao,
                                 InternalLicenseService licenseService) {
        super(directoryManager, propertiesService, userDao, applicationProperties, appManager, licenseHelper,
                configurationService, globalPermissionDao, applicationPropertyDao, licenseService);
    }

    @Override
    protected boolean setupLicense(RemoveSetupConfigurationRequest.Builder builder) {
        if (super.setupLicense(builder)) {
            return true;
        }

        // Unlike the regular production automated setup, no license is available early to DefaultLicenseHelper.
        // Therefore the license here must be set directly to the service, which resets the license reference.
        licenseService.set(LICENSE_CLUSTERED);

        return true;
    }

    @Override
    protected boolean setupBaseUrl(RemoveSetupConfigurationRequest.Builder builder) {
        if (super.setupBaseUrl(builder)) {
            return true;
        }

        // This is the default AMPS URL.
        propertiesService.setBaseURL(uncheckedCreateURI(String.format("http://%s:7990/bitbucket", getBaseUrlMachineName())));
        return true;
    }

    @Override
    protected boolean setupUsers(RemoveSetupConfigurationRequest.Builder builder, Directory directory) {
        try {
            boolean isMirror = applicationMode == ApplicationMode.MIRROR;
            BitbucketServerLicense license = licenseHelper.getDecoded();
            if (license == null) {
                throw new IllegalStateException("A license must be set prior to adding dev mode users");
            }

            int licensedUsers = 0;
            if (usersExist()) {
                log.info("Skipping user creation on setup: users have already been created");
                return true;
            }

            for (UserData admin : ADMINISTRATORS) {
                createUser(directory, admin.username, admin.username, admin.displayName, admin.emailAddress, true, true);
                log.info("Created administrator '{}'", admin.username);
                ++licensedUsers;
            }

            if (!isMirror) {
                for (UserData data : USERS) {
                    boolean willExceedLicense = licensedUsers >= license.getMaximumNumberOfUsers();
                    createUser(directory, data.username, data.username, data.displayName, data.emailAddress, !willExceedLicense, false);
                    if (willExceedLicense) {
                        log.warn("Not adding {} to {}; there are already {} licensed users",
                                data.username, ApplicationConstants.STASH_USERS, licensedUsers);
                    } else {
                        log.info("Created regular user '{}'", data.username);
                        ++licensedUsers;
                    }
                }
            }

            return true;
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }

    protected String getBaseUrlMachineName() {
        try {
            //Override the baseUrl host, typically used when generating a Stash home for clustered integration tests
            //because AMPS can only override the host name for HSQL-backed Stash (it is limited to doing text
            //replacements on db.* but for a clustered integration test there is no HSQL database).
            //Setting it here means it will be persisted for the actual integration tests.
            if (initBaseUrlWithMachineHost()) {
                return InetAddress.getLocalHost().getHostName().toLowerCase(Locale.US);
            }
        } catch (UnknownHostException e) {
            //Fall through
        }
        return "localhost";
    }

    private boolean initBaseUrlWithMachineHost() {
        return Boolean.getBoolean("stash.dev.init.baseurl.with.machine.host");
    }

    private static class UserData {

        private final String displayName;
        private final String username;
        private final String emailAddress;

        private UserData(String username, String displayName, String emailAddress) {
            this.username = username;
            this.displayName = displayName;
            this.emailAddress = emailAddress;
        }
    }
}
