import { useState } from 'react';
import { Meta, StoryObj } from '@storybook/react-webpack5';
import { expect, fn, userEvent, waitFor, within } from 'storybook/test';
import { Bank } from '@transferwise/icons';
import Button from '../../button';
import { withVariantConfig } from '../../../.storybook/helpers';
import { CriticalBanner } from './CriticalBanner';

const meta: Meta<typeof CriticalBanner> = {
  component: CriticalBanner,
  title: 'Prompts/CriticalBanner/Tests',
  tags: ['!autodocs', '!manifest'],
};

export default meta;
type Story = StoryObj<typeof CriticalBanner>;

const wait = async (ms: number) =>
  new Promise<void>((resolve) => {
    setTimeout(resolve, ms);
  });

const ANIMATION_DURATION = 200; // 150ms animation + 50ms buffer
const ENTRY_ANIMATION_DURATION = 950; // 500ms delay + 400ms animation + 50ms buffer

const longDescription =
  'We have detected unusual activity on your account that does not match your typical usage patterns. To protect your funds and personal information, we have temporarily restricted access. Please verify your identity to restore full access to your account.';

const BACKGROUND_SCROLL_CONTENT = [
  'Manage your card settings, limits, and delivery details from this page.',
  'Your recent payments, subscriptions, and cash withdrawals will appear here.',
  'Freeze your card any time if it is lost, stolen, or somewhere unexpected.',
  'Set spending limits for online, in-store, and contactless payments.',
  'Add your card to a digital wallet for quicker checkout in supported stores.',
  'Choose the currency balance you want to spend from before you travel.',
  'Review fees before using an ATM or paying in a currency you do not hold.',
  'Order a replacement card if yours is damaged or about to expire.',
  'Keep your delivery address up to date so new cards arrive in the right place.',
  'Contact support if a card payment looks unfamiliar or incorrect.',
];

function AllVariants({ gap = '1rem' }: { gap?: string }) {
  const [states, setStates] = useState({
    negativeExpanded: true,
    negativeCollapsed: false,
    warningExpanded: true,
    warningCollapsed: false,
    neutralExpanded: true,
    successExpanded: true,
    noTitle: false,
    withMedia: true,
    withBothActions: true,
  });

  const toggle = (key: keyof typeof states) =>
    setStates((prev) => ({ ...prev, [key]: !prev[key] }));

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap, width: '100%' }}>
      <CriticalBanner
        sentiment="negative"
        title="Negative — expanded"
        description="Your account has been temporarily blocked due to unusual activity."
        action={{ label: 'Verify now', onClick: fn() }}
        actionSecondary={{ label: 'Learn more', onClick: fn() }}
        expanded={states.negativeExpanded}
        onToggle={() => toggle('negativeExpanded')}
      />
      <CriticalBanner
        sentiment="negative"
        title="Negative — collapsed"
        description="Your account has been temporarily blocked."
        action={{ label: 'Verify now', onClick: fn() }}
        expanded={states.negativeCollapsed}
        onToggle={() => toggle('negativeCollapsed')}
      />
      <CriticalBanner
        sentiment="warning"
        title="Warning — expanded"
        description="Your account is at risk of being blocked within 7 days."
        action={{ label: 'Take action', onClick: fn() }}
        expanded={states.warningExpanded}
        onToggle={() => toggle('warningExpanded')}
      />
      <CriticalBanner
        sentiment="warning"
        title="Warning — collapsed"
        description="Your account is at risk."
        action={{ label: 'Take action', onClick: fn() }}
        expanded={states.warningCollapsed}
        onToggle={() => toggle('warningCollapsed')}
      />
      <CriticalBanner
        sentiment="neutral"
        title="Neutral — expanded"
        description="Your account needs attention."
        action={{ label: 'Review', onClick: fn() }}
        expanded={states.neutralExpanded}
        onToggle={() => toggle('neutralExpanded')}
      />
      <CriticalBanner
        sentiment="success"
        title="Success — expanded"
        description="Your account has been restored."
        action={{ label: 'Continue', onClick: fn() }}
        expanded={states.successExpanded}
        onToggle={() => toggle('successExpanded')}
      />
      <CriticalBanner
        sentiment="negative"
        description={longDescription}
        action={{ label: 'Verify now', onClick: fn() }}
        expanded={states.noTitle}
        onToggle={() => toggle('noTitle')}
      />
      <CriticalBanner
        sentiment="warning"
        title="With custom media"
        description="Re-link your bank account to continue receiving transfers."
        media={{ avatar: { asset: <Bank title="Bank account" /> } }}
        action={{ label: 'Connect bank', onClick: fn() }}
        expanded={states.withMedia}
        onToggle={() => toggle('withMedia')}
      />
      <CriticalBanner
        sentiment="negative"
        title="Both actions"
        description="We've blocked your account due to unusual activity."
        action={{ label: 'Verify now', onClick: fn() }}
        actionSecondary={{ label: 'Contact support', onClick: fn() }}
        expanded={states.withBothActions}
        onToggle={() => toggle('withBothActions')}
      />
    </div>
  );
}

/**
 * All four sentiments in both expanded and collapsed states, with and without
 * title, media variants, and action combinations. Used for Chromatic visual
 * regression across default, dark, bright-green, and forest-green themes.
 */
export const Variants: Story = {
  render: () => <AllVariants />,
  ...withVariantConfig(['default', 'dark', 'bright-green', 'forest-green']),
};

/** Mobile viewport regression test with all variants. */
export const MobileVariants: Story = {
  render: () => <AllVariants gap="2rem" />,
  ...withVariantConfig(['mobile']),
};

/** Neutral background regression test with page content scrolled under the mobile overhang. */
export const BackgroundIsNotTransparent: Story = {
  render: function Render() {
    return (
      <div
        data-testid="background-scroll-container"
        style={{
          width: 350,
          height: 640,
          overflowY: 'auto',
          border: '1px solid var(--color-border-neutral)',
        }}
      >
        <div style={{ position: 'sticky', top: 0, zIndex: 1 }}>
          <CriticalBanner
            sentiment="neutral"
            title="Your account requires verification"
            description="Please verify your identity to continue using all features."
            action={{ label: 'Verify now', onClick: fn() }}
          />
        </div>
        <main style={{ padding: 'var(--size-24) var(--size-16) var(--size-48)' }}>
          <h1 style={{ margin: '0 0 var(--size-8)', fontSize: 32, lineHeight: 1.1 }}>Debit card</h1>
          <p style={{ margin: '0 0 var(--size-24)' }}>Spend in 150+ currencies</p>
          {BACKGROUND_SCROLL_CONTENT.map((paragraph) => (
            <p key={paragraph}>{paragraph}</p>
          ))}
        </main>
      </div>
    );
  },
  play: async ({ canvasElement }) => {
    const scrollContainer = canvasElement.querySelector(
      '[data-testid="background-scroll-container"]',
    );

    if (scrollContainer) {
      scrollContainer.scrollTop = 250;
      await wait(ENTRY_ANIMATION_DURATION);
    }
  },
  ...withVariantConfig(['mobile']),
};

/** Mobile entry animation visual regression test. */
export const AnimatedEntryMobile: Story = {
  render: function Render() {
    return (
      <CriticalBanner
        sentiment="negative"
        title="Your account is restricted."
        description="Review your personal details to get your account active again."
        action={{ label: 'Review details', onClick: fn() }}
      />
    );
  },
  ...withVariantConfig(['mobile'], {
    parameters: {
      chromatic: { pauseAnimationAtEnd: true },
    },
  }),
};

/**
 * Tests keyboard navigation and interaction:
 * - Tab focuses the first chevron toggle button
 * - Enter toggles expanded/collapsed state
 * - Tab moves focus to the action button (when expanded)
 * - Tab moves to the next banner's toggle
 */
export const KeyboardInteraction: Story = {
  render: function Render() {
    const [expanded1, setExpanded1] = useState(true);
    const [expanded2, setExpanded2] = useState(false);

    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: '2rem', width: '100%' }}>
        <CriticalBanner
          sentiment="negative"
          title="First banner"
          description="This banner starts expanded."
          action={{ label: 'Verify now', onClick: fn() }}
          expanded={expanded1}
          onToggle={() => setExpanded1(!expanded1)}
        />
        <CriticalBanner
          sentiment="warning"
          title="Second banner"
          description="This banner starts collapsed."
          action={{ label: 'Take action', onClick: fn() }}
          expanded={expanded2}
          onToggle={() => setExpanded2(!expanded2)}
        />
      </div>
    );
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    // Wait for the collapse button to appear (viewport change needs time to apply)
    const collapseBtn = await canvas.findByRole('button', { name: /^collapse$/i });

    // Tab to first toggle (Collapse button since first banner is expanded)
    await userEvent.tab();
    await expect(collapseBtn).toHaveFocus();

    // Space collapses the first banner
    await userEvent.keyboard(' ');
    await wait(ANIMATION_DURATION);

    // After collapsing, the button label changes to "Expand"
    await waitFor(async () => {
      const expandBtns = canvas.getAllByRole('button', { name: /^expand$/i });
      await expect(expandBtns).toHaveLength(2);
      await expect(expandBtns[0]).toHaveFocus();
    });

    await userEvent.tab();
    await waitFor(async () => {
      const expandBtns = canvas.getAllByRole('button', { name: /^expand$/i });
      await expect(expandBtns[1]).toHaveFocus();
    });

    // Space expands the second banner
    await userEvent.keyboard(' ');
    await wait(ANIMATION_DURATION);

    // Now the second banner has a Collapse button and it should have focus
    await waitFor(async () => {
      const secondCollapse = canvas.getByRole('button', { name: /^collapse$/i });
      await expect(secondCollapse).toHaveFocus();
    });

    // Tab to the second banner's action button (now visible)
    await userEvent.tab();

    await waitFor(async () => {
      const actionBtn = canvas.getByRole('button', { name: /^take action$/i });
      await expect(actionBtn).toHaveFocus();
    });
  },
  ...withVariantConfig(['mobile']),
  parameters: {
    ...withVariantConfig(['mobile']).parameters,
    viewport: {
      defaultViewport: 'mobile2',
    },
  },
};

/**
 * CriticalBanner uses `aria-live="assertive"` to immediately announce content to screen readers.
 * Toggle the banner on/off and expand/collapse to hear how announcements change.
 */
export const LiveRegionAnnouncements: Story = {
  render: function Render() {
    const [visible, setVisible] = useState(false);
    const [expanded, setExpanded] = useState(false);

    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem', width: '100%' }}>
        <div>
          <Button v2 onClick={() => setVisible(!visible)}>
            {visible ? 'Hide banner' : 'Show banner'}
          </Button>
        </div>
        {visible && (
          <CriticalBanner
            sentiment="negative"
            title="Your account requires verification"
            description="Please verify your identity to continue using all features."
            action={{ label: 'Verify now', onClick: fn() }}
            actionSecondary={{ label: 'Learn more', onClick: fn() }}
            expanded={expanded}
            onToggle={() => setExpanded(!expanded)}
          />
        )}
      </div>
    );
  },
};

/** Right-to-left layout regression test. */
export const RTL: Story = {
  render: function Render() {
    const [expanded, setExpanded] = useState(true);
    const [collapsed, setCollapsed] = useState(false);

    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem', width: '100%' }}>
        <CriticalBanner
          sentiment="negative"
          title="Your account requires verification"
          description="Please verify your identity to continue using all features."
          action={{ label: 'Verify now', onClick: fn() }}
          actionSecondary={{ label: 'Learn more', onClick: fn() }}
          expanded={expanded}
          onToggle={() => setExpanded(!expanded)}
        />
        <CriticalBanner
          sentiment="warning"
          title="Collapsed state"
          description="This banner is collapsed."
          action={{ label: 'Take action', onClick: fn() }}
          expanded={collapsed}
          onToggle={() => setCollapsed(!collapsed)}
        />
      </div>
    );
  },
  ...withVariantConfig(['rtl']),
};

/** 400% zoom regression test with all variants. */
export const Zoom400: Story = {
  render: () => <AllVariants gap="2rem" />,
  ...withVariantConfig(['400%']),
};

/**
 * Desktop force-expand test: verifies that banner is always expanded
 * when container width >= 768px, regardless of expanded prop value.
 */
export const DesktopForceExpand: Story = {
  render: function Render() {
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem', width: '100%' }}>
        <CriticalBanner
          sentiment="negative"
          title="Banner without expanded"
          description="On desktop (width >= 768px), this banner should still be fully expanded even though expanded prop is not set. The toggle button should also be hidden."
          action={{ label: 'Verify now', onClick: fn() }}
          actionSecondary={{ label: 'Learn more', onClick: fn() }}
          expanded={false}
          onToggle={fn()}
        />
        <CriticalBanner
          sentiment="warning"
          title="Banner with expanded=true"
          description="This banner has expanded=true, so it should be expanded on both mobile and desktop."
          action={{ label: 'Take action', onClick: fn() }}
          expanded
          onToggle={fn()}
        />
      </div>
    );
  },
};

/**
 * Multi-line title animation test: verifies that animations work correctly
 * even when the title wraps to multiple lines on mobile.
 */
export const MultiLineTitleAnimation: Story = {
  render: function Render() {
    const [expanded, setExpanded] = useState(true);

    return (
      <CriticalBanner
        sentiment="negative"
        title="This is a very long title that will definitely wrap to multiple lines on mobile devices and should not break the animation at all"
        description="The description should animate smoothly even with a multi-line title. This was the main issue with PR #4091 that prevented it from being merged."
        action={{ label: 'Verify now', onClick: fn() }}
        actionSecondary={{ label: 'Learn more', onClick: fn() }}
        expanded={expanded}
        onToggle={() => setExpanded(!expanded)}
      />
    );
  },
  ...withVariantConfig(['mobile']),
};

/**
 * Internal state management test: verifies that the banner can manage its own
 * expanded state when onToggle is not provided (uncontrolled mode).
 */
export const UncontrolledState: Story = {
  render: function Render() {
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: '2rem', width: '100%' }}>
        <CriticalBanner
          sentiment="negative"
          title="Uncontrolled banner (no onToggle)"
          description="This banner manages its own state internally. Click the toggle to expand/collapse. The state is maintained by the component itself."
          action={{ label: 'Verify now', onClick: fn() }}
          actionSecondary={{ label: 'Learn more', onClick: fn() }}
        />
        <CriticalBanner
          sentiment="warning"
          title="Uncontrolled banner starting collapsed"
          description="This banner also manages its own state, but starts collapsed."
          action={{ label: 'Take action', onClick: fn() }}
          expanded={false}
        />
      </div>
    );
  },
  ...withVariantConfig(['mobile']),
};

/**
 * Tests that clicking the toggle button works correctly in uncontrolled mode.
 * Verifies that the banner can expand/collapse without external state management.
 */
export const UncontrolledStateInteraction: Story = {
  render: function Render() {
    return (
      <CriticalBanner
        sentiment="negative"
        title="Click the toggle to test"
        description="This banner manages its own expanded state. The toggle button should work without any external state management."
        action={{ label: 'Verify now', onClick: fn() }}
        actionSecondary={{ label: 'Learn more', onClick: fn() }}
      />
    );
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    // Wait for the collapse button to appear
    const collapseBtn = await canvas.findByRole('button', { name: /^collapse$/i });

    // Click to collapse
    await userEvent.click(collapseBtn);
    await wait(ANIMATION_DURATION);

    // After collapsing, the button label changes to "Expand"
    const expandBtn = canvas.getByRole('button', { name: /^expand$/i });
    await expect(expandBtn).toBeInTheDocument();

    // Click to expand again
    await userEvent.click(expandBtn);
    await wait(ANIMATION_DURATION);

    // Should be back to "Collapse"
    const collapseBtnAgain = canvas.getByRole('button', { name: /^collapse$/i });
    await expect(collapseBtnAgain).toBeInTheDocument();
  },
  ...withVariantConfig(['mobile']),
  parameters: {
    ...withVariantConfig(['mobile']).parameters,
    viewport: {
      defaultViewport: 'mobile2',
    },
  },
};
