import {Fragment, useState} from 'react';
import {css} from '@emotion/react';
import styled from '@emotion/styled';

import {ModalRenderProps} from 'sentry/actionCreators/modal';
import {Button} from 'sentry/components/button';
import ButtonBar from 'sentry/components/buttonBar';
import Checkbox from 'sentry/components/checkbox';
import LoadingIndicator from 'sentry/components/loadingIndicator';
import {
  InviteModalHook,
  InviteModalRenderFunc,
} from 'sentry/components/modals/inviteMembersModal';
import {StatusMessage} from 'sentry/components/modals/inviteMembersModal/inviteStatusMessage';
import {InviteStatus} from 'sentry/components/modals/inviteMembersModal/types';
import {MissingMemberInvite} from 'sentry/components/modals/inviteMissingMembersModal/types';
import PanelItem from 'sentry/components/panels/panelItem';
import PanelTable from 'sentry/components/panels/panelTable';
import RoleSelectControl from 'sentry/components/roleSelectControl';
import TeamSelector from 'sentry/components/teamSelector';
import {Tooltip} from 'sentry/components/tooltip';
import {IconCheckmark, IconCommit, IconGithub, IconInfo} from 'sentry/icons';
import {t, tct, tn} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {MissingMember, Organization, OrgRole} from 'sentry/types';
import {trackAnalytics} from 'sentry/utils/analytics';
import useApi from 'sentry/utils/useApi';
import {StyledExternalLink} from 'sentry/views/settings/organizationMembers/inviteBanner';

export interface InviteMissingMembersModalProps extends ModalRenderProps {
  allowedRoles: OrgRole[];
  // the API response returns {integration: "github", users: []}
  // but we only ever return Github missing members at the moment
  // so we can simplify the props and state to only store the users (missingMembers)
  missingMembers: MissingMember[];
  organization: Organization;
}

export function InviteMissingMembersModal({
  missingMembers,
  organization,
  allowedRoles,
  closeModal,
  modalContainerRef,
}: InviteMissingMembersModalProps) {
  const initialMemberInvites = (missingMembers || []).map(member => ({
    email: member.email,
    commitCount: member.commitCount,
    role: organization.defaultRole,
    teamSlugs: new Set<string>(),
    externalId: member.externalId,
    selected: true,
  }));
  const [memberInvites, setMemberInvites] =
    useState<MissingMemberInvite[]>(initialMemberInvites);
  const referrer = 'github_nudge_invite';
  const [inviteStatus, setInviteStatus] = useState<InviteStatus>({});
  const [sendingInvites, setSendingInvites] = useState(false);
  const [complete, setComplete] = useState(false);

  const api = useApi();

  if (memberInvites.length === 0 || !organization.access.includes('org:write')) {
    return null;
  }

  const setRole = (role: string, index: number) => {
    setMemberInvites(currentMemberInvites =>
      currentMemberInvites.map((member, i) => {
        if (i === index) {
          member.role = role;
        }
        return member;
      })
    );
  };

  const setTeams = (teamSlugs: string[], index: number) => {
    setMemberInvites(currentMemberInvites =>
      currentMemberInvites.map((member, i) => {
        if (i === index) {
          member.teamSlugs = new Set(teamSlugs);
        }
        return member;
      })
    );
  };

  const selectAll = (checked: boolean) => {
    const selectedMembers = memberInvites.map(m => ({...m, selected: checked}));
    setMemberInvites(selectedMembers);
  };

  const toggleCheckbox = (checked: boolean, index: number) => {
    const selectedMembers = [...memberInvites];
    selectedMembers[index].selected = checked;
    setMemberInvites(selectedMembers);
  };

  const renderStatusMessage = () => {
    if (sendingInvites) {
      return (
        <StatusMessage>
          <LoadingIndicator mini relative hideMessage size={16} />
          {t('Sending organization invitations\u2026')}
        </StatusMessage>
      );
    }

    if (complete) {
      const statuses = Object.values(inviteStatus);
      const sentCount = statuses.filter(i => i.sent).length;
      const errorCount = statuses.filter(i => i.error).length;

      const invites = <strong>{tn('%s invite', '%s invites', sentCount)}</strong>;
      const tctComponents = {
        invites,
        failed: errorCount,
      };

      return (
        <StatusMessage status="success">
          <IconCheckmark size="sm" />
          {errorCount > 0
            ? tct('Sent [invites], [failed] failed to send.', tctComponents)
            : tct('Sent [invites]', tctComponents)}
        </StatusMessage>
      );
    }

    return null;
  };

  const sendMemberInvite = async (invite: MissingMemberInvite) => {
    const data = {
      email: invite.email,
      teams: [...invite.teamSlugs],
      role: invite.role,
    };

    try {
      await api.requestPromise(
        `/organizations/${organization?.slug}/members/?referrer=${referrer}`,
        {
          method: 'POST',
          data,
        }
      );
    } catch (err) {
      const errorResponse = err.responseJSON;

      // Use the email error message if available. This inconsistently is
      // returned as either a list of errors for the field, or a single error.
      const emailError =
        !errorResponse || !errorResponse.email
          ? false
          : Array.isArray(errorResponse.email)
          ? errorResponse.email[0]
          : errorResponse.email;

      const error = emailError || t('Could not invite user');

      setInviteStatus(prevInviteStatus => {
        return {...prevInviteStatus, [invite.email]: {sent: false, error}};
      });
    }

    setInviteStatus(prevInviteStatus => {
      return {...prevInviteStatus, [invite.email]: {sent: true}};
    });
  };

  const sendMemberInvites = async () => {
    setSendingInvites(true);
    await Promise.all(memberInvites.filter(i => i.selected).map(sendMemberInvite));
    setSendingInvites(false);
    setComplete(true);

    if (organization) {
      trackAnalytics(
        'missing_members_invite_modal.requests_sent',
        {
          organization,
        },
        {startSession: true}
      );
    }
  };

  const selectedCount = memberInvites.filter(i => i.selected).length;
  const selectedAll = memberInvites.length === selectedCount;

  const inviteButtonLabel = () => {
    return tct('Invite [memberCount] missing member[isPlural]', {
      memberCount:
        memberInvites.length === selectedCount
          ? `all ${selectedCount}`
          : selectedCount === 0
          ? ''
          : selectedCount,
      isPlural: selectedCount !== 1 ? 's' : '',
    });
  };

  const hookRenderer: InviteModalRenderFunc = ({sendInvites, canSend, headerInfo}) => (
    <Fragment>
      <h4>{t('Invite Your Dev Team')}</h4>
      {headerInfo}
      <StyledPanelTable
        headers={[
          <Checkbox
            key={0}
            aria-label={selectedAll ? t('Deselect All') : t('Select All')}
            onChange={() => selectAll(!selectedAll)}
            checked={selectedAll}
          />,
          t('User Information'),
          <StyledHeader key={1}>
            {t('Recent Commits')}
            <Tooltip title={t('Based on the last 30 days of commit data')}>
              <IconInfo size="xs" />
            </Tooltip>
          </StyledHeader>,
          t('Role'),
          t('Team'),
        ]}
        stickyHeaders
      >
        {memberInvites?.map((member, i) => {
          const checked = memberInvites[i].selected;
          const username = member.externalId.split(':').pop();
          return (
            <Fragment key={i}>
              <div>
                <Checkbox
                  aria-label={t('Select %s', member.email)}
                  checked={checked}
                  onChange={() => toggleCheckbox(!checked, i)}
                />
              </div>
              <StyledPanelItem>
                <ContentRow>
                  <IconGithub size="sm" />
                  <StyledExternalLink href={`https://github.com/${username}`}>
                    @{username}
                  </StyledExternalLink>
                </ContentRow>
                <MemberEmail>{member.email}</MemberEmail>
              </StyledPanelItem>
              <ContentRow>
                <IconCommit size="sm" />
                {member.commitCount}
              </ContentRow>
              <RoleSelectControl
                aria-label={t('Role')}
                data-test-id="select-role"
                disabled={false}
                value={member.role}
                roles={allowedRoles}
                disableUnallowed
                onChange={value => setRole(value?.value, i)}
                menuPortalTarget={modalContainerRef?.current}
                isInsideModal
              />
              <TeamSelector
                organization={organization}
                aria-label={t('Add to Team')}
                data-test-id="select-teams"
                disabled={false}
                placeholder={t('None')}
                onChange={opts => setTeams(opts ? opts.map(v => v.value) : [], i)}
                multiple
                clearable
                menuPortalTarget={modalContainerRef?.current}
                isInsideModal
              />
            </Fragment>
          );
        })}
      </StyledPanelTable>
      <Footer>
        <div>{renderStatusMessage()}</div>
        <ButtonBar gap={1}>
          <Button
            size="sm"
            onClick={() => {
              closeModal();
            }}
          >
            {t('Cancel')}
          </Button>
          <Button
            size="sm"
            priority="primary"
            aria-label={t('Send Invites')}
            onClick={sendInvites}
            disabled={!canSend || selectedCount === 0}
            analyticsEventName="Github Invite Modal: Invite"
            analyticsEventKey="github_invite_modal.invite"
            analyticsParams={{
              invited_all: memberInvites.length === selectedCount,
              invited_count: selectedCount,
            }}
          >
            {inviteButtonLabel()}
          </Button>
        </ButtonBar>
      </Footer>
    </Fragment>
  );

  return (
    <InviteModalHook
      organization={organization}
      willInvite
      onSendInvites={sendMemberInvites}
    >
      {hookRenderer}
    </InviteModalHook>
  );
}

export default InviteMissingMembersModal;

const StyledPanelTable = styled(PanelTable)`
  grid-template-columns: max-content 1fr max-content 1fr 1fr;
  overflow: scroll;
  max-height: 475px;
`;

const StyledHeader = styled('div')`
  display: flex;
  gap: ${space(0.5)};
`;

const StyledPanelItem = styled(PanelItem)`
  flex-direction: column;
`;

const Footer = styled('div')`
  display: flex;
  justify-content: space-between;
`;

const ContentRow = styled('div')`
  display: flex;
  align-items: center;
  font-size: ${p => p.theme.fontSizeMedium};
  gap: ${space(0.75)};
`;

const MemberEmail = styled('div')`
  display: block;
  max-width: 150px;
  font-size: ${p => p.theme.fontSizeSmall};
  font-weight: 400;
  color: ${p => p.theme.gray300};
  text-overflow: ellipsis;
  overflow: hidden;
`;

export const modalCss = css`
  width: 80%;
  max-width: 870px;
`;
