package com.atlassian.stash.internal.johnson;

import com.atlassian.johnson.JohnsonEventContainer;
import com.atlassian.johnson.event.ApplicationEventCheck;
import com.atlassian.johnson.event.Event;
import com.atlassian.johnson.event.EventLevel;
import com.atlassian.johnson.event.EventType;
import com.atlassian.bitbucket.Product;
import com.atlassian.stash.internal.home.HomeDirectoryDetails;
import com.atlassian.stash.internal.home.HomeDirectoryResolver;
import com.atlassian.bitbucket.util.FileUtils;
import org.apache.commons.lang3.SystemUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.servlet.ServletContext;
import java.io.File;
import java.io.IOException;

/**
 * Ensures the home environment variable has been set.
 * <p>
 * Note: Spring is not available in {@code ApplicationEventCheck}s--they are executed while Johnson is starting up,
 * which happens before the main application.
 */
public class HomeApplicationEventCheck implements ApplicationEventCheck {

    private static final Logger log = LoggerFactory.getLogger(HomeApplicationEventCheck.class);

    @Override
    public void check(@Nonnull JohnsonEventContainer eventContainer, @Nonnull ServletContext servletContext) {
        HomeDirectoryDetails homeDirectoryDetails;
        try {
            homeDirectoryDetails = resolveHomeDirectory();
        } catch (IllegalStateException e) {
            addError(eventContainer, e.getMessage());
            return;
        }

        File home = homeDirectoryDetails.getHome();
        if (!home.exists()) {
            //Note: By this point, the initialisation of Logback should have created the home directory, if it
            //      was possible to create it. While that's relying on a side effect from another framework, it
            //      is a consistent side-effect: Logging will always be initialised before Johnson.
            String message = formatErrorMessage("%1$s %3$s [%2$s] cannot be created or %1$s does not have permission to access it", home, "Home");
            addError(eventContainer, message);
            return;
        }

        if (!verify(eventContainer, home, "Home")) {
            return;
        }

        File sharedHome = homeDirectoryDetails.getSharedHome();
        if (!sharedHome.exists()) {
            // Create the stash.shared.home if it does not exist. Contrast this with the stash.home directory treatment,
            // which we expect to have been created by Logback initialization by the time we get here.
            try {
                FileUtils.mkdir(sharedHome);
            } catch (IllegalStateException e) {
                addError(eventContainer, e.getMessage());
                return;
            }
        }

        verify(eventContainer, sharedHome, "Shared Home");
    }

    private boolean verify(JohnsonEventContainer eventContainer, File dir, String dirName) {
        String messageTemplate = null;
        if (!dir.isDirectory()) {
            messageTemplate = "%1$s %3$s [%2$s] is not a directory";
        } else if (!dir.canRead()) {
            messageTemplate = "%1$s %3$s [%2$s] exists but is not readable by %1$s";
        } else if (!isWritable(dir)) {
            messageTemplate = "%1$s %3$s [%2$s] exists but is not writable by %1$s";
        } else if (!dir.canExecute()) {
            messageTemplate = "%1$s %3$s [%2$s] exists but is not accessible by %1$s";
        }

        if (messageTemplate != null) {
            String message = formatErrorMessage(messageTemplate, dir, dirName);
            addError(eventContainer, message);
        }

        return messageTemplate == null;
    }

    private String formatErrorMessage(String template, File dir, String dirName) {
        return String.format(template, Product.NAME, dir, dirName);
    }

    private void addError(JohnsonEventContainer eventContainer, String message) {
        log.error(message);
        eventContainer.addEvent(new Event(EventType.get("home"), message, EventLevel.get(EventLevel.FATAL)));
    }

    private boolean isWritable(File home) {
        if (SystemUtils.IS_OS_WINDOWS) {
            // Windows can report that the directory is writable with File.canWrite(),
            // whereas it is not because of the ACLs: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4420020
            // so instead, this checks whether Stash is able to create a file within the home directory
            try {
                File f = File.createTempFile(Product.NAME, ".tmp", home);
                if (!f.delete()) {
                    f.deleteOnExit();
                }
                return true;
            } catch (IOException e) {
                return false;
            }
        } else {
            return home.canWrite();
        }
    }

    protected HomeDirectoryDetails resolveHomeDirectory() {
        return new HomeDirectoryResolver().resolve();
    }

}
