import React from 'react';
import {
  cleanup,
  fireEvent,
  render,
  RenderResult,
  waitFor,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import startCase from 'lodash/startCase';

import {
  constructOrganizationURL,
  dataFixtures,
  hostDefaults,
  urlFixtures,
} from '../data';
import {
  defaultMongoNavContext,
  MongoNavContextProps,
  MongoNavProvider,
} from '../MongoNavContext';
import { ActiveNavElement, CurrentOrganizationInterface } from '../types';

import OrgNav, { OrgNavProps } from './OrgNav';

// data
const { account, currentOrganization, organizations, currentProject } =
  dataFixtures;
const {
  orgNav: { billing, allClusters, admin },
} = urlFixtures;

// this avoids having to explicitly type orgNav with nullable fields
// and then extend it to allow string indexes
const linkNamesToUrls: Record<string, string | undefined> = {
  billing,
  allClusters,
  admin,
};

const linkNamesToText: Record<string, string> = {
  allClusters: 'All Clusters',
  admin: 'Admin',
  billing: 'Billing',
};

describe('packages/mongo-nav/src/org-nav', () => {
  let onOrganizationChange: jest.Mock;

  beforeEach(() => {
    onOrganizationChange = jest.fn();
  });

  afterEach(() => {
    jest.restoreAllMocks();
    cleanup();
  });

  /**
   * Utility function to render the OrgNav with given props and context
   */
  function renderComponent(
    props?: Partial<OrgNavProps>,
    withContext?: Partial<MongoNavContextProps>,
  ): RenderResult {
    const context: MongoNavContextProps = {
      ...defaultMongoNavContext,
      currentOrg: currentOrganization,
      currentProject: currentProject,
      activeNav: ActiveNavElement.OrgNavOrgSettings,
      ...withContext,
    };

    return render(
      <MongoNavProvider context={context}>
        <OrgNav
          account={account}
          data={organizations}
          constructOrganizationURL={constructOrganizationURL}
          onOrganizationChange={onOrganizationChange}
          urls={urlFixtures}
          admin={false}
          hosts={hostDefaults()}
          showProjectNav={true}
          {...props}
        />
      </MongoNavProvider>,
    );
  }

  describe('when rendered with default props', () => {
    it(`does not display the version`, () => {
      const { queryByTestId } = renderComponent();
      const version = queryByTestId('org-nav-on-prem-version');
      expect(version).not.toBeInTheDocument();
    });

    it(`displays the UserMenu and does not display the onPrem User Menu`, () => {
      const { queryByTestId } = renderComponent();
      const userMenu = queryByTestId('user-menu-trigger');
      const onPremUserMenu = queryByTestId('om-user-menu-trigger');

      expect(userMenu).toBeVisible();
      expect(onPremUserMenu).toBeNull();
    });

    Object.keys(linkNamesToUrls).forEach(linkName => {
      const isVisible = linkName !== 'admin';
      it(`${isVisible ? 'displays' : 'does not display'} the ${startCase(
        linkName,
      )} nav`, () => {
        const { queryByText } = renderComponent();

        const navLink =
          queryByText(linkNamesToText[linkName])?.closest('a, button') ?? null;

        if (isVisible) {
          expect(navLink).toBeVisible();

          expect((navLink as HTMLAnchorElement).href).toEqual(
            linkNamesToUrls[linkName],
          );
        } else {
          expect(navLink).toBeNull();
        }
      });
    });

    it(`does not display the payment status badge`, () => {
      const { queryByTestId } = renderComponent();
      const badge = queryByTestId('org-nav-payment-status');
      expect(badge).toBeNull();
    });

    test('it does not render the FedRAMP banner', () => {
      const { queryByTestId } = renderComponent();
      expect(queryByTestId('org-nav-fedramp-banner')).toBeNull();
    });

    test('it displays a Get Help menu to the nav by default', () => {
      const { getByTestId } = renderComponent();
      const getHelp = getByTestId('org-nav-dropdown-get-help');
      expect(getHelp).toBeInTheDocument();
    });
  });

  describe('when rendered as an admin without an active preferences nav', () => {
    it(`displays the payment status badge`, () => {
      const { queryByTestId } = renderComponent(
        {
          admin: true,
        },
        {
          currentOrg: currentOrganization,
        },
      );
      const badge = queryByTestId('org-nav-payment-status');
      expect(badge).toBeInTheDocument();
      expect(badge!.textContent).toEqual('OK');
    });

    it(`displays the payment status badge with the provided displayName`, () => {
      const { queryByTestId } = renderComponent(
        {
          admin: true,
        },
        {
          currentOrg: {
            ...currentOrganization,
            paymentStatusDisplayName: 'INACTIVE',
          } as CurrentOrganizationInterface,
        },
      );
      const badge = queryByTestId('org-nav-payment-status');
      expect(badge).toBeInTheDocument();
      expect(badge!.textContent).toEqual('INACTIVE');
    });

    it(`displays the payment status badge when no displayName is provided`, () => {
      const { queryByTestId } = renderComponent(
        {
          admin: true,
        },
        {
          currentOrg: {
            ...currentOrganization,
            paymentStatusDisplayName: undefined,
          } as CurrentOrganizationInterface,
        },
      );
      const badge = queryByTestId('org-nav-payment-status');
      expect(badge).toBeInTheDocument();
      expect(badge!.textContent).toEqual(currentOrganization!.paymentStatus);
    });

    describe('Links', () => {
      Object.keys(linkNamesToUrls).forEach(
        linkName => {
          it(`displays the ${startCase(linkName)} nav`, () => {
            const { queryByText } = renderComponent(
              { admin: true },
              {
                currentOrg: currentOrganization,
              },
            );
            const navLink =
              queryByText(linkNamesToText[linkName])?.closest('a, button') ??
              null;

            expect(navLink).toBeVisible();
            expect((navLink as HTMLAnchorElement).href).toEqual(
              linkNamesToUrls[linkName],
            );
          });
        },
        // testForNavLink(linkName),
      );
    });
  });

  describe('when rendered as an admin with an active preferences nav', () => {
    const cloudUserNavItems = [
      ActiveNavElement.UserMenuCloudInvitations,
      ActiveNavElement.UserMenuCloudMFA,
      ActiveNavElement.UserMenuCloudOrganizations,
      ActiveNavElement.UserMenuCloudOther,
      ActiveNavElement.UserMenuCloudUserPreferences,
    ];

    cloudUserNavItems.forEach(activeNav => {
      describe(`when the element ${activeNav as string} is active`, () => {
        let queryByTestId: RenderResult['queryByTestId'];
        let queryByText: RenderResult['queryByText'];
        beforeEach(() => {
          ({ queryByTestId, queryByText } = renderComponent(
            {
              admin: true,
            },
            {
              activeNav,
            },
          ));
        });
        it(`does not display the payment status badge`, () => {
          const badge = queryByTestId('org-nav-payment-status');
          expect(badge).toBeNull();
        });

        it(`does not display the version`, () => {
          const version = queryByTestId('org-nav-on-prem-version');
          expect(version).toBeNull();
        });

        Object.keys(linkNamesToUrls).forEach(linkName => {
          const isVisible = ['allClusters', 'admin'].includes(linkName);
          it(`${isVisible ? 'displays' : 'does not display'} the ${startCase(
            linkName,
          )} nav`, () => {
            const navLink =
              queryByText(linkNamesToText[linkName])?.closest('a, button') ??
              null;

            if (isVisible) {
              expect(navLink).toBeVisible();

              expect((navLink as HTMLAnchorElement).href).toEqual(
                linkNamesToUrls[linkName],
              );
            } else {
              expect(navLink).toBeNull();
            }
          });
        });
      });
    });
  });

  describe('the Get Help dropdown displays correctly', () => {
    test('support link appears in menu', () => {
      const { getByTestId } = renderComponent();
      const getHelp = getByTestId('org-nav-dropdown-get-help');
      userEvent.click(getHelp);
      const support = getByTestId('org-nav-support-link');
      expect(support).toBeInTheDocument();
    });

    test('docs link appears in menu', () => {
      const { getByTestId } = renderComponent();
      const getHelp = getByTestId('org-nav-dropdown-get-help');
      userEvent.click(getHelp);
      const docs = getByTestId('org-nav-docs-link');
      expect(docs).toBeInTheDocument();
    });
  });

  describe('when rendered without project nav', () => {
    it('shows project access as disabled', () => {
      const { getByTestId } = renderComponent({
        showProjectNav: false,
      });

      const accessMgrDropdown = getByTestId('org-nav-access-manager-dropdown');
      userEvent.click(accessMgrDropdown);

      const accessManagerProject = getByTestId(
        'org-nav-dropdown-project-access-manager',
      );
      expect(accessManagerProject).toHaveAttribute('aria-disabled', 'true');
      expect(accessManagerProject).toContainHTML('None');
    });
  });

  describe('when rendered onPrem', () => {
    let queryByTestId: RenderResult['queryByTestId'];
    let getByTestId: RenderResult['getByTestId'];
    let queryByText: RenderResult['queryByText'];

    beforeEach(
      () =>
        ({ queryByTestId, getByTestId, queryByText } = renderComponent({
          onPremEnabled: true,
          onPremVersion: '4.4.0',
        })),
    );

    it(`does not display the payment status badge`, () => {
      const badge = queryByTestId('org-nav-payment-status');
      expect(badge).toBeNull();
    });

    it(`displays the version`, () => {
      const version = queryByTestId('org-nav-on-prem-version');
      expect(version).toBeVisible();
    });

    it(`does not display the UserMenu and displays the onPrem User Menu`, () => {
      const userMenu = queryByTestId('user-menu-trigger');
      const onPremUserMenu = queryByTestId('om-user-menu-trigger');
      expect(onPremUserMenu).toBeVisible();
      expect(userMenu).toBeNull();
    });

    it(`does not display the MFA option`, () => {
      const onPremUserMenu = getByTestId('om-user-menu-trigger');
      fireEvent.click(onPremUserMenu);

      const onPremUserMenuMFA = queryByTestId('om-user-menuitem-mfa');
      expect(onPremUserMenuMFA).toBeNull();
    });

    Object.keys(linkNamesToUrls).forEach(linkName => {
      const isVisible = !['billing', 'admin'].includes(linkName);

      it(`${isVisible ? 'displays' : 'does not display'} the ${startCase(
        linkName,
      )} nav`, () => {
        const navLink =
          queryByText(linkNamesToText[linkName])?.closest('a, button') ?? null;

        if (isVisible) {
          expect(navLink).toBeVisible();

          expect((navLink as HTMLAnchorElement).href).toEqual(
            linkNamesToUrls[linkName],
          );
        } else {
          expect(navLink).toBeNull();
        }
      });
    });
  });

  describe('when rendered onPrem and onPremMFA is true', () => {
    let queryByTestId: RenderResult['queryByTestId'];
    let getByTestId: RenderResult['getByTestId'];

    beforeEach(
      () =>
        ({ queryByTestId, getByTestId } = renderComponent({
          onPremEnabled: true,
          onPremMFA: true,
        })),
    );

    it(`displays the MFA option`, () => {
      const onPremUserMenu = getByTestId('om-user-menu-trigger');
      fireEvent.click(onPremUserMenu);

      const onPremUserMenuMFA = queryByTestId('om-user-menuitem-mfa');
      expect(onPremUserMenuMFA).toBeInTheDocument();
      expect((onPremUserMenuMFA as HTMLAnchorElement).href).toBe(
        'https://cloud.mongodb.com/v2#/account/2fa',
      );
    });
  });

  describe('when rendered onPrem and admin is set to true', () => {
    let queryByText: RenderResult['queryByText'];
    beforeEach(
      () =>
        ({ queryByText } = renderComponent({
          onPremEnabled: true,
          onPremVersion: '4.4.0',
          admin: true,
        })),
    );

    Object.keys(linkNamesToUrls).forEach(
      linkName => {
        const isVisible = linkName !== 'billing';
        it(`${isVisible ? 'displays' : 'does not display'} the ${startCase(
          linkName,
        )} nav`, () => {
          const navLink =
            queryByText(linkNamesToText[linkName])?.closest('a, button') ??
            null;

          if (isVisible) {
            expect(navLink).toBeVisible();

            expect((navLink as HTMLAnchorElement).href).toEqual(
              linkNamesToUrls[linkName],
            );
          } else {
            expect(navLink).toBeNull();
          }
        });
      },
      // testForNavLink(linkName, ),
    );
  });

  describe('when rendered as an ops manager admin with an active account nav', () => {
    const onPremUserNavItems = [
      ActiveNavElement.UserMenuOnPremInvitations,
      ActiveNavElement.UserMenuOnPremOrganizations,
      ActiveNavElement.UserMenuOnPremOther,
      ActiveNavElement.UserMenuOnPremPersonalization,
      ActiveNavElement.UserMenuOnPremProfile,
      ActiveNavElement.UserMenuOnPremPublicApiAccess,
      ActiveNavElement.UserMenuOnPremTwoFactorAuth,
    ];

    onPremUserNavItems.forEach(activeNav => {
      describe(`when the element ${activeNav as string} is active`, () => {
        let queryByText: RenderResult['queryByText'];
        let queryByTestId: RenderResult['queryByTestId'];

        beforeEach(
          () =>
            ({ queryByText, queryByTestId } = renderComponent(
              {
                onPremEnabled: true,
                onPremVersion: '4.4.0',
                admin: true,
              },
              {
                activeNav,
              },
            )),
        );

        it(`does not display the payment status badge`, () => {
          const badge = queryByText('org-nav-payment-status');
          expect(badge).toBeNull();
        });

        it('displays the version', () => {
          const version = queryByTestId('org-nav-on-prem-version');
          expect(version).toBeVisible();
        });

        Object.keys(linkNamesToUrls).forEach(linkName => {
          const isVisible = ['allClusters', 'admin'].includes(linkName);

          it(`${isVisible ? 'displays' : 'does not display'} the ${startCase(
            linkName,
          )} nav`, () => {
            const navLink =
              queryByText(linkNamesToText[linkName])?.closest('a, button') ??
              null;

            if (isVisible) {
              expect(navLink).toBeVisible();

              expect((navLink as HTMLAnchorElement).href).toEqual(
                linkNamesToUrls[linkName],
              );
            } else {
              expect(navLink).toBeNull();
            }
          });
        });
      });
    });
  });

  describe('the Access Manager element displays correctly', () => {
    describe('renders according to `showUnifiedAccessManagementNav` prop', () => {
      test('showUnifiedAccessManagementNav == false', () => {
        const { getByTestId } = renderComponent(
          {
            showProjectNav: true,
            currentProjectName: 'Test Project',
            currentProjectId: 'test-project-id',
          },
          {
            showUnifiedAccessManagementNav: false,
          },
        );

        const element = getByTestId('org-nav-access-manager-dropdown');

        expect(element).toBeInTheDocument();
        expect(element.tagName).toBe('BUTTON');
      });

      test('showUnifiedAccessManagementNav == true', () => {
        const { getByTestId } = renderComponent(
          {
            showProjectNav: true,
            currentProjectName: 'Test Project',
            currentProjectId: 'test-project-id',
          },
          {
            showUnifiedAccessManagementNav: true,
          },
        );

        const element = getByTestId('org-nav-access-manager-dropdown');

        expect(element).toBeInTheDocument();
        expect(element.tagName).toBe('A');
      });
    });

    describe('and the accessibility props are properly set', () => {
      let getByTestId: RenderResult['getByTestId'];
      beforeEach(
        () =>
          ({ getByTestId } = renderComponent({
            showProjectNav: true,
            currentProjectName: 'Test Project',
            currentProjectId: 'test-project-id',
          })),
      );

      test('when closed, "aria-expanded" is set to false', () => {
        expect(
          getByTestId('org-nav-access-manager-dropdown').getAttribute(
            'aria-expanded',
          ),
        ).toBe('false');
      });

      test('when open, "aria-expanded" is set to true', () => {
        fireEvent.click(getByTestId('org-nav-access-manager-dropdown'));
        expect(
          getByTestId('org-nav-access-manager-dropdown').getAttribute(
            'aria-expanded',
          ),
        ).toBe('true');
      });
    });
    describe('when onPrem is true', () => {
      test('and currentProject exists, the Project Access Manager link is not disabled and project name is displayed', () => {
        const { getByTestId } = renderComponent({
          onPremEnabled: true,
          showProjectNav: false,
          currentProjectName: 'Test Project',
          currentProjectId: 'test-project-id',
        });
        userEvent.click(getByTestId('org-nav-access-manager-dropdown'));

        waitFor(() => {
          const accessManagerProject = getByTestId(
            'org-nav-dropdown-project-access-manager',
          );
          expect(accessManagerProject.innerHTML.includes('Test Project')).toBe(
            true,
          );
          expect(accessManagerProject.getAttribute('aria-disabled')).toBe(
            'false',
          );
        });
      });

      test('and currentProject does not exist, the Project Access Manager link is disabled and the project name appears as "None"', () => {
        const { getByTestId } = renderComponent({
          onPremEnabled: true,
          showProjectNav: false,
        });
        userEvent.click(getByTestId('org-nav-access-manager-dropdown'));

        waitFor(() => {
          const accessManagerProject = getByTestId(
            'org-nav-dropdown-project-access-manager',
          );
          expect(accessManagerProject.innerHTML.includes('None')).toBe(true);
          expect(accessManagerProject.getAttribute('aria-disabled')).toBe(
            'true',
          );
        });
      });
    });

    describe('when onPrem is false', () => {
      test('and showProjectNav is true, the Project Access Manager link is not disabled and project name is displayed', () => {
        const { getByTestId } = renderComponent({
          showProjectNav: true,
          currentProjectName: 'Test Project',
          currentProjectId: 'test-project-id',
        });
        userEvent.click(getByTestId('org-nav-access-manager-dropdown'));

        waitFor(() => {
          const accessManagerProject = getByTestId(
            'org-nav-dropdown-project-access-manager',
          );
          expect(accessManagerProject.innerHTML.includes('Test Project')).toBe(
            true,
          );
          expect(accessManagerProject.getAttribute('aria-disabled')).toBe(
            'false',
          );
        });
      });

      test('and showProjectNav is false, the Project Access Manager link is disabled and the project name appears as "None"', () => {
        const { getByTestId } = renderComponent({
          showProjectNav: false,
        });
        userEvent.click(getByTestId('org-nav-access-manager-dropdown'));

        waitFor(() => {
          const accessManagerProject = getByTestId(
            'org-nav-dropdown-project-access-manager',
          );
          expect(accessManagerProject.innerHTML.includes('None')).toBe(true);
          expect(accessManagerProject.getAttribute('aria-disabled')).toBe(
            'true',
          );
        });
      });
    });
  });

  describe('when environment is "government"', () => {
    test('it renders a FedRAMP banner in the org nav', () => {
      const { getByTestId } = renderComponent({ environment: 'government' });
      expect(getByTestId('org-nav-fedramp-banner')).toBeVisible();
    });
  });

  test('when currentOrganization is null', () => {
    const { getByTestId } = renderComponent({}, { currentOrg: null });
    const orgSelect = getByTestId('org-trigger');
    const accessManager = getByTestId('org-nav-access-manager-dropdown');
    const billing = getByTestId('org-nav-billing');
    const userMenu = getByTestId('user-menu-trigger');

    expect(orgSelect).toBeInTheDocument();
    expect(accessManager).toBeInTheDocument();
    expect(billing).toBeInTheDocument();
    expect(userMenu).toBeInTheDocument();

    expect(orgSelect).not.toBeDisabled();
    expect(accessManager).toBeDisabled();
    expect(billing).toBeDisabled();
    expect(userMenu).not.toBeDisabled();
  });
});
