import dayjs from 'dayjs';
import { createGuestDetails } from '@support-user/index';
import { UserModal } from '../lib/user-modal';
import { Timeouts, TripTileType, utilitySelector } from '@utility-lib/index';

const selectors = {
  givenNameInput: 'input[formcontrolname="givenName"]',
  familyNameInput: 'input[formcontrolname="familyName"]',
  genderInput: 'ta-select[formcontrolname="gender"] .ta-select',
  birthdayInput: 'input[placeholder="MM/DD/YYYY"]',
  birthdayField: 'ta-date-input[formcontrolname="birthdate"] input',
  phoneInput: 'ta-phone-input[formcontrolname="mobilePhone"]',
  phoneValue: 'input.ta-phone-input__template',
  saveFooterButton: '[data-testid="stickyPrimaryButton"]',
  passportIssuingInput: 'ta-country-select[formcontrolname="countryOfIssue"]',
  passportCitizenshipInput: 'ta-country-select[formcontrolname="countryOfCitizenship"]',
  passportDateIssued: 'ta-date-input[formcontrolname="issuedOn"]',
  passportDateExpired: 'ta-date-input[formcontrolname="expiresOn"]',
  passportNumber: 'input[formcontrolname="number"]'
};

interface Passport {
  country: string;
  issue: string;
  expire: string;
  number: string;
}

export class TravelerModal {
  static interceptApis(numRetries: number, aliasName: string): void {
    if (numRetries === 0) {
      throw new Error(`Could not find property '${'givenName'}' after multiple attempts`);
    }
    cy.intercept('GET', '/api/user/passenger').as(aliasName);
    cy.allure().logStep('Visit profile page on traveler tab');
    this.visitPage();
    cy.wait(`@${aliasName}`).then((interception) => {
      const response = interception.response;
      if (response && Object.prototype.hasOwnProperty.call(response.body, 'givenName')) {
        // Response has the expected property, proceed with the test
        cy.wrap(response.body['givenName']).as('givenName');
      } else {
        // Response is missing the expected property, retry after 1 second with a new intercept
        cy.log('Bad API response, retrying...');
        cy.wait(Timeouts.SHORT_TIMEOUT_3_SEC.timeout);
        TravelerModal.interceptApis(numRetries - 1, aliasName);
      }
    });
  }

  static visitPage(): void {
    cy.allure().logStep('Visit profile page on traveler tab');
    cy.visit('app/user2/profile?tab=traveler');
  }

  static getTravelerInfo(): void {
    cy.wait('@travelerInfo').then((interception) => {
      // Gets traveler's current information and saves them as aliases
      const response = interception.response;
      if (response) {
        const properties = ['givenName', 'familyName', 'gender', 'birthdate'];
        properties.forEach((property) => {
          cy.wrap(response.body)
            .should('have.property', property)
            .as(`org${property.charAt(0).toUpperCase()}${property.slice(1)}`);
        });
        cy.wrap(response.body)
          .should('have.nested.property', 'contact.phone.number')
          .as('orgPhoneNumber');
      }
    });
  }

  static compareTravelerInfo(): void {
    cy.reload();
    const aliasName = 'newTravelerInfo';
    TravelerModal.interceptApis(3, aliasName);
    cy.wait(`@${aliasName}`).then((interception) => {
      // Gets traveler's new information and saves them as new aliases
      const response = interception.response;

      function setAliasForProperty(propertyName: string) {
        cy.wrap(response?.body)
          .should('have.property', propertyName)
          .as(`new${propertyName.charAt(0).toUpperCase()}${propertyName.slice(1)}`);
      }

      if (response) {
        setAliasForProperty('givenName');
        setAliasForProperty('familyName');
        setAliasForProperty('gender');
        setAliasForProperty('birthdate');
        cy.wrap(response.body)
          .should('have.nested.property', 'contact.phone.number')
          .as('newPhoneNumber');
      }
    });
    // Confirms on the API level that the traveler's info has been updated
    const properties = {
      orgGivenName: 'newGivenName',
      orgFamilyName: 'newFamilyName',
      orgGender: 'newGender',
      orgBirthdate: 'newBirthdate',
      orgPhoneNumber: 'newPhoneNumber'
    };

    // Confirms that the UI is also displaying the updated traveler's info
    this.compareUIProperties(selectors.givenNameInput, properties.orgGivenName);
    this.compareUIProperties(selectors.familyNameInput, properties.orgFamilyName);
    // Phone, Birthday and Gender require unique comparisons
    cy.get(selectors.phoneValue)
      .invoke('val')
      .then((displayedPhoneNumber) => {
        const phoneStr = displayedPhoneNumber?.toString().replace(/[\s⚊]+/g, '');
        cy.get('@newPhoneNumber').then((value) => {
          expect(phoneStr).to.equal(value);
        });
      });
    cy.get(selectors.birthdayInput)
      .invoke('val')
      .then((displayedBirthday) => {
        const dateStr = displayedBirthday?.toString();
        const formattedDate = dayjs(dateStr).format('YYYY-MM-DD');
        cy.get('@newBirthdate').then((value) => {
          expect(formattedDate).to.equal(value);
        });
      });
    cy.get('@newGender').then((value1) => {
      cy.get(selectors.genderInput)
        .invoke('attr', 'value')
        .then((value2) => {
          if (value2) {
            const uppercaseValue2 = value2.toUpperCase().trim();
            expect(value1).to.equal(uppercaseValue2);
          }
        });
    });
  }

  static compareAPIProperties(oldAlias: string, newAlias: string) {
    cy.get('@' + oldAlias).then((value1) => {
      cy.get('@' + newAlias).then((value2) => {
        expect(value1).to.not.equal(value2);
      });
    });
  }

  static compareUIProperties(selector: string, newAlias: string) {
    cy.get(selector)
      .invoke('val')
      .then((value1) => {
        cy.get('@' + newAlias).then((value2) => {
          expect(value1).to.equal(value2);
        });
      });
  }

  static createNewTravelerInfo(
    travelerType: TripTileType,
    gender: 'MALE' | 'FEMALE',
    age: number
  ) {
    const {
      givenName = '',
      familyName = '',
      phoneNumber = ''
    } = createGuestDetails(age, gender, travelerType);
    // The /api/user/passenger API only stores the first 11 numbers of a phone number, so we'll trim the phone number here down to 11 characters (with a '+')
    const maxLength = 12;
    cy.wrap(phoneNumber)
      .invoke('substring', 0, maxLength)
      .then((number) => {
        const trimNumber = number;
        // Adds and saves new traveler information
        cy.get(selectors.birthdayField).clear();
        UserModal.fillTravelerName(givenName, familyName);
        UserModal.fillBirthday(age);
        UserModal.fillGender(gender);
        UserModal.fillPhoneNumber(trimNumber);
        cy.get(selectors.saveFooterButton).click();
      });
  }

  static updatePassportInfo(passport: Passport) {
    const { country, issue, expire, number } = passport;
    // Enters generated passport information into the passport fields
    cy.get(selectors.passportIssuingInput).type(
      '{selectall}{backspace}' + country + '{enter}'
    );
    cy.get(selectors.passportCitizenshipInput).type(
      '{selectall}{backspace}' + country + '{enter}'
    );
    cy.get(selectors.passportDateIssued).type('{selectall}{backspace}' + issue);
    cy.get(selectors.passportDateExpired).type('{selectall}{backspace}' + expire);
    cy.get(selectors.passportNumber).type('{selectall}{backspace}' + number);
    cy.get(selectors.saveFooterButton).click();
  }

  static verifyUpdatedPassportInfo(passport: Passport) {
    const { country, issue, expire, number } = passport;
    cy.reload();
    cy.get(selectors.passportIssuingInput)
      .find(utilitySelector.inputSelector)
      .should('have.value', country);
    cy.get(selectors.passportCitizenshipInput)
      .find(utilitySelector.inputSelector)
      .should('have.value', country);
    cy.get(selectors.passportDateIssued)
      .find(utilitySelector.inputSelector)
      .invoke('val')
      .then((value1) => {
        const dateStr = value1?.toString();
        const passportFormattedIssueDate = dayjs(dateStr, 'MMM D, YYYY').format(
          'MMDDYYYY'
        );
        expect(passportFormattedIssueDate).to.equal(issue);
      });
    cy.get(selectors.passportDateExpired)
      .find(utilitySelector.inputSelector)
      .invoke('val')
      .then((value) => {
        const dateStr = value?.toString();
        const passportFormattedExpiredDate = dayjs(dateStr, 'MMM D, YYYY').format(
          'MMDDYYYY'
        );
        expect(passportFormattedExpiredDate).to.equal(expire);
      });
    cy.get(selectors.passportNumber).should('have.value', number);
  }
}
