/*
 * The MIT License
 *
 * Copyright (c) 2015, CloudBees, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package jenkins.install;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.mockito.Mockito;

import hudson.Main;
import hudson.model.UpdateCenter;
import hudson.model.UpdateCenter.DownloadJob.Failure;
import hudson.model.UpdateCenter.DownloadJob.InstallationStatus;
import hudson.model.UpdateCenter.DownloadJob.Installing;
import hudson.model.UpdateCenter.DownloadJob.Pending;
import hudson.model.UpdateCenter.DownloadJob.Success;
import hudson.model.UpdateCenter.UpdateCenterJob;
import hudson.model.UpdateSite;
import jenkins.model.Jenkins;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

/**
 * Test
 * @author <a href="mailto:tom.fennelly@gmail.com">tom.fennelly@gmail.com</a>
 */
public class InstallUtilTest {

    @Rule
    public JenkinsRule jenkinsRule = new JenkinsRule();

    @Before
    public void setup() {
        // JenkinsRule will have created the last exec file (indirectly),
        // so remove it so we can fake the tests.
        InstallUtil.getLastExecVersionFile().delete();
        // Disable the unit test flag.
        Main.isUnitTest = false;
    }

    @After
    public void tearDown() {
        // Reset the unit test flag back to its default.
        Main.isUnitTest = true;
    }

    /**
     * Test jenkins startup sequences and the changes to the startup type..
     */
    @Test
    public void test_typeTransitions() {
        // A new test instance sets up security first
        Assert.assertEquals(InstallState.INITIAL_SECURITY_SETUP, InstallUtil.getNextInstallState(InstallState.UNKNOWN));

        // And proceeds to the new state
        Assert.assertEquals(InstallState.NEW, InstallUtil.getNextInstallState(InstallState.INITIAL_SECURITY_SETUP));

        // Save the last exec version. This will only be done by Jenkins after one of:
        //   1. A successful run of the install wizard.
        //   2. A success upgrade.
        //   3. A successful restart.
        InstallUtil.saveLastExecVersion();

        // Fudge things a little now, pretending there's a restart...

        // Now if we ask what is the InstallState, we should be told it's a RESTART because
        // the install wizard is complete and the version matches the currently executing
        // Jenkins version.
        Assert.assertEquals(InstallState.RESTART, InstallUtil.getNextInstallState(InstallState.UNKNOWN));

        // Fudge things again, changing the stored version to something old, faking an upgrade...
        InstallUtil.saveLastExecVersion("1.584");
        Assert.assertEquals(InstallState.UPGRADE, InstallUtil.getNextInstallState(InstallState.UNKNOWN));

        // Fudge things yet again, changing the stored version to something very very new, faking a downgrade...
        InstallUtil.saveLastExecVersion("1000.0");
        Assert.assertEquals(InstallState.DOWNGRADE, InstallUtil.getNextInstallState(InstallState.UNKNOWN));
    }


    /**
     * Test jenkins startup sequences and the changes to the startup type..
     */
    @Test
    public void test_getLastExecVersion() throws Exception {
        // Delete the config file, forcing getLastExecVersion to return
        // the default/unset version value.
        InstallUtil.getConfigFile().delete();
        Assert.assertEquals("1.0", InstallUtil.getLastExecVersion());

        // Set the version to some stupid value and check again. This time,
        // getLastExecVersion should read it from the file.
        setStoredVersion("9.123");
        Assert.assertEquals("9.123", InstallUtil.getLastExecVersion());
    }

    private void setStoredVersion(String version) throws Exception {
        Field versionField = Jenkins.class.getDeclaredField("version");
        versionField.setAccessible(true);
        versionField.set(jenkinsRule.jenkins, version);
        Assert.assertEquals(version, Jenkins.getStoredVersion().toString());
        // Force a save of the config.xml
        jenkinsRule.jenkins.save();
    }

    /**
     * Validate proper statuses are persisted and install status is cleared when invoking appropriate methods on {@link InstallUtil}
     */
    @Test
    public void testSaveAndRestoreInstallingPlugins() throws Exception {
        final List<UpdateCenterJob> updates = new ArrayList<>();

        final Map<String,String> nameMap = new HashMap<>();

        new UpdateCenter() { // inner classes...
		{
			new UpdateSite("foo", "http://omg.org") {
				{
					for(String name : Arrays.asList("pending-plug:Pending", "installing-plug:Installing", "failure-plug:Failure", "success-plug:Success")) {
						String statusType = name.split(":")[1];
						name = name.split(":")[0];

						InstallationStatus status;
						if("Success".equals(statusType)) {
							status = Mockito.mock(Success.class);
						}
						else if("Failure".equals(statusType)) {
							status = Mockito.mock(Failure.class);
						}
						else if("Installing".equals(statusType)) {
							status = Mockito.mock(Installing.class);
						}
						else {
							status = Mockito.mock(Pending.class);
						}

						nameMap.put(statusType, status.getClass().getSimpleName());

					JSONObject json = new JSONObject();
					json.put("name", name);
					json.put("version", "1.1");
					json.put("url", "http://google.com");
					json.put("dependencies", new JSONArray());
					Plugin p = new Plugin(getId(), json);

					InstallationJob job = new InstallationJob(p, null, null, false);
						job.status = status;
						job.setCorrelationId(UUID.randomUUID()); // this indicates the plugin was 'directly selected'
		                updates.add(job);
			        }
				}
			};
		}
        };

        InstallUtil.persistInstallStatus(updates);

	Map<String,String> persisted = InstallUtil.getPersistedInstallStatus();

	Assert.assertEquals(nameMap.get("Pending"), persisted.get("pending-plug"));
	Assert.assertEquals("Pending", persisted.get("installing-plug")); // only marked as success/fail after successful install
	Assert.assertEquals(nameMap.get("Failure"), persisted.get("failure-plug"));
	Assert.assertEquals(nameMap.get("Success"), persisted.get("success-plug"));

        InstallUtil.clearInstallStatus();

	persisted = InstallUtil.getPersistedInstallStatus();

	Assert.assertNull(persisted); // should be deleted
    }
}
