import React, { ComponentProps } from 'react';
import { StyleSheet, View } from 'react-native';

import type { ReactTestInstance } from 'react-test-renderer';

import { render, waitFor } from '@testing-library/react-native';
import { v4 as uuidv4 } from 'uuid';

import { AudioPlayerProvider } from '../../../contexts/audioPlayerContext/AudioPlayerContext';
import type { MessageContextValue } from '../../../contexts/messageContext/MessageContext';
import { MessageProvider } from '../../../contexts/messageContext/MessageContext';
import type { MessagesContextValue } from '../../../contexts/messagesContext/MessagesContext';
import { MessagesProvider } from '../../../contexts/messagesContext/MessagesContext';
import { mergeThemes, ThemeProvider } from '../../../contexts/themeContext/ThemeContext';
import {
  generateAudioAttachment,
  generateFileAttachment,
  generateImageAttachment,
  generateVideoAttachment,
} from '../../../mock-builders/generator/attachment';
import { generateMessage } from '../../../mock-builders/generator/message';
import { FileTypes } from '../../../types/types';

import { ImageLoadingFailedIndicator } from '../../Attachment/ImageLoadingFailedIndicator';
import { ImageLoadingIndicator } from '../../Attachment/ImageLoadingIndicator';
import { Attachment } from '../Attachment';
import { FilePreview as FilePreviewDefault } from '../FilePreview';

jest.mock('../../../native.ts', () => {
  const { View } = require('react-native');

  return {
    NativeHandlers: {
      SDK: 'stream-chat-react-native',
      Sound: {
        initializeSound: jest.fn(() => null),
        Player: View,
      },
    },
    isVideoPlayerAvailable: jest.fn(() => false),
    isSoundPackageAvailable: jest.fn(() => false),
  };
});

jest.mock('../../../hooks/usePendingAttachmentUpload', () => ({
  usePendingAttachmentUpload: jest.fn(() => ({
    isUploading: false,
    uploadProgress: undefined,
  })),
}));

const getAttachmentComponent = (
  props: ComponentProps<typeof Attachment>,
  messageContextValue: Partial<MessageContextValue> = {},
) => {
  const message = messageContextValue.message ?? generateMessage();
  return (
    <ThemeProvider>
      <AudioPlayerProvider value={{ allowConcurrentAudioPlayback: false }}>
        <MessagesProvider
          value={
            {
              FilePreview: FilePreviewDefault,
              ImageLoadingFailedIndicator,
              ImageLoadingIndicator,
              message,
            } as unknown as MessagesContextValue
          }
        >
          <MessageProvider
            value={{ message, ...messageContextValue } as unknown as MessageContextValue}
          >
            <Attachment {...props} />
          </MessageProvider>
        </MessagesProvider>
      </AudioPlayerProvider>
    </ThemeProvider>
  );
};

const getWaveformBarCount = (root: ReactTestInstance) =>
  root.findAllByType(View).filter((node: ReactTestInstance) => {
    const flattenedStyle = StyleSheet.flatten(node.props.style);
    return flattenedStyle?.width === 2 && typeof flattenedStyle?.height === 'number';
  }).length;

describe('Attachment', () => {
  const lightTheme = mergeThemes({ scheme: 'light' });

  it('should render File component for "audio" type attachment', async () => {
    const attachment = generateAudioAttachment();
    const { getByTestId } = render(getAttachmentComponent({ attachment }));

    await waitFor(() => {
      expect(getByTestId('file-attachment')).toBeTruthy();
    });
  });

  it('should render File component for "video" type attachment', async () => {
    const attachment = generateVideoAttachment();
    const { getByTestId } = render(getAttachmentComponent({ attachment }));

    await waitFor(() => {
      expect(getByTestId('file-attachment')).toBeTruthy();
    });
  });

  it('should render File component for "file" type attachment', async () => {
    const attachment = generateFileAttachment();
    const { getByTestId } = render(getAttachmentComponent({ attachment }));

    await waitFor(() => {
      expect(getByTestId('file-attachment')).toBeTruthy();
    });
  });

  it('should render waveform for playable audio attachments without an active upload', async () => {
    const { isSoundPackageAvailable } = require('../../../native');
    isSoundPackageAvailable.mockReturnValue(true);
    const attachment = generateAudioAttachment({
      duration: 10,
      waveform_data: [0.2, 0.6, 0.4],
    });
    const { getByLabelText, root } = render(getAttachmentComponent({ attachment }));

    await waitFor(() => {
      expect(getByLabelText('audio-attachment-preview')).toBeTruthy();
      expect(getWaveformBarCount(root)).toBeGreaterThan(0);
    });
    isSoundPackageAvailable.mockReturnValue(false);
  });

  it('uses a transparent audio player background for quoted replies without captions', async () => {
    const { isSoundPackageAvailable } = require('../../../native');
    isSoundPackageAvailable.mockReturnValue(true);
    const attachment = generateAudioAttachment({
      duration: 10,
      waveform_data: [0.2, 0.6, 0.4],
    });
    const quotedMessage = generateMessage();
    const message = generateMessage({
      attachments: [attachment],
      quoted_message: quotedMessage,
      quoted_message_id: quotedMessage.id,
      text: '',
    });

    const { getByLabelText } = render(
      getAttachmentComponent(
        { attachment },
        { isMyMessage: false, message, messageHasOnlySingleAttachment: false },
      ),
    );

    await waitFor(() => {
      const style = StyleSheet.flatten(getByLabelText('audio-attachment-preview').props.style);
      expect(style.backgroundColor).toBe('transparent');
    });
    isSoundPackageAvailable.mockReturnValue(false);
  });

  it('keeps the audio player background for quoted replies with captions', async () => {
    const { isSoundPackageAvailable } = require('../../../native');
    isSoundPackageAvailable.mockReturnValue(true);
    const attachment = generateAudioAttachment({
      duration: 10,
      type: FileTypes.VoiceRecording,
      waveform_data: [0.2, 0.6, 0.4],
    });
    const quotedMessage = generateMessage();
    const message = generateMessage({
      attachments: [attachment],
      quoted_message: quotedMessage,
      quoted_message_id: quotedMessage.id,
      text: 'caption',
    });

    const { getByLabelText } = render(
      getAttachmentComponent(
        { attachment },
        { isMyMessage: false, message, messageHasOnlySingleAttachment: false },
      ),
    );

    await waitFor(() => {
      const style = StyleSheet.flatten(getByLabelText('audio-attachment-preview').props.style);
      expect(style.backgroundColor).toBe(lightTheme.semantics.chatBgAttachmentIncoming);
    });
    isSoundPackageAvailable.mockReturnValue(false);
  });

  it('keeps the audio player background for quoted replies with multiple attachments and no captions', async () => {
    const { isSoundPackageAvailable } = require('../../../native');
    isSoundPackageAvailable.mockReturnValue(true);
    const attachment = generateAudioAttachment({
      duration: 10,
      waveform_data: [0.2, 0.6, 0.4],
    });
    const quotedMessage = generateMessage();
    const message = generateMessage({
      attachments: [attachment, generateAudioAttachment()],
      quoted_message: quotedMessage,
      quoted_message_id: quotedMessage.id,
      text: '',
    });

    const { getByLabelText } = render(
      getAttachmentComponent(
        { attachment },
        { isMyMessage: false, message, messageHasOnlySingleAttachment: false },
      ),
    );

    await waitFor(() => {
      const style = StyleSheet.flatten(getByLabelText('audio-attachment-preview').props.style);
      expect(style.backgroundColor).toBe(lightTheme.semantics.chatBgAttachmentIncoming);
    });
    isSoundPackageAvailable.mockReturnValue(false);
  });

  it('should render UrlPreview component if attachment has title_link or og_scrape_url', async () => {
    const attachment = generateImageAttachment({
      og_scrape_url: uuidv4(),
      title_link: uuidv4(),
    });
    const { getByTestId } = render(getAttachmentComponent({ attachment }));

    await waitFor(() => {
      expect(getByTestId('card-attachment')).toBeTruthy();
    });
  });

  it('should render Gallery component if image does not have title_link or og_scrape_url', async () => {
    const attachment = generateImageAttachment();
    const { getByTestId } = render(getAttachmentComponent({ attachment }));

    await waitFor(() => {
      expect(getByTestId('gallery-container')).toBeTruthy();
    });
  });
});
