import { keyStore } from 'utils';
import { baseAppUrl } from 'data/services/lms/urls';
import { EXECUTIVE_EDUCATION_COURSE_MODES } from 'data/constants/course';

import simpleSelectors from './simpleSelectors';
import * as module from './courseCard';

jest.mock('data/services/lms/urls', () => ({
  baseAppUrl: url => ({ baseAppUrl: url }),
}));

jest.mock('./simpleSelectors', () => ({
  mkCardSelector: (simpleSelector, selector) => ({
    mkCardSelector: { selector, simpleSelector },
  }),
  cardSimpleSelectors: jest.requireActual('./simpleSelectors').cardSimpleSelectors,
}));

const { courseCard } = module;
const { cardSimpleSelectors } = simpleSelectors;

const moduleKeys = keyStore(module);

let testData;
let selector;
let simpleSelector;
let selected;

const dates = {
  today: new Date(),
  tomorrow: new Date(),
  nextYear: new Date(),
  yesterday: new Date(),
};
dates.tomorrow.setDate(dates.tomorrow.getDate() + 1);
dates.nextYear.setDate(dates.nextYear.getDate() + 365);
dates.yesterday.setDate(dates.yesterday.getDate() - 1);

/*
 * Takes a selector as input and fetches the referenced selector and simpleSelector for
 * the selector to be tested, to be stored in global variables.
 * Also sets `selected` global variable is loaded with the passed data, which is stored
 * as `testData`.
 */
const loadSelector = (sel, data) => {
  ({ simpleSelector, selector } = sel.mkCardSelector);
  testData = data;
  selected = selector(data);
};

describe('courseCard selectors module', () => {
  describe('loadDateVal helper function', () => {
    it('returns passed date value converted to Date', () => {
      const testDate = '2000-10-10';
      expect(module.loadDateVal(testDate)).toEqual(new Date(testDate));
    });
    it('returns null if no value is passed', () => {
      expect(module.loadDateVal()).toEqual(null);
    });
  });
  describe('courseCard selectors', () => {
    let dateSpy;
    beforeEach(() => {
      dateSpy = jest.spyOn(module, moduleKeys.loadDateVal);
      dateSpy.mockImplementation(v => new Date(v));
    });
    afterEach(() => {
      dateSpy.mockRestore();
    });
    describe('certificate selector', () => {
      beforeEach(() => {
        loadSelector(courseCard.certificate, {
          certPreviewUrl: 'test-cert-preview-url',
          isDownloadable: 'test-is-downloadable',
          isRestricted: 'test-is-restricted',
          isEarned: false,
          availableDate: '2020-10-10',
        });
      });
      it('returns a card selector based on certificate cardSimpleSelector', () => {
        expect(simpleSelector).toEqual(cardSimpleSelectors.certificate);
      });
      it('returns {} object if null certificate received', () => {
        expect(selector(null)).toEqual({});
      });
      it('passes availableDate, converted to a date', () => {
        expect(selected.availableDate).toMatchObject(new Date(testData.availableDate));
      });
      it('passes [isDownloadable, isRestricted]', () => {
        expect(selected.isDownloadable).toEqual(testData.isDownloadable);
        expect(selected.isRestricted).toEqual(testData.isRestricted);
      });
      it('passes certPreviewUrl as app url', () => {
        expect(selected.certPreviewUrl).toEqual(baseAppUrl(testData.certPreviewUrl));
      });
      describe('isEarnedButUnavailable', () => {
        it('passes true iff certificate is earned but availableDate is in the future', () => {
          const testSelector = (data, expected) => {
            expect(selector({ ...testData, ...data }).isEarnedButUnavailable).toEqual(expected);
          };
          testSelector({ isEarned: true, availableDate: dates.today }, false);
          testSelector({ isEarned: true, availableDate: dates.yesterday }, false);
          testSelector({ isEarned: true, availableDate: dates.tomorrow }, true);
          testSelector({ isEarned: false, availableDate: dates.tomorrow }, false);
        });
      });
    });
    describe('course selector', () => {
      beforeEach(() => {
        loadSelector(courseCard.course, {
          bannerImgSrc: 'test-banner-img-src',
          courseNumber: 'test-course-number',
          courseName: 'test-course-name',
          socialShareUrl: 'test-social-share-url',
        });
      });
      it('returns a card selector based on course cardSimpleSelector', () => {
        expect(simpleSelector).toEqual(cardSimpleSelectors.course);
      });
      it('passes bannerImgSrc, converted to a baseAppUrl', () => {
        expect(selected.bannerImgSrc).toEqual(baseAppUrl(testData.bannerImgSrc));
      });
      it('passes [courseNumber, courseName, socialShareUrl]', () => {
        expect(selected.courseNumber).toEqual(testData.courseNumber);
        expect(selected.courseName).toEqual(testData.courseName);
        expect(selected.socialShareUrl).toEqual(testData.socialShareUrl);
      });
    });
    describe('courseProvider selector', () => {
      beforeEach(() => {
        loadSelector(courseCard.courseProvider, { name: 'test-provider-name' });
      });
      it('returns a card selector based on courseProvider cardSimpleSelector', () => {
        expect(simpleSelector).toEqual(cardSimpleSelectors.courseProvider);
      });
      describe('name', () => {
        it('passes the provider name if provider is known', () => {
          expect(selected.name).toEqual(testData.name);
        });
        it('passes undefined if provider is not known', () => {
          expect(selector().name).toEqual(undefined);
        });
      });
    });
    describe('courseRun selector', () => {
      beforeEach(() => {
        loadSelector(courseCard.courseRun, {
          endDate: '3000-10-20',
          startDate: '2000-10-20',
          advertisedStart: 'Mid June',

          courseId: 'test-course-id',
          isArchived: 'test-is-archived',
          isStarted: 'test-is-started',

          minPassingGrade: 0.9354,

          homeUrl: 'test-home-url',
          marketingUrl: 'test-marketing-url',

          progressUrl: 'test-progress-url',
          resumeUrl: 'test-resume-url',
          unenrollUrl: 'test-unenroll-url',
        });
      });
      it('returns a card selector based on courseRun cardSimpleSelector', () => {
        expect(simpleSelector).toEqual(cardSimpleSelectors.courseRun);
      });
      it('returns {} object if null courseRun received', () => {
        expect(selector(null)).toEqual({});
      });
      it('passes [endDate, startDate], converted to dates', () => {
        expect(selected.endDate).toEqual(new Date(testData.endDate));
        expect(selected.startDate).toEqual(new Date(testData.startDate));
      });
      it('passes advertised start date', () => {
        expect(selected.advertisedStart).toEqual(testData.advertisedStart);
      });
      it('passes [courseId, isArchived, isStarted]', () => {
        expect(selected.courseId).toEqual(testData.courseId);
        expect(selected.isArchived).toEqual(testData.isArchived);
        expect(selected.isStarted).toEqual(testData.isStarted);
      });
      it('passes minPassingGrade floored from float to a percentage value', () => {
        expect(selected.minPassingGrade).toEqual(93);
      });
      it('passes [homeUrl, marketingUrl]', () => {
        expect(selected.homeUrl).toEqual(testData.homeUrl);
        expect(selected.marketingUrl).toEqual(testData.marketingUrl);
      });
      it('passes [progressUrl, unenrollUrl, resumeUrl], converted to baseAppUrl', () => {
        expect(selected.progressUrl).toEqual(baseAppUrl(testData.progressUrl));
        expect(selected.resumeUrl).toEqual(baseAppUrl(testData.resumeUrl));
        expect(selected.unenrollUrl).toEqual(baseAppUrl(testData.unenrollUrl));
      });
    });
    describe('credit selector', () => {
      const credit = {
        providerStatusUrl: 'test-provider-status-url',
        providerName: 'test-provider-name',
        providerId: 'test-provider-id',
        error: 'test-provider-id',
        purchased: 'test-purchased',
        requestStatus: 'test-request-status',
      };
      it('returns a card selector based on credit cardSimpleSelector', () => {
        loadSelector(courseCard.credit, {});
        expect(simpleSelector).toEqual(cardSimpleSelectors.credit);
      });
      it('returns { isEligible: false } if empty object received for credit', () => {
        loadSelector(courseCard.credit, {});
        expect(selected).toEqual({ isEligible: false });
      });
      describe('credit fields when credit object is passed', () => {
        beforeEach(() => {
          loadSelector(courseCard.credit, credit);
        });
        it('returns isEligible: true', () => {
          expect(selected.isEligible).toEqual(true);
        });
        it('returns provider status url, name, and id', () => {
          expect(selected.providerStatusUrl).toEqual(credit.providerStatusUrl);
          expect(selected.providerName).toEqual(credit.providerName);
          expect(selected.providerId).toEqual(credit.providerId);
        });
        it('returns error, purchased and requestStatus fields', () => {
          expect(selected.error).toEqual(credit.error);
          expect(selected.purchased).toEqual(credit.purchased);
          expect(selected.requestStatus).toEqual(credit.requestStatus);
        });
      });
    });
    describe('enrollment selector', () => {
      const defaultData = {
        coursewareAccess: {
          isStaff: false,
          hasUnmetPrereqs: false,
          isTooEarly: false,
        },
        isEnrolled: 'test-is-enrolled',
        lastEnrolled: 'test-last-enrolled',
        hasStarted: 'test-has-started',
        accessExpirationDate: '3000-10-20',
        canUpgrade: 'test-can-upgrade',
        isAudit: 'test-is-audit',
        isAuditAccessExpired: 'test-is-audit-access-expired',
        isVerified: 'test-is-verified',
        isEmailEnabled: 'test-is-email-enabled',
        mode: 'default',
      };
      beforeEach(() => {
        loadSelector(courseCard.enrollment, defaultData);
      });
      it('returns a card selector based on enrollment cardSimpleSelector', () => {
        expect(simpleSelector).toEqual(cardSimpleSelectors.enrollment);
      });
      it('returns { isEnrolled: false } object if null enrollment received', () => {
        expect(selector(null)).toEqual({ isEnrolled: false });
      });
      it('passes [coursewareAccess, hasStarted, isEnrolled, lastEnrolled]', () => {
        expect(selected.coursewareAccess).toEqual(testData.coursewareAccess);
        expect(selected.hasStarted).toEqual(testData.hasStarted);
        expect(selected.isEnrolled).toEqual(testData.isEnrolled);
        expect(selected.lastEnrolled).toEqual(testData.lastEnrolled);
      });
      it('passes hasAccess if staff or neither has umet prereqs nor is too early', () => {
        const testAccess = (access, expected) => {
          expect(selector({ ...testData, coursewareAccess: access }).hasAccess).toEqual(expected);
        };
        testAccess({ isStaff: false, hasUnmetPrereqs: false, isTooEarly: false }, true);
        testAccess({ isStaff: false, hasUnmetPrereqs: false, isTooEarly: true }, false);
        testAccess({ isStaff: false, hasUnmetPrereqs: true, isTooEarly: false }, false);
        testAccess({ isStaff: false, hasUnmetPrereqs: true, isTooEarly: true }, false);
        testAccess({ isStaff: true, hasUnmetPrereqs: true, isTooEarly: true }, true);
      });
      it('passes accessExpirationDate, converted to date', () => {
        expect(selected.accessExpirationDate).toEqual(new Date(testData.accessExpirationDate));
      });
      it('passes [canUpgrade, isAudit, isAuditAccessExpired, isVerified]', () => {
        expect(selected.canUpgrade).toEqual(testData.canUpgrade);
        expect(selected.isAudit).toEqual(testData.isAudit);
        expect(selected.isAuditAccessExpired).toEqual(testData.isAuditAccessExpired);
        expect(selected.isVerified).toEqual(testData.isVerified);
      });
      it('passes isEmailEnabled', () => {
        expect(selected.isEmailEnabled).toEqual(testData.isEmailEnabled);
      });
      it('returns isExecEd2UCourse: false if mode is not in EXECUTIVE_EDUCATION_COURSE_MODES', () => {
        expect(selected.isExecEd2UCourse).toEqual(false);
      });
      it('returns isExecEd2UCourse: true if mode is in EXECUTIVE_EDUCATION_COURSE_MODES', () => {
        loadSelector(courseCard.enrollment, { ...defaultData, mode: EXECUTIVE_EDUCATION_COURSE_MODES[0] });
        expect(selected.isExecEd2UCourse).toEqual(true);
      });
    });
    describe('entitlement selector', () => {
      beforeEach(() => {
        loadSelector(courseCard.entitlement, {
          availableSessions: ['test', 'sessions'],
          changeDeadline: '2000-10-20',
          isExpired: 'test-is-expired',
          isFulfilled: 'test-is-fulfilled',
          uuid: 'test-uuid',
        });
      });
      it('returns a card selector based on entilement cardSimpleSelector', () => {
        expect(simpleSelector).toEqual(cardSimpleSelectors.entitlement);
      });
      it('returns { isEntilement: false } if entitlement object is missing or empty', () => {
        expect(selector({})).toEqual({ isEntitlement: false });
        expect(selector()).toEqual({ isEntitlement: false });
      });
      it('passes isEntitlement: true for entilement data', () => {
        expect(selected.isEntitlement).toEqual(true);
      });
      it('passes [availableSessions, isExpired, isFulfilled, uuid]', () => {
        expect(selected.availableSessions).toEqual(testData.availableSessions);
        expect(selected.isExpired).toEqual(testData.isExpired);
        expect(selected.isFulfilled).toEqual(testData.isFulfilled);
        expect(selected.uuid).toEqual(testData.uuid);
      });
      it('passess changeDeadline as changeDeadline, converted to a date', () => {
        expect(selected.changeDeadline).toEqual(new Date(testData.changeDeadline));
      });
      it('passes hasSessions if availableSessions is provided and has a length > 0', () => {
        expect(selected.hasSessions).toEqual(true);
        expect(selector({ ...testData, availableSessions: [] }).hasSessions).toEqual(false);
      });
      it('passes canChange if the deadline is not before current date', () => {
        expect(selector({ ...testData, changeDeadline: dates.yesterday }).canChange).toEqual(false);
        expect(selector({ ...testData, changeDeadline: dates.tomorrow }).canChange).toEqual(true);
      });
      it('passes showExpirationWarning if the deadline is 0-6 months in the future and not fulfilled', () => {
        const testSelector = ({ isFulfilled, changeDeadline }, expected) => {
          expect(
            selector({ ...testData, isFulfilled, changeDeadline }).showExpirationWarning,
          ).toEqual(expected);
        };
        testSelector({ isFulfilled: false, changeDeadline: dates.yesterday }, false);
        testSelector({ isFulfilled: false, changeDeadline: dates.tomorrow }, true);
        testSelector({ isFulfilled: false, changeDeadline: dates.nextYear }, false);
        testSelector({ isFulfilled: true, changeDeadline: dates.nextYear }, false);
      });
    });
    describe('gradeData selector', () => {
      beforeEach(() => {
        loadSelector(courseCard.gradeData, { isPassing: 'test-is-passing' });
      });
      it('returns a card selector based on gradeData cardSimpleSelector', () => {
        expect(simpleSelector).toEqual(cardSimpleSelectors.gradeData);
      });
      it('passes isPassing', () => {
        expect(selected.isPassing).toEqual(testData.isPassing);
      });
    });
    describe('relatedPrograms selector', () => {
      beforeEach(() => {
        const programData = (index) => ({
          bannerImgSrc: `test-banner-img-src-${index}`,
          logoImgSrc: `test-logo-img-src-${index}`,
          numberOfCourses: `test-number-of-courses-${index}`,
          programType: `test-program-type-${index}`,
          programUrl: `test-program-url-${index}`,
          provider: `test-provider-${index}`,
          title: `test-title-${index}`,
        });
        loadSelector(courseCard.relatedPrograms, [
          programData(0),
          programData(1),
          programData(2),
          programData(3),
          programData(4),
        ]);
      });
      it('returns a card selector based on relatedPrograms cardSimpleSelector', () => {
        expect(simpleSelector).toEqual(cardSimpleSelectors.relatedPrograms);
      });
      it('passes [bannerImgSrc, logoImgSrc] for each program', () => {
        selected.list.forEach((row, i) => {
          expect(row.bannerImgSrc).toEqual(testData[i].bannerImgSrc);
          expect(row.logoImgSrc).toEqual(testData[i].logoImgSrc);
        });
      });
      it('passes [numberOfCourses, programType, programUrl, provider, title] for each', () => {
        selected.list.forEach((row, i) => {
          expect(row.numberOfCourses).toEqual(testData[i].numberOfCourses);
          expect(row.programType).toEqual(testData[i].programType);
          expect(row.programUrl).toEqual(testData[i].programUrl);
          expect(row.provider).toEqual(testData[i].provider);
          expect(row.title).toEqual(testData[i].title);
        });
      });
      it('passes number of programs a length', () => {
        expect(selected.length).toEqual(testData.length);
      });
    });
  });
});
