// ***********************************************
// This File is for Custom Cypress commands.
// See docs at https://docs.cypress.io/api/cypress-api/custom-commands
// ***********************************************

import {
  AuthTokenInfo,
  TOKENITEM,
} from 'src/app/core/auth/models/auth-token-info.model';
import {
  DSPACE_XSRF_COOKIE,
  XSRF_REQUEST_HEADER,
} from 'src/app/core/xsrf/xsrf.constants';
import { v4 as uuidv4 } from 'uuid';

// Declare Cypress namespace to help with Intellisense & code completion in IDEs
// ALL custom commands MUST be listed here for code completion to work
declare global {
    // eslint-disable-next-line @typescript-eslint/no-namespace
    namespace Cypress {
        interface Chainable<Subject = any> {
            /**
             * Login to backend before accessing the next page. Ensures that the next
             * call to "cy.visit()" will be authenticated as this user.
             * @param email email to login as
             * @param password password to login as
             */
            login(email: string, password: string): typeof login;

            /**
             * Login via form before accessing the next page. Useful to fill out login
             * form when a cy.visit() call is to an a page which requires authentication.
             * @param email email to login as
             * @param password password to login as
             */
             loginViaForm(email: string, password: string): typeof loginViaForm;

            /**
             * Generate view event for given object. Useful for testing statistics pages with
             * pre-generated statistics. This just generates a single "hit", but can be called multiple times to
             * generate multiple hits.
             * @param uuid  UUID of object
             * @param dsoType type of DSpace Object (e.g. "item", "collection", "community")
             */
            generateViewEvent(uuid: string, dsoType: string): typeof generateViewEvent;

            /**
             * Create a new CSRF token and add to required Cookie. CSRF Token is returned
             * in chainable in order to allow it to be sent also in required CSRF header.
             * @returns Chainable reference to allow CSRF token to also be sent in header.
             */
            createCSRFCookie(): Chainable<any>;
        }
    }
}

/**
 * Login user via REST API directly, and pass authentication token to UI via
 * the UI's dsAuthInfo cookie.
 * WARNING: WHILE THIS METHOD WORKS, OCCASIONALLY RANDOM AUTHENTICATION ERRORS OCCUR.
 * At this time "loginViaForm()" seems more consistent/stable.
 * @param email email to login as
 * @param password password to login as
 */
function login(email: string, password: string): void {
  // Create a fake CSRF cookie/token to use in POST
  cy.createCSRFCookie().then((csrfToken: string) => {
    // get our REST API's base URL, also needed for POST
    cy.task('getRestBaseURL').then((baseRestUrl: string) => {
      // Now, send login POST request including that CSRF token
      cy.request({
        method: 'POST',
        url: baseRestUrl + '/api/authn/login',
        headers: { [XSRF_REQUEST_HEADER]: csrfToken },
        form: true, // indicates the body should be form urlencoded
        body: { user: email, password: password },
      }).then((resp) => {
        // We expect a successful login
        expect(resp.status).to.eq(200);
        // We expect to have a valid authorization header returned (with our auth token)
        expect(resp.headers).to.have.property('authorization');

        // Initialize our AuthTokenInfo object from the authorization header.
        const authheader = resp.headers.authorization as string;
        const authinfo: AuthTokenInfo = new AuthTokenInfo(authheader);

        // Save our AuthTokenInfo object to our dsAuthInfo UI cookie
        // This ensures the UI will recognize we are logged in on next "visit()"
        cy.setCookie(TOKENITEM, JSON.stringify(authinfo));
      });
    });
  });
}
// Add as a Cypress command (i.e. assign to 'cy.login')
Cypress.Commands.add('login', login);

/**
 * Login user via displayed login form
 * @param email email to login as
 * @param password password to login as
 */
function loginViaForm(email: string, password: string): void {
  // Enter email
  cy.get('[data-test="email"]').type(email);
  // Enter password
  cy.get('[data-test="password"]').type(password);
  // Click login button
  cy.get('[data-test="login-button"]').click();
}
// Add as a Cypress command (i.e. assign to 'cy.loginViaForm')
Cypress.Commands.add('loginViaForm', loginViaForm);


/**
 * Generate statistic view event for given object. Useful for testing statistics pages with
 * pre-generated statistics. This just generates a single "hit", but can be called multiple times to
 * generate multiple hits.
 *
 * NOTE: This requires that "solr-statistics.autoCommit=false" be set on the DSpace backend
 * (as it is in our docker-compose-ci.yml used in CI).
 * Otherwise, by default, new statistical events won't be saved until Solr's autocommit next triggers.
 * @param uuid UUID of object
 * @param dsoType type of DSpace Object (e.g. "item", "collection", "community")
 */
function generateViewEvent(uuid: string, dsoType: string): void {
  // Create a fake CSRF cookie/token to use in POST
  cy.createCSRFCookie().then((csrfToken: string) => {
    // get our REST API's base URL, also needed for POST
    cy.task('getRestBaseURL').then((baseRestUrl: string) => {
      // Now, send 'statistics/viewevents' POST request including that fake CSRF token in required header
      cy.request({
        method: 'POST',
        url: baseRestUrl + '/api/statistics/viewevents',
        headers: {
          [XSRF_REQUEST_HEADER] : csrfToken,
          // use a known public IP address to avoid being seen as a "bot"
          'X-Forwarded-For': '1.1.1.1',
          // Use a user-agent of a Firefox browser on Windows. This again avoids being seen as a "bot"
          'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0',
        },
        //form: true, // indicates the body should be form urlencoded
        body: { targetId: uuid, targetType: dsoType },
      }).then((resp) => {
        // We expect a 201 (which means statistics event was created)
        expect(resp.status).to.eq(201);
      });
    });
  });
}
// Add as a Cypress command (i.e. assign to 'cy.generateViewEvent')
Cypress.Commands.add('generateViewEvent', generateViewEvent);


/**
 * Can be used by tests to generate a random XSRF/CSRF token and save it to
 * the required XSRF/CSRF cookie for usage when sending POST requests or similar.
 * The generated CSRF token is returned in a Chainable to allow it to be also sent
 * in the CSRF HTTP Header.
 * @returns a Cypress Chainable which can be used to get the generated CSRF Token
 */
function createCSRFCookie(): Cypress.Chainable {
  // Generate a new token which is a random UUID
  const csrfToken: string = uuidv4();

  // Save it to our required cookie
  cy.task('getRestBaseDomain').then((baseDomain: string) => {
    // Create a fake CSRF Token.  Set it in the required server-side cookie
    cy.setCookie(DSPACE_XSRF_COOKIE, csrfToken, { 'domain': baseDomain });
  });

  // return the generated token wrapped in a chainable
  return cy.wrap(csrfToken);
}
// Add as a Cypress command (i.e. assign to 'cy.createCSRFCookie')
Cypress.Commands.add('createCSRFCookie', createCSRFCookie);
