import { AUTH_TOKEN, loginRequest } from '../commands';
import { stagingUsers } from '@fixtures/staging-users';
import { AccountRegistrationClient, SuperAdminClient } from './lib';
import { Accept, HttpRequest, HttpStatusCodes } from '../base';
import {
  CompanyLoginOptions,
  CustomField,
  CustomFieldsUtility,
  CustomFiledOption,
  Logs,
  Random,
  Timeouts
} from '@utility-lib/index';
import { LoyaltyInfo, CreditCard } from '@flight-modal/index';
import { faker } from '@faker-js/faker';
import { SplitUtility } from '@split-lib/index';
import { Provider_Type, User_Type } from '@support-onboard/index';
import { postChargeableEntity } from './../commerce/commerceCommands';
import { AgencyCurrency, AgencyData, CompanyParams, DEFAULT_LOCALE_EN_US } from '@fixtures/onboard';

const { superAdmin } = stagingUsers;
const superAdminCredentials = Cypress.env('superAdmin').SUPER_ADMIN_PASSWORD;
const onBoardUserCredentials = Cypress.env('newUser').NEW_USER_PASSWORD;

Cypress.Commands.add('initiateCompanyCreationFlow', (companyLoginOptions: CompanyLoginOptions) => {
  const {
    providers,
    users,
    locale,
    creditCardInfo,
    loyaltyInfo,
    feeModelInfo,
    companyCustomFields,
    vip,
    companySuffix,
    phoneNumber
  } = companyLoginOptions;
  cy.allure().logStep('Initiate company creation flow');
  const email = superAdmin.email;
  cy.loginWithAuthToken({ email, password: superAdminCredentials, isSA: true })
    .then(() => createCompany(loyaltyInfo, companySuffix))
    .then((companyResponse) => checkAndCreateFeeModelForCompany(companyResponse, feeModelInfo))
    .then((companyResponse) => updateCompanyParams(companyResponse, companyLoginOptions))
    .then((companyResponse) => addUserAgency(companyResponse, feeModelInfo, vip))
    .then((agencyResponse) => isUserRegistered(agencyResponse, users, providers))
    .then((email) => sendPreSignupEmail(email))
    .then((email) => getSignUpToken(email))
    .then(({ email, body }) => createAccount(email, body, onBoardUserCredentials))
    .then((email) => retrieveUserByEmail(email))
    .then(({ userResponse }) => setUserAsVIP(userResponse, vip))
    .then(({ userResponse }) => setUserByType(userResponse, users))
    .then((email) => loginRequest({ email, password: onBoardUserCredentials }))
    .then(() => setTravelFrequency())
    .then(() => updateUserInfo())
    .then(() => updatePassengerInfoDefault(phoneNumber))
    .then((companyResponse) => addPersonalPaymentMethod(companyResponse, creditCardInfo))
    .then(() => getAndCheckSplitsForProviders(providers))
    .then(() => (locale !== DEFAULT_LOCALE_EN_US ? setLocale(locale) : 'Locale is already set to english'))
    .then(() => {
      companyCustomFields.forEach((values, companyCustomField) => setCompanyCustomField(companyCustomField, values));
    });
});

Cypress.Commands.add('addNewTraveler', (companyDomain: string) => {
  const email = Random.randomString(5) + '@' + `${companyDomain}`;
  cy.loginWithAuthToken({
    email: superAdmin.email,
    password: superAdminCredentials,
    isSA: true
  })
    .then(() => sendPreSignupEmail(email))
    .then((email) => getSignUpToken(email))
    .then(({ email, body }) => createAccount(email, body, onBoardUserCredentials))
    .then((email) => loginRequest({ email, password: onBoardUserCredentials }))
    .then(() => setTravelFrequency())
    .then(() => updateUserInfo())
    .then(() => updatePassengerInfoDefault())
    .then(() => addPersonalPaymentMethod())
    .then(() => cy.task('putDataInCache', { key: 'travelerEmail', data: email }));
});

Cypress.Commands.add('retrieveSignUpToken', (email: string) => {
  cy.loginWithAuthToken({
    email: superAdmin.email,
    password: superAdminCredentials,
    isSA: true,
    localStorage: false
  }).then(() => getSignUpToken(email));
});

Cypress.Commands.add('retrieveUserByEmail', (email: string) => {
  cy.loginWithAuthToken({
    email: superAdmin.email,
    password: superAdminCredentials,
    isSA: true,
    localStorage: false
  }).then(() => retrieveUserByEmail(email));
});

function createCompany(loyaltyInfo?: LoyaltyInfo, companySuffix?: string): Cypress.Chainable<any> {
  cy.allure().logStep('Start creating brand new company');
  const randomId = Random.randomString(12);
  const companyDomain = randomId + '-navan' + companySuffix + '.staging.tripactions.xyz';
  const companyName = 'TA_CYPRESS_' + randomId;
  const options = {
    method: HttpRequest.Post,
    url: SuperAdminClient.companiesUrl,
    headers: {
      Authorization: AUTH_TOKEN,
      Accept: Accept.ApplicationJson
    },
    body: {
      name: companyName,
      domain: companyDomain,
      loyaltyInfo,
      status: 'ACTIVE'
    },
    retryOnStatusCodeFailure: true
  };

  return cy
    .request(options)
    .then((xhr) => Logs.logResponseStatusAndBody(xhr))
    .then(({ body, status }) => {
      expect(status, 'Successfully created company').to.equal(HttpStatusCodes.Created);
      return body;
    });
}

function updateCompanyParams(companyResponse: any, companyLoginOptions: CompanyLoginOptions) {
  const newCompanyParams: CompanyParams = {
    tripProposalEnabled: companyLoginOptions.tripProposal,
    chargeBookingFee: true,
    chargeTripFee: true
  };
  const requestBody = {
    ...companyResponse,
    ...newCompanyParams
  };
  cy.log('Update the company with the following parameters: ' + JSON.stringify(newCompanyParams));
  return cy
    .request({
      method: 'PUT',
      url: SuperAdminClient.companiesUrl + `/${companyResponse.uuid}`,
      headers: {
        Authorization: AUTH_TOKEN
      },
      body: requestBody,
      retryOnStatusCodeFailure: true
    })
    .then(({ body, status }) => {
      expect(status, 'Company params updated').to.equal(HttpStatusCodes.Ok);
      return body;
    });
}

function updateUserInfo() {
  const userInfo = {
    phone_number: '+14157483333',
    onboardingCompleted: true
  };
  cy.log('Update user info with the following parameters: ' + JSON.stringify(userInfo));
  return cy
    .request({
      method: HttpRequest.Patch,
      url: AccountRegistrationClient.userInfo,
      headers: {
        Authorization: AUTH_TOKEN,
        Accept: Accept.ApplicationJson
      },
      body: userInfo,
      retryOnStatusCodeFailure: true
    })
    .then(({ status }) => {
      expect(status, 'Updated user info').to.equal(HttpStatusCodes.Ok);
    });
}

function checkAndCreateFeeModelForCompany(companyResponse: any, feeModelName: string): Cypress.Chainable<any> {
  if (feeModelName?.includes('Fee')) {
    cy.log(companyResponse.uuid);
    cy.log(feeModelName);
    postChargeableEntity(companyResponse.uuid, feeModelName);
  }
  return cy.wrap(companyResponse);
}

function addUserAgency(companyResponse: any, feeModelInfo: string, vip: boolean): Cypress.Chainable<any> {
  const companyUuid = companyResponse.uuid;
  const agencyData = getAgencyData(companyResponse, feeModelInfo, vip);
  cy.log(feeModelInfo);
  cy.task('putDataInCache', { key: 'agencyUuid', data: agencyData.agency.uuid });
  if (!feeModelInfo.includes('Fee')) {
    cy.log('feeModelInfo does not include FEE');

    cy.task('putDataInCache', { key: 'tripFee', data: '5.00' });
  }
  cy.allure().logStep(`Add user agency for ${companyUuid} company`);

  return cy
    .request({
      method: HttpRequest.Put,
      url: SuperAdminClient.travelOpsCompaniesAgencyUrl
        .replace('{companyUuid}', companyUuid)
        .replace('{agencyUuid}', agencyData.agency.uuid),
      headers: {
        Authorization: AUTH_TOKEN,
        Accept: Accept.ApplicationJson
      },
      body: agencyData,
      retryOnStatusCodeFailure: true
    })
    .then(({ body, status }) => {
      expect(status, 'Successfully updated company with agency ').to.equal(HttpStatusCodes.Ok);
      return body;
    });
}

function getAgencyData(companyResponse: any, feeModelInfo: string, vip: boolean) {
  const companyUuid = companyResponse?.uuid;
  const companyId = companyResponse?.domain?.split('.')[0];
  if (vip) {
    return AgencyData.generateUSDAgencyVIPData(companyUuid, companyId);
  }
  if (feeModelInfo.includes(AgencyCurrency.Euro)) {
    return AgencyData.generateEURAgencyData(companyUuid, companyId);
  }
  return AgencyData.generateUSDAgencyData(companyUuid, companyId);
}

function isUserRegistered(
  userAgencyResponse: any,
  users: User_Type[],
  providers: Provider_Type[]
): Cypress.Chainable<any> {
  const domain = userAgencyResponse.companyId;

  const usersString = getEnumsAsString(users);
  const providersString = getEnumsAsString(providers);

  const email =
    providersString + '_' + usersString + '_' + Random.randomString(3) + '@' + `${domain}` + `.staging.tripactions.xyz`;

  cy.task('putDataInCache', { key: 'companyCreationEmail', data: email });
  cy.allure().logStep(`Is user is already registered : ${email}`);

  return cy
    .request({
      method: HttpRequest.Get,
      url: AccountRegistrationClient.emailInfo,
      headers: {
        Authorization: AUTH_TOKEN,
        Accept: Accept.ApplicationJson
      },
      qs: { email },
      retryOnStatusCodeFailure: true
    })
    .then(({ status }) => {
      expect(status, 'User is not registered').to.equal(HttpStatusCodes.Ok);
      return email;
    });
}

function getEnumsAsString<T extends string | number>(enumArray: T[]) {
  let enumsString = '';
  enumArray.forEach((enumItem: T) => {
    if (enumsString) {
      enumsString += '_';
    }
    enumsString += enumItem.toString();
  });
  return enumsString;
}

function sendPreSignupEmail(email: string): Cypress.Chainable<any> {
  cy.allure().logStep(`Send pre sign up email for ${email}`);

  return cy
    .request({
      method: HttpRequest.Post,
      url: AccountRegistrationClient.preSignUpEmail,
      headers: {
        Authorization: AUTH_TOKEN,
        Accept: Accept.ApplicationJson
      },
      qs: { email },
      retryOnStatusCodeFailure: true
    })
    .then(({ status }) => {
      expect(status, 'Sign up email triggered').to.equal(HttpStatusCodes.Accepted);
      return email;
    });
}

function getSignUpToken(email: string): Cypress.Chainable<any> {
  cy.allure().logStep(`Retrieve sign up token for ${email}`);

  return cy
    .request({
      method: HttpRequest.Get,
      url: SuperAdminClient.retrieveSignupToken,
      headers: {
        Authorization: AUTH_TOKEN,
        Accept: Accept.ApplicationJson
      },
      qs: { email },
      retryOnStatusCodeFailure: true
    })
    .then(({ body, status }) => {
      expect(status, 'Signup token retrieved').to.equal(HttpStatusCodes.Ok);
      return { email, body };
    });
}

function createAccount(email: string, token: string, password: string): Cypress.Chainable<any> {
  cy.allure().logStep(`Creating account for ${email}`);
  cy.task('putDataInCache', { key: 'userEmail', data: email });
  return cy.task('getDataFromCache', 'agencyUuid').then((agencyData) => {
    return cy
      .request({
        method: HttpRequest.Post,
        url: AccountRegistrationClient.signUp,
        headers: {
          Authorization: AUTH_TOKEN,
          Accept: Accept.ApplicationJson
        },
        body: {
          emailVerificationToken: token,
          email: email,
          familyName: faker.name.lastName().replace(/'/g, ''),
          givenName: faker.name.firstName(),
          password: password,
          agencyCompanyAssociationUuid: agencyData
        },
        retryOnStatusCodeFailure: true
      })
      .then(({ status }) => {
        expect(status, 'Account created').to.equal(HttpStatusCodes.Created);
        return email;
      });
  });
}

function setTravelFrequency(travelFrequency = '1-2 times a month'): Cypress.Chainable<any> {
  cy.allure().logStep('Setting travel frequency');

  return cy
    .request({
      method: HttpRequest.Patch,
      url: AccountRegistrationClient.userProfile,
      headers: {
        Authorization: AUTH_TOKEN,
        Accept: Accept.ApplicationJson
      },
      body: {
        travelFrequency: travelFrequency
      },
      retryOnStatusCodeFailure: true
    })
    .then(({ status }) => {
      expect(status, 'Updated travel frequency').to.equal(HttpStatusCodes.Ok);
    });
}

function getAndCheckSplitsForProviders(providers: Provider_Type[]): Cypress.Chainable<any> {
  cy.allure().logStep('Update splits');
  if (AUTH_TOKEN) {
    return cy
      .request({
        method: HttpRequest.Get,
        url: AccountRegistrationClient.getSplits,
        headers: {
          Authorization: AUTH_TOKEN,
          Accept: Accept.ApplicationJson
        },
        retryOnStatusCodeFailure: true
      })
      .then(({ body, status }) => {
        expect(status, 'Updated splits').to.equal(HttpStatusCodes.Ok);
        cy.task('putDataInCache', {
          key: 'splitList',
          data: body
        });
        SplitUtility.checkOnlyRelevantSplitIsOn(providers);
      });
  } else {
    throw new Error('Please login before calling api endpoints');
  }
}

function updatePassengerInfoDefault(phoneNumber = '+12029064406'): Cypress.Chainable<any> {
  cy.allure().logStep('Update passenger information');
  const firstName = faker.name.firstName();
  const lastName = faker.name.lastName().replace(/'/g, '');
  const travelerName = firstName + ' ' + lastName;
  cy.task('putDataInCache', {
    key: 'travelerName',
    data: travelerName
  });

  return cy
    .request({
      method: HttpRequest.Put,
      url: AccountRegistrationClient.passengerInfo,
      headers: {
        Authorization: AUTH_TOKEN,
        Accept: Accept.ApplicationJson
      },
      body: {
        givenName: firstName,
        familyName: lastName,
        passport: {
          countryOfCitizenship: 'US',
          number: '31195855',
          countryOfIssue: 'US',
          issuedOn: '2020-12-12',
          expiresOn: '2030-12-12'
        },
        birthdate: '1990-07-17',
        gender: 'X',
        secondaryGender: 'MALE',
        contact: {
          phone: {
            number: phoneNumber
          }
        }
      },
      retryOnStatusCodeFailure: true
    })
    .then(({ status }) => {
      expect(status, 'Update passenger default information').to.equal(HttpStatusCodes.Ok);
    });
}

function addPersonalPaymentMethod(
  companyResponse?: any,
  creditCardInfo?: CreditCard,
  current_attempt = 1
): Cypress.Chainable<any> {
  cy.allure().logStep('Add personal payment method');

  const creditCard = creditCardInfo
    ? creditCardInfo
    : {
        fullName: 'JMeter',
        givenName: faker.name.firstName(),
        familyName: faker.name.lastName().replace(/'/g, ''),
        number: '4242424242424242',
        expirationMonth: '1',
        expirationYear: '2026',
        cvc: '123'
      };

  return cy
    .request({
      method: HttpRequest.Post,
      url: AccountRegistrationClient.paymentMethod,
      headers: {
        Authorization: AUTH_TOKEN,
        Accept: Accept.ApplicationJson
      },
      body: {
        billingAddress: {
          addressLine1: 'Strawinskylaan',
          addressLine2: '1337',
          state: 'AL',
          city: 'Amsterdam',
          country: 'NL',
          postalCode: '1337AM'
        },
        creditCard,
        nickname: 'Corporate Visa Card'
      },
      failOnStatusCode: false
    })
    .then(({ status, body }) => {
      if ((status === 500 || status === 504) && current_attempt < 5) {
        cy.allure().logStep('Retrying adding personal payment method, attempt: ' + current_attempt);
        cy.wait(Timeouts.MEDIUM_TIMEOUT_20_SEC.timeout);
        cy.allure().logStep('Applied wait time: ' + Timeouts.MEDIUM_TIMEOUT_20_SEC.timeout);
        addPersonalPaymentMethod(companyResponse, creditCardInfo, ++current_attempt);
      }
      expect(status, 'Payment method updated').to.equal(HttpStatusCodes.Created);
      cy.task('putDataInCache', { key: 'paymentMethod', data: body });
      const paymentMethodUuId = body.uuid;
      cy.task('putDataInCache', { key: 'paymentMethodUuId', data: paymentMethodUuId });
    });
}

function setUserByType(userResponse: any, users: User_Type[]): Cypress.Chainable<any> | PromiseLike<string> {
  users.forEach((user) => {
    if (user === 'admin' || user === 'programManager') {
      cy.allure().logStep('Set user as ' + users.toString());
      return cy
        .request({
          method: HttpRequest.Patch,
          url: SuperAdminClient.adminUsers + `/${userResponse.uuid}`,
          headers: {
            Authorization: AUTH_TOKEN,
            Accept: Accept.ApplicationJson
          },
          body: getUserAvailableObject(users),
          retryOnStatusCodeFailure: true
        })
        .then(({ body, status }) => {
          expect(status, 'Set user as ' + users.toString()).to.equal(HttpStatusCodes.Ok);
          return body.email;
        });
    }
  });

  return new Cypress.Promise((resolve) => {
    resolve(userResponse.email);
  });
}

function setUserAsVIP(userResponse: any, vip: boolean): Cypress.Chainable<any> | PromiseLike<any> {
  if (vip) {
    cy.allure().logStep(`Set user as VIP = ${vip}`);
    return cy
      .request({
        method: HttpRequest.Patch,
        url: SuperAdminClient.adminUsers + `/${userResponse.uuid}`,
        headers: {
          Authorization: AUTH_TOKEN,
          Accept: Accept.ApplicationJson
        },
        body: { vip: true },
        retryOnStatusCodeFailure: true
      })
      .then(({ body, status }) => {
        expect(status, 'Set user as VIP').to.equal(HttpStatusCodes.Ok);
        cy.log('Returned response email : ' + body.email);
        cy.task('putDataInCache', {
          key: 'userResponse',
          data: userResponse
        }).then(() => {
          return { userResponse };
        });
      });
  }

  return new Cypress.Promise((resolve) => {
    resolve({ userResponse });
  });
}

function getUserAvailableObject(users: User_Type[]): any {
  const userParameters: { [key in User_Type]?: boolean } = {};
  users.forEach((user) => {
    userParameters[user] = true;
  });
  return userParameters;
}

function retrieveUserByEmail(email: string, current_attempt = 1): Cypress.Chainable<any> {
  cy.allure().logStep('Retrieve user by email');
  cy.wait(Timeouts.SHORT_TIMEOUT_3_SEC.timeout); // Wait newly created user to sync
  return cy
    .request({
      method: HttpRequest.Get,
      url: SuperAdminClient.adminUsers,
      headers: {
        Authorization: AUTH_TOKEN,
        Accept: Accept.ApplicationJson
      },
      qs: {
        q: email,
        size: 12
      },
      retryOnStatusCodeFailure: true
    })
    .then(({ body, status }) => {
      expect(status, 'Retrieve user by email').to.equal(HttpStatusCodes.Ok);
      const response = body?._embedded?.users;
      if (!response) {
        // Temporary logging to track the response body without an array of users. To be deleted later
        Logs.logToCypressAndAllure(
          `Request: ${HttpRequest.Get} ${SuperAdminClient.adminUsers}\n Response body: ${JSON.stringify(body)}`
        );
      }
      const userResponse = response?.find(function (user: any) {
        cy.log('Returned response email : ' + user.email);
        return user.email === email.toLowerCase();
      });
      if (!userResponse) {
        if (current_attempt > 3) {
          cy.log(`User response return empty after ${current_attempt} attempts`);
          throw new Error(`User response return empty after ${current_attempt} attempts`);
        } else {
          ++current_attempt;
          cy.wait(Timeouts.MEDIUM_TIMEOUT_10_SEC.timeout);
          cy.log('Retrying since user response return empty. Attempt: ' + current_attempt);
          return retrieveUserByEmail(email, current_attempt);
        }
      }
      cy.task('putDataInCache', {
        key: 'userResponse',
        data: userResponse
      }).then(() => {
        return { userResponse };
      });
    });
}

function setLocale(locale: string): Cypress.Chainable<any> {
  cy.allure().logStep('Setting locale for user');

  return cy
    .request({
      method: HttpRequest.Patch,
      url: AccountRegistrationClient.userInfo,
      headers: {
        Authorization: AUTH_TOKEN,
        contentType: Accept.ApplicationJson
      },
      body: {
        locale: locale
      },
      retryOnStatusCodeFailure: true
    })
    .then(({ status }) => {
      expect(status, 'Updated locale').to.equal(HttpStatusCodes.Ok);
    });
}

function setCompanyCustomField(companyCustomField: CustomField, options: CustomFiledOption[]) {
  return cy
    .request({
      method: HttpRequest.Post,
      url: AccountRegistrationClient.customFields,
      headers: {
        Authorization: AUTH_TOKEN,
        Accept: Accept.ApplicationJson
      },
      body: CustomFieldsUtility.generateCompanyTripPurposeData(companyCustomField, options),
      retryOnStatusCodeFailure: true
    })
    .then(({ status }) => {
      expect(
        status,
        `Custom field ${companyCustomField} updated with values: ${options.map((option) => option.value)}`
      ).to.equal(HttpStatusCodes.Ok);
    });
}
