package de.deadlocker8.budgetmaster.integration.selenium;

import de.deadlocker8.budgetmaster.Main;
import de.deadlocker8.budgetmaster.accounts.Account;
import de.deadlocker8.budgetmaster.accounts.AccountType;
import de.deadlocker8.budgetmaster.authentication.UserService;
import de.deadlocker8.budgetmaster.charts.ChartDisplayType;
import de.deadlocker8.budgetmaster.charts.ChartGroupType;
import de.deadlocker8.budgetmaster.integration.helpers.IntegrationTestHelper;
import de.deadlocker8.budgetmaster.integration.helpers.SeleniumTest;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.File;
import java.util.Arrays;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Main.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
@SeleniumTest
public class ChartTest
{
	private final String SELECTOR_ACTIVE_DISPLAY_TYPE = ".button-display-type.active";
	private final String SELECTOR_ACTIVE_GROUP_TYPE = ".button-group-type.active";
	private final String SELECTOR_VISIBLE_CHART_PREVIEWS = ".chart-preview-column:not(.hidden)";
	private final String SELECTOR_ACTIVE_CHART_PREVIEWS = ".chart-preview.active";


	private IntegrationTestHelper helper;
	private WebDriver driver;

	@LocalServerPort
	int port;

	@Rule
	public TestName name = new TestName();

	@Rule
	public TestWatcher testWatcher = new TestWatcher()
	{
		@Override
		protected void finished(Description description)
		{
			driver.quit();
		}

		@Override
		protected void failed(Throwable e, Description description)
		{
			IntegrationTestHelper.saveScreenshots(driver, name, ChartTest.class);
		}
	};

	@Before
	public void prepare()
	{
		FirefoxOptions options = new FirefoxOptions();
		options.setHeadless(false);
		options.addPreference("devtools.console.stdout.content", true);
		driver = new FirefoxDriver(options);

		// prepare
		helper = new IntegrationTestHelper(driver, port);
		helper.start();
		helper.login(UserService.DEFAULT_PASSWORD);
		helper.hideBackupReminder();
		helper.hideWhatsNewDialog();

		String path = getClass().getClassLoader().getResource("SearchDatabase.json").getFile().replace("/", File.separator);
		final Account account1 = new Account("DefaultAccount0815", AccountType.CUSTOM);
		final Account account2 = new Account("Account2", AccountType.CUSTOM);

		helper.uploadDatabase(path, Arrays.asList("DefaultAccount0815", "sfsdf"), List.of(account1, account2));
	}

	@Test
	public void test_defaultSelection()
	{
		driver.get(helper.getUrl() + "/charts");

		// check display type
		assertThat(getSelectedType(SELECTOR_ACTIVE_DISPLAY_TYPE)).isEqualTo(ChartDisplayType.BAR.name());
		assertThat(driver.findElement(By.id("buttonCustomCharts")).isDisplayed()).isFalse();

		// check group type
		assertThat(driver.findElement(By.id("chart-group-type-buttons")).isDisplayed()).isTrue();
		assertThat(getSelectedType(SELECTOR_ACTIVE_GROUP_TYPE)).isEqualTo(ChartGroupType.MONTH.name());

		// check displayed chart previews
		final List<WebElement> displayedChartPreviews = driver.findElements(By.cssSelector(SELECTOR_VISIBLE_CHART_PREVIEWS));
		assertThat(displayedChartPreviews)
				.hasSize(3);
		assertThat(displayedChartPreviews.get(0).findElement(By.cssSelector(".card-action span")).getText())
				.isEqualTo("Incomes/Expenditures per month");

		// filter
		assertThat(driver.findElement(By.id("filterActiveBadge")).isDisplayed()).isFalse();

		// button
		assertThat(driver.findElement(By.name("buttonSave")).isEnabled()).isFalse();
	}

	@Test
	public void test_selectDisplayType()
	{
		driver.get(helper.getUrl() + "/charts");

		final String buttonSelector = ".button-display-type[data-value='" + ChartDisplayType.LINE.name() + "']";
		driver.findElement(By.cssSelector(buttonSelector)).click();

		WebDriverWait wait = new WebDriverWait(driver, 5);
		wait.until(ExpectedConditions.attributeContains(By.cssSelector(buttonSelector), "class", "active"));

		// check display type
		assertThat(getSelectedType(SELECTOR_ACTIVE_DISPLAY_TYPE)).isEqualTo(ChartDisplayType.LINE.name());
		assertThat(driver.findElement(By.id("buttonCustomCharts")).isDisplayed()).isFalse();

		// check group type
		assertThat(driver.findElement(By.id("chart-group-type-buttons")).isDisplayed()).isTrue();
		assertThat(getSelectedType(SELECTOR_ACTIVE_GROUP_TYPE)).isEqualTo(ChartGroupType.MONTH.name());

		// check displayed chart previews
		final List<WebElement> displayedChartPreviews = driver.findElements(By.cssSelector(SELECTOR_VISIBLE_CHART_PREVIEWS));
		assertThat(displayedChartPreviews)
				.hasSize(1);
		assertThat(displayedChartPreviews.get(0).findElement(By.cssSelector(".card-action span")).getText())
				.isEqualTo("Incomes/Expenditures per month");

		// filter
		assertThat(driver.findElement(By.id("filterActiveBadge")).isDisplayed()).isFalse();

		// button
		assertThat(driver.findElement(By.name("buttonSave")).isEnabled()).isFalse();
	}

	@Test
	public void test_hideGroupTypeIfOnlyOneDistinct()
	{
		driver.get(helper.getUrl() + "/charts");

		final String buttonSelector = ".button-display-type[data-value='" + ChartDisplayType.PIE.name() + "']";
		driver.findElement(By.cssSelector(buttonSelector)).click();

		WebDriverWait wait = new WebDriverWait(driver, 5);
		wait.until(ExpectedConditions.attributeContains(By.cssSelector(buttonSelector), "class", "active"));

		// check display type
		assertThat(getSelectedType(SELECTOR_ACTIVE_DISPLAY_TYPE)).isEqualTo(ChartDisplayType.PIE.name());

		// check group type
		assertThat(driver.findElement(By.id("chart-group-type-buttons")).isDisplayed()).isFalse();
	}

	@Test
	public void test_displayGroupTypeAfterHiding()
	{
		driver.get(helper.getUrl() + "/charts");

		String buttonSelector = ".button-display-type[data-value='" + ChartDisplayType.PIE.name() + "']";
		driver.findElement(By.cssSelector(buttonSelector)).click();

		WebDriverWait wait = new WebDriverWait(driver, 5);
		wait.until(ExpectedConditions.attributeContains(By.cssSelector(buttonSelector), "class", "active"));

		// check display type
		assertThat(getSelectedType(SELECTOR_ACTIVE_DISPLAY_TYPE)).isEqualTo(ChartDisplayType.PIE.name());

		// check group type
		assertThat(driver.findElement(By.id("chart-group-type-buttons")).isDisplayed()).isFalse();

		buttonSelector = ".button-display-type[data-value='" + ChartDisplayType.BAR.name() + "']";
		driver.findElement(By.cssSelector(buttonSelector)).click();

		wait = new WebDriverWait(driver, 5);
		wait.until(ExpectedConditions.attributeContains(By.cssSelector(buttonSelector), "class", "active"));

		// check display type
		assertThat(getSelectedType(SELECTOR_ACTIVE_DISPLAY_TYPE)).isEqualTo(ChartDisplayType.BAR.name());

		// check group type
		assertThat(driver.findElement(By.id("chart-group-type-buttons")).isDisplayed()).isTrue();
	}

	@Test
	public void test_selectGroupType()
	{
		driver.get(helper.getUrl() + "/charts");

		final String buttonSelector = ".button-group-type[data-value='" + ChartGroupType.YEAR.name() + "']";
		driver.findElement(By.cssSelector(buttonSelector)).click();

		WebDriverWait wait = new WebDriverWait(driver, 5);
		wait.until(ExpectedConditions.attributeContains(By.cssSelector(buttonSelector), "class", "active"));

		// check group type
		assertThat(driver.findElement(By.id("chart-group-type-buttons")).isDisplayed()).isTrue();
		assertThat(getSelectedType(SELECTOR_ACTIVE_GROUP_TYPE)).isEqualTo(ChartGroupType.YEAR.name());

		// check displayed chart previews
		final List<WebElement> displayedChartPreviews = driver.findElements(By.cssSelector(SELECTOR_VISIBLE_CHART_PREVIEWS));
		assertThat(displayedChartPreviews)
				.hasSize(1);
		assertThat(displayedChartPreviews.get(0).findElement(By.cssSelector(".card-action span")).getText())
				.isEqualTo("Incomes/Expenditures per year");

		// filter
		assertThat(driver.findElement(By.id("filterActiveBadge")).isDisplayed()).isFalse();

		// button
		assertThat(driver.findElement(By.name("buttonSave")).isEnabled()).isFalse();
	}

	@Test
	public void test_selectChartEnabledButton()
	{
		driver.get(helper.getUrl() + "/charts");

		final String chartPreviewSelector = ".chart-preview-column[data-id='6']";
		driver.findElement(By.cssSelector(chartPreviewSelector)).click();

		final WebDriverWait wait = new WebDriverWait(driver, 5);
		wait.until(ExpectedConditions.attributeContains(By.cssSelector(chartPreviewSelector + " .chart-preview"), "class", "active"));

		// check displayed chart previews
		final List<WebElement> activeChartPreviews = driver.findElements(By.cssSelector(SELECTOR_ACTIVE_CHART_PREVIEWS));
		assertThat(activeChartPreviews)
				.hasSize(1);
		assertThat(activeChartPreviews.get(0).findElement(By.cssSelector(".card-action span")).getText())
				.isEqualTo("Incomes/Expenditures per month by categories");

		// button
		assertThat(driver.findElement(By.name("buttonSave")).isEnabled()).isTrue();
	}

	@Test
	public void test_selectDisplayTypeAfterSelectingChartDisablesButton()
	{
		driver.get(helper.getUrl() + "/charts");

		final String chartPreviewSelector = ".chart-preview-column[data-id='6']";
		driver.findElement(By.cssSelector(chartPreviewSelector)).click();

		WebDriverWait wait = new WebDriverWait(driver, 5);
		wait.until(ExpectedConditions.attributeContains(By.cssSelector(chartPreviewSelector + " .chart-preview"), "class", "active"));

		final String buttonSelector = ".button-display-type[data-value='" + ChartDisplayType.LINE.name() + "']";
		driver.findElement(By.cssSelector(buttonSelector)).click();

		wait = new WebDriverWait(driver, 5);
		wait.until(ExpectedConditions.attributeContains(By.cssSelector(buttonSelector), "class", "active"));

		// check displayed chart previews
		final List<WebElement> activeChartPreviews = driver.findElements(By.cssSelector(SELECTOR_ACTIVE_CHART_PREVIEWS));
		assertThat(activeChartPreviews).isEmpty();

		// button
		assertThat(driver.findElement(By.name("buttonSave")).isEnabled()).isFalse();
	}

	@Test
	public void test_showFilterBadge()
	{
		driver.get(helper.getUrl() + "/charts");

		assertThat(driver.findElement(By.id("filterActiveBadge")).isDisplayed()).isFalse();

		driver.findElement(By.id("chart-filter-container")).click();
		driver.findElement(By.id("section-type")).click();
		final WebElement checkBox = driver.findElement(By.cssSelector("#section-type .text-default"));
		((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView(true);", checkBox);
		checkBox.click();

		final WebDriverWait wait = new WebDriverWait(driver, 5);
		wait.until(ExpectedConditions.visibilityOfElementLocated(By.className("filter-button-reset")));

		assertThat(driver.findElement(By.id("filterActiveBadge")).isDisplayed()).isTrue();
	}

	@Test
	public void test_hideFilterBadgeOnReset()
	{
		driver.get(helper.getUrl() + "/charts");

		driver.findElement(By.id("chart-filter-container")).click();
		driver.findElement(By.id("section-type")).click();
		final WebElement checkBox = driver.findElement(By.cssSelector("#section-type .text-default"));
		((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView(true);", checkBox);
		checkBox.click();

		final WebDriverWait wait = new WebDriverWait(driver, 5);
		wait.until(ExpectedConditions.visibilityOfElementLocated(By.className("filter-button-reset")));

		driver.findElement(By.className("filter-button-reset")).click();
		assertThat(driver.findElement(By.id("filterActiveBadge")).isDisplayed()).isFalse();
	}

	@Test
	public void test_showManageButtonForCustomCharts()
	{
		driver.get(helper.getUrl() + "/charts");

		final String buttonSelector = ".button-display-type[data-value='" + ChartDisplayType.CUSTOM.name() + "']";
		driver.findElement(By.cssSelector(buttonSelector)).click();

		WebDriverWait wait = new WebDriverWait(driver, 5);
		wait.until(ExpectedConditions.attributeContains(By.cssSelector(buttonSelector), "class", "active"));

		// check display type
		assertThat(getSelectedType(SELECTOR_ACTIVE_DISPLAY_TYPE)).isEqualTo(ChartDisplayType.CUSTOM.name());

		assertThat(driver.findElement(By.id("buttonCustomCharts")).isDisplayed()).isTrue();
	}

	@Test
	public void test_showChart()
	{
		driver.get(helper.getUrl() + "/charts");

		final String chartPreviewSelector = ".chart-preview-column[data-id='6']";
		driver.findElement(By.cssSelector(chartPreviewSelector)).click();

		WebDriverWait wait = new WebDriverWait(driver, 5);
		wait.until(ExpectedConditions.attributeContains(By.cssSelector(chartPreviewSelector + " .chart-preview"), "class", "active"));

		driver.findElement(By.name("buttonSave")).click();

		wait = new WebDriverWait(driver, 5);
		wait.until(ExpectedConditions.visibilityOfElementLocated(By.className("chart-canvas")));

		assertThat(driver.findElements(By.cssSelector(".chart-canvas .plot-container"))).hasSize(1);
	}

	private String getSelectedType(String selector)
	{
		final List<WebElement> activeTypeButtons = driver.findElements(By.cssSelector(selector));
		assertThat(activeTypeButtons)
				.hasSize(1);

		return activeTypeButtons.get(0).getAttribute("data-value");
	}
}