package azkaban.project;

import azkaban.storage.StorageManager;
import azkaban.user.User;
import azkaban.utils.Props;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static org.mockito.Mockito.*;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class ProjectManagerTest {
  private ProjectManager manager;
  private ProjectLoader loader;
  private StorageManager storageManager;
  private User user;
  private static final String PROJECT_NAME = "myTest";
  private static final String PROJECT_NAME_2 = "myTest_2";
  private static final String PROJECT_DESCRIPTION = "This is to test project manager";
  private static final String TEST_USER = "testUser";
  private static final String FILE_TYPE = "zip";
  private static final int PROJECT_ID = 1;
  private static final int PROJECT_ID_2 = 2;
  private static final int PROJECT_VERSION = 5;
  private static final int PROJECT_VERSION_RETENTIION = 3;

  @Before
  public void setUp() throws Exception {
    Props props = new Props();
    loader = mock(ProjectLoader.class);
    storageManager = mock(StorageManager.class);
    manager = new ProjectManager(loader, storageManager, props);
    user = new User(TEST_USER);
    Project project1 = new Project(PROJECT_ID, PROJECT_NAME);
    project1.setDescription(PROJECT_DESCRIPTION);
    project1.setActive(true);
    project1.setVersion(PROJECT_VERSION);

    when(loader.createNewProject(PROJECT_NAME, PROJECT_DESCRIPTION, user)).thenReturn(project1);
    when(loader.fetchProjectById(PROJECT_ID)).thenReturn(project1);
    when(loader.fetchProjectByName(PROJECT_NAME)).thenReturn(project1);
    when(loader.fetchAllProjectFlows(project1)).thenReturn(new ArrayList<>());
    when(loader.getLatestProjectVersion(project1)).thenReturn(PROJECT_VERSION);

    doAnswer(new Answer<Void>() {
      public Void answer(InvocationOnMock invocation) {
        project1.setActive(false);
        return null;
      }
    }).when(loader).removeProject(project1, user.getUserId());

    doAnswer(new Answer<Void>() {
      public Void answer(InvocationOnMock invocation) {
        project1.setVersion(PROJECT_VERSION + 1);
        return null;
      }
    }).when(loader).changeProjectVersion(project1, PROJECT_VERSION + 1, user.getUserId());

    doThrow(ProjectManagerException.class).when(loader).fetchAllProjectFlows(null);

  }

  @Test
  public void testCreateProject() throws Exception {
    System.out.println("TestCreateProject");
    Project project = manager.createProject(PROJECT_NAME, PROJECT_DESCRIPTION, user);
    verify(loader).postEvent(project, ProjectLogEvent.EventType.CREATED, user.getUserId(), null);
    Assert.assertEquals("Project Id", PROJECT_ID, project.getId());
    Assert.assertEquals("Project name", PROJECT_NAME, project.getName());
    Assert.assertEquals("Project description", PROJECT_DESCRIPTION,
        project.getDescription());
    Assert.assertTrue("Project is active", project.isActive());
  }

  @Test(expected = ProjectManagerException.class)
  public void testCreateProjectWithEmptyName() throws Exception {
    System.out.println("TestCreateProjectWithEmptyName");
    manager.createProject(null, PROJECT_DESCRIPTION, user);
  }

  @Test(expected = ProjectManagerException.class)
  public void testCreateProjectWithInvalidName() throws Exception {
    System.out.println("TestCreateProjectWithInvalidName");
    //Project name must start with a letter, test invalid project name "123", should throw exception
    manager.createProject("123", PROJECT_DESCRIPTION, user);
  }

  @Test(expected = ProjectManagerException.class)
  public void testCreateProjectWithEmptyDescription() throws Exception {
    System.out.println("testCreateProjectWithEmptyDescription");
    manager.createProject(PROJECT_NAME, null, user);
  }

  @Test(expected = ProjectManagerException.class)
  public void testCreateProjectWithEmptyUser() throws Exception {
    System.out.println("testCreateProjectWithEmptyUser");
    manager.createProject(PROJECT_NAME, PROJECT_DESCRIPTION, null);
  }

  @Test
  public void testRemoveProject() throws Exception {
    System.out.println("TestRemoveProject");
    Project project = manager.createProject(PROJECT_NAME, PROJECT_DESCRIPTION, user);
    manager.removeProject(project, user);
    verify(loader).removeProject(project, user.getUserId());
    verify(loader).postEvent(project, ProjectLogEvent.EventType.DELETED, user.getUserId(),
        null);
    Project fetchedProject = manager.getProject(project.getId());
    verify(loader).fetchProjectById(project.getId());
    verify(loader).fetchAllProjectFlows(project);
    Assert.assertFalse(fetchedProject.isActive());
  }

  @Test
  public void testUploadProject() throws Exception {
    System.out.println("TestUploadProject");
    Project project = manager.createProject(PROJECT_NAME, PROJECT_DESCRIPTION, user);
    File testFile = new File(this.getClass().getClassLoader().getResource("project/testjob/testjob.zip").getFile());
    System.out.println("Uploading zip file: " + testFile.getAbsolutePath());
    Props props = new Props();
    manager.uploadProject(project, testFile, FILE_TYPE, user, props);

    verify(storageManager).uploadProject(project, PROJECT_VERSION + 1, testFile, user);

    verify(loader).uploadFlows(eq(project), eq(PROJECT_VERSION + 1), anyCollection());
    verify(loader).changeProjectVersion(project, PROJECT_VERSION + 1, user.getUserId());
    //uploadProjectProperties should be called twice, one for jobProps, the other for propProps
    verify(loader, times(2)).uploadProjectProperties(eq(project), anyList());
    verify(loader).postEvent(project, ProjectLogEvent.EventType.UPLOADED, user.getUserId(),
        "Uploaded project files zip " + testFile.getName());
    verify(loader).cleanOlderProjectVersion(project.getId(), PROJECT_VERSION + 1 - PROJECT_VERSION_RETENTIION);
  }

  @Test
  public void testFetchProjectByName() throws Exception {
    System.out.println("TestFetchProjectByName");
    Project project = manager.createProject(PROJECT_NAME, PROJECT_DESCRIPTION, user);
    Project fetchedProject = manager.getProject(project.getName());
    Assert.assertEquals("Fetched project by name", project, fetchedProject);
  }

  @Test(expected = RuntimeException.class)
  public void testFetchInvalidProjectByName() throws Exception {
    System.out.println("TestFetchInvalidProjectByName");
    manager.createProject(PROJECT_NAME, PROJECT_DESCRIPTION, user);
    manager.getProject("Invalid_Project");
  }

  @Test
  public void testFetchProjectById() throws Exception {
    System.out.println("TestFetchProjectById");
    Project project = manager.createProject(PROJECT_NAME, PROJECT_DESCRIPTION, user);
    Project fetchedProject = manager.getProject(project.getId());
    Assert.assertEquals("Fetched project by id", project, fetchedProject);
  }

  @Test(expected = RuntimeException.class)
  public void testFetchInvalidProjectById() throws Exception {
    System.out.println("TestFetchInvalidProjectById");
    manager.createProject(PROJECT_NAME, PROJECT_DESCRIPTION, user);
    manager.getProject(100);
  }

  @Test
  public void testFetchAllProjects() throws Exception {
    System.out.println("TestFetchAllProjects");
    List<Project> projects = new ArrayList<>();
    Project new_project1 = manager.createProject(PROJECT_NAME, PROJECT_DESCRIPTION, user);
    Project project2 = new Project(PROJECT_ID_2, PROJECT_NAME_2);
    project2.setDescription(PROJECT_DESCRIPTION);
    project2.setActive(true);
    project2.setVersion(PROJECT_VERSION);
    when(loader.createNewProject(PROJECT_NAME_2, PROJECT_DESCRIPTION, user)).thenReturn(project2);
    Project new_project2 = manager.createProject(PROJECT_NAME_2, PROJECT_DESCRIPTION, user);
    projects.add(new_project1);
    projects.add(new_project2);

    when(loader.fetchAllActiveProjects()).thenReturn(projects);
    List<Project> fetchedProjects = manager.getProjects();
    Assert.assertEquals("Fetched projects: ", projects, fetchedProjects);
  }
}
