package old;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.*;
import javax.crypto.SecretKey;

import old.core.Browser;
import old.core.BrowserArgumentProvider;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.remote.Augmenter;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import old.page.InboxPage;
import old.util.PropertyManager;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static old.page.BaseComponentPage.getLoginHeaderBy;

/**
 * @author gulnaz
 */
public class JWTokensIT extends BaseComponentIT {

  private static final String SURNAME = "demo";
  private static final String SECRET_KEY = "ZjE5ZjMxNmYtODViZC00ZTQ5LWIxZmYtOGEzYzE3Yjc1MDVk}";
  private static final SecretKey SIGNING_KEY;
  private static final String ACCESS_TOKEN = "Access-token";
  private static final String DEMO_GROUP_ID = "22ee608b-dd9d-4633-906d-7c4efca231a0";
  private static final String PROCESS_NAME = "test_process";
  private static final Logger LOGGER = LoggerFactory.getLogger(JWTokensIT.class);

  private static PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(11);
  private Properties config;
  private WebDriver newDriver;
  private Set<Cookie> cookies;

  static {
    byte[] encodedKey = Base64.getMimeDecoder().decode(SECRET_KEY);
    SIGNING_KEY = Keys.hmacShaKeyFor(encodedKey);
  }

  public JWTokensIT() throws IOException {
    super();
    config = PropertyManager.getProperties();
  }

  @AfterEach
  public void afterEachTest() {
    if (newDriver != null) {
      newDriver.close();
    }

    EXECUTION_MANAGER.deleteTestUser(login);
  }

  @Override
  protected void initCredentials() {
    initAdminName();
    login = name;
    password = login;
    String encodedPassword = passwordEncoder.encode(password);
    EXECUTION_MANAGER.insertUser(login, encodedPassword, login + "@test.ru", ORGANIZATION_ID,
        DEMO_GROUP_ID
    );
  }

  private void pasteDeadAccessToken() {
    String deadAccessToken = createDeadAccessToken(config.getProperty("login"));
    Cookie cookie = driver.manage().getCookieNamed(ACCESS_TOKEN);
    driver.manage().deleteCookie(cookie);
    addCookie(driver, cookie, deadAccessToken);
  }

  private void addCookie(WebDriver driver, Cookie cookie, String value) {
    driver.manage().addCookie(
        new Cookie.Builder(cookie.getName(), value)
            .expiresOn(cookie.getExpiry())
            .path(cookie.getPath())
            .build()
    );
  }

  private String createDeadAccessToken(String username) {
    return Jwts.builder()
        .setSubject(username)
        .setIssuer("ru.cg.webbpm.components")
        .setIssuedAt(new Date(System.currentTimeMillis()))
        .setExpiration(
            new Date(System.currentTimeMillis() - 3600 * 1000)
        )
        .signWith(SIGNING_KEY)
        .compact();
  }

  private void reopenApp() throws MalformedURLException {
    int attempts = 0;
    while (attempts++ < 5) {

      try {
        newDriver = new Augmenter().augment(new RemoteWebDriver(getGridURL(), options));
        break;
      }
      catch (WebDriverException ignored) {
        LOGGER.info("The driver did not start on " + attempts + " attempt");
      }
    }
    newDriver.get(applicationUrl);
    cookies.forEach(cookie -> addCookie(newDriver, cookie, cookie.getValue()));
    newDriver.get(applicationUrl); // reopen app again
    inboxPage = PageFactory.initElements(newDriver, InboxPage.class);
    inboxPage.waitForPageLoad();
  }

  private void initDriverAndLogin(Browser browser) {
    initDriver(browser);
    login(login, password);
    inboxPage.waitForVisibilityOf(inboxPage.adminList);
  }

  @ParameterizedTest
  @ArgumentsSource(BrowserArgumentProvider.class)
  public void refreshPageWithAliveAccessToken(Browser browser) {
    initDriverAndLogin(browser);

    inboxPage.refreshPage();
    assertTrue(inboxPage.getEmailInputsFrom(driver).isEmpty());
  }

  @ParameterizedTest
  @ArgumentsSource(BrowserArgumentProvider.class)
  public void goToPageWithAliveAccessToken(Browser browser) {
    initDriverAndLogin(browser);

    inboxPage.startAdminUser();
    inboxPage.waitForPageLoad();
    assertTrue(inboxPage.getEmailInputsFrom(driver).isEmpty());
  }

  @ParameterizedTest
  @ArgumentsSource(BrowserArgumentProvider.class)
  public void filterWithAliveAccessToken(Browser browser) {
    initDriverAndLogin(browser);

    inboxPage.startAdminUser();
    inboxPage.waitForPageLoad();

    inboxPage.waitProgressBarNotVisible();
    inboxPage.searchUser(SURNAME);
    assertTrue(inboxPage.getEmailInputsFrom(driver).isEmpty());
  }

  @ParameterizedTest
  @ArgumentsSource(BrowserArgumentProvider.class)
  public void refreshPageWithDeadAccessToken(Browser browser) {
    initDriverAndLogin(browser);

    pasteDeadAccessToken();
    inboxPage.refreshPage();
    assertTrue(inboxPage.getEmailInputsFrom(driver).isEmpty());
  }

  @ParameterizedTest
  @ArgumentsSource(BrowserArgumentProvider.class)
  public void goToPageWithDeadAccessToken(Browser browser) {
    initDriverAndLogin(browser);

    pasteDeadAccessToken();
    inboxPage.startAdminUser();
    inboxPage.waitForPageLoad();
    assertTrue(inboxPage.getEmailInputsFrom(driver).isEmpty());
  }

  @Disabled("qa WEBBPMNEXT-9052")
  @ParameterizedTest
  @ArgumentsSource(BrowserArgumentProvider.class)
  public void filterWithDeadAccessToken(Browser browser) {
    initDriverAndLogin(browser);

    inboxPage.startAdminUser();
    inboxPage.waitProgressBarNotVisible();
    pasteDeadAccessToken();
    inboxPage.searchUser(SURNAME);
    inboxPage.waitForPageLoad();
    assertTrue(inboxPage.getEmailInputsFrom(driver).isEmpty());
  }

  @ParameterizedTest
  @ArgumentsSource(BrowserArgumentProvider.class)
  public void refreshPageWithDeadRefreshToken(Browser browser) {
    initDriverAndLogin(browser);

    driver.manage().deleteAllCookies();
    inboxPage.refreshPage();
    assertFalse(inboxPage.getEmailInputsFrom(driver).isEmpty());
  }

  @ParameterizedTest
  @ArgumentsSource(BrowserArgumentProvider.class)
  public void goToPageWithDeadRefreshToken(Browser browser) {
    initDriverAndLogin(browser);

    processPage = inboxPage.startProcess(PROCESS_NAME);
    processPage.waitForInitializationOf(processPage.exitButton);
    driver.manage().deleteAllCookies();
    processPage.exitButton.click();
    inboxPage.waitForPageLoad();
    assertFalse(inboxPage.getEmailInputsFrom(driver).isEmpty());
  }

  @ParameterizedTest
  @ArgumentsSource(BrowserArgumentProvider.class)
  public void filterWithDeadRefreshToken(Browser browser) {
    initDriverAndLogin(browser);

    inboxPage.startAdminUser();
    inboxPage.waitProgressBarNotVisible();
    driver.manage().deleteAllCookies();
    inboxPage.waitForPageLoad();
    inboxPage.searchUser(SURNAME);
    inboxPage.waitForPageLoad();
    assertFalse(inboxPage.getEmailInputsFrom(driver).isEmpty());
  }

  @Disabled("dev WEBBPMNEXT-9052") //включить после решения задачи
  @ParameterizedTest
  @ArgumentsSource(BrowserArgumentProvider.class)
  public void checkTwoAuthorizedUsersCase(Browser browser) {
    initDriverAndLogin(browser);

    String firstTab = driver.getWindowHandle();
    cookies = driver.manage().getCookies();
    inboxPage.exitApp();
    getExplicitWait(getDefaultTimeout()).until(
        ExpectedConditions.visibilityOfElementLocated(getLoginHeaderBy()));

    ((JavascriptExecutor) driver).executeScript("window.open();");
    List<String> windowHandles = new ArrayList<>(driver.getWindowHandles());
    driver.switchTo().window(windowHandles.get(1));
    driver.get(applicationUrl);
    login(login, password);

    driver.switchTo().window(firstTab);
    driver.get(applicationUrl);
    cookies.forEach(cookie -> addCookie(driver, cookie, cookie.getValue()));
    pasteDeadAccessToken();
    inboxPage.refreshPage();
    inboxPage.waitEmailInput();
    assertFalse(inboxPage.getEmailInputsFrom(driver).isEmpty());
  }

  @ParameterizedTest
  @ArgumentsSource(BrowserArgumentProvider.class)
  public void filterWithNoAccessToken(Browser browser) {
    initDriverAndLogin(browser);

    inboxPage.startAdminUser();
    inboxPage.waitProgressBarNotVisible();
    driver.manage().deleteCookieNamed(ACCESS_TOKEN);
    inboxPage.searchUser(SURNAME);
    assertTrue(inboxPage.getEmailInputsFrom(driver).isEmpty());
  }

  @ParameterizedTest
  @ArgumentsSource(BrowserArgumentProvider.class)
  public void reopenWithAliveAccessToken(Browser browser) throws MalformedURLException {
    initDriverAndLogin(browser);

    //don't close browser in the end, we'll do that explicitly
    setCloseBrowser(false);

    cookies = driver.manage().getCookies();
    driver.close();
    driver = null;

    reopenApp();
    assertTrue(inboxPage.getEmailInputsFrom(newDriver).isEmpty());
  }
  
  @ParameterizedTest
  @ArgumentsSource(BrowserArgumentProvider.class)
  public void reopenWithDeadAccessToken(Browser browser) throws MalformedURLException {
    initDriverAndLogin(browser);

    //don't close browser in the end, we'll do that explicitly
    setCloseBrowser(false);

    pasteDeadAccessToken();
    cookies = driver.manage().getCookies();
    driver.close();
    driver = null;

    reopenApp();
    assertTrue(inboxPage.getEmailInputsFrom(newDriver).isEmpty());
  }

  @ParameterizedTest
  @ArgumentsSource(BrowserArgumentProvider.class)
  public void refreshWithNoSession(Browser browser) {
    initDriverAndLogin(browser);

    ((JavascriptExecutor) driver).executeScript(
        String.format("window.localStorage.clear();")); //clear localStorage
    inboxPage.refreshPage();
    assertTrue(inboxPage.getEmailInputsFrom(driver).isEmpty());
  }
}
