import {InsertAsset, SelectAsset} from '@modules/chats/db/interfaces';
import {
  Asset,
  BaseAsset,
  PictureAsset,
  VideoAsset,
  AudioAsset,
  MediaEntity,
  MediaType,
  DocumentAsset,
  GifAsset,
  AssetFromServer,
  BaseAssetFromServer,
  VideoAssetFromServer,
  Block,
  BlockType,
} from '@modules/chats/interfaces';
import {AUDIO_EXTENSIONS} from '@modules/chats/constants';
import {API_URL} from '@shared/keys';

import {calculateGifWidthAndHeight} from '../measuring';

export function normalizeMediaExtension(
  fileName: string | null | undefined,
  type: 'video' | 'image',
): string {
  const isVideo = type === 'video';
  const fallback = isVideo ? 'media.mp4' : 'image.jpg';
  const ext = isVideo ? '.mp4' : '.jpg';

  const name = fileName ?? fallback;

  return name.includes('.') ? name.replace(/\.[^.]+$/, ext) : name + ext;
}

export const replaceFilenameSpaces = (filename: string) => {
  return filename.replace(' ', '+');
};

export const sortAssets = (assets: Asset[]) => {
  return [...assets].sort((a, b) => a.id - b.id);
};

export const sortAssetsFromBlocks = (blocks: Block[]) => {
  return blocks.map(block => {
    if (block.type === BlockType.MessageReplied) {
      block.assets = sortAssets(block.assets || []);
    }
    return block;
  });
};

export const isPictureAsset = (
  asset: BaseAsset | BaseAssetFromServer,
): asset is PictureAsset => {
  return asset.metadata.format === 'image';
};

export const isVideoAsset = (
  asset: BaseAsset | BaseAssetFromServer,
): asset is VideoAsset => {
  return asset.metadata.format === 'video';
};

export const isAudioAsset = (
  asset: BaseAsset | BaseAssetFromServer,
): asset is AudioAsset => {
  return asset.metadata.format === 'audio';
};

export const isDocumentAsset = (
  asset: BaseAsset | BaseAssetFromServer,
): asset is DocumentAsset => {
  return asset.metadata.format === 'file';
};

export const isGifAsset = (
  asset: BaseAsset | BaseAssetFromServer,
): asset is GifAsset => {
  return asset.metadata.format === 'gif';
};

export const mapAssetFromDB = (assets: SelectAsset[]): Asset[] => {
  return sortAssets(
    assets.map(asset => {
      return {
        id: asset.id,
        mimetype: asset.mimetype,
        filename: asset.filename,
        url: asset.url ?? '',
        size: asset.size ?? 0,
        image_url: asset.image_url ?? '',
        image_bytes: asset.image_bytes ?? 0,
        image_height: asset.image_height ?? 0,
        image_width: asset.image_width ?? 0,
        audio_duration_ms: asset.audio_duration_ms ?? 0,
        video_duration_ms: asset.video_duration_ms ?? 0,
        thumbnail_url: asset.thumbnail_url ?? null,
        video_url: asset.url ?? '',
        isValid: asset.is_valid ?? undefined,
        external_reference: asset.external_reference
          ? JSON.parse(asset.external_reference)
          : undefined,
        is_available: asset.is_available ?? true,
        metadata: {
          format: asset.format as 'image',
        },
      };
    }),
  );
};

export const mapAssetToDB = (asset: Asset): InsertAsset => {
  switch (asset.metadata.format) {
    case 'image':
      return {
        filename: asset.filename,
        format: asset.metadata.format,
        id: asset.id,
        image_bytes: (asset as PictureAsset).image_bytes,
        image_width: (asset as PictureAsset).image_width,
        image_height: (asset as PictureAsset).image_height,
        image_url: (asset as PictureAsset).image_url,
        mimetype: asset.mimetype,
      };
    case 'video':
      return {
        filename: asset.filename,
        format: asset.metadata.format,
        id: asset.id,
        mimetype: asset.mimetype,
        size: (asset as VideoAsset).size,
        url: (asset as VideoAsset).video_url,
        video_duration_ms: (asset as VideoAsset).video_duration_ms,
        thumbnail_url: (asset as VideoAsset).thumbnail_url,
      };
    case 'audio':
      return {
        filename: asset.filename,
        format: asset.metadata.format,
        id: asset.id,
        mimetype: asset.mimetype,
        url: (asset as AudioAsset).url,
        audio_duration_ms: (asset as AudioAsset).audio_duration_ms,
        is_valid: (asset as AudioAsset).isValid,
      };
    case 'gif':
      return {
        filename: asset.filename,
        format: asset.metadata.format,
        id: asset.id,
        mimetype: asset.mimetype,
        image_height: (asset as GifAsset).image_height,
        image_width: (asset as GifAsset).image_width,
        is_available: (asset as GifAsset).is_available,
        external_reference: JSON.stringify(
          (asset as GifAsset).external_reference,
        ),
      };

    default:
      return {
        filename: asset.filename,
        format: asset.metadata.format,
        id: asset.id,
        mimetype: asset.mimetype,
        size: (asset as DocumentAsset).size,
        //TODO: Right now we are mapping every non image asset to a document asset, when we support video and audio we will have to change this.
        url: (asset as DocumentAsset).url,
      };
  }
};

export const mapMediaToService = (media: MediaEntity[]): Asset[] =>
  media.map(data => {
    switch (data.type) {
      case MediaType.IMAGE:
        return {
          id: data.fileAsset!.id,
          mimetype: data.contentType,
          key: data.fileAsset!.key,
          filename: data.name,
          metadata: {
            format: 'image',
          },
          image_url: data.fileAsset!.url,
          image_bytes: data.sizeInBytes,
          image_height: data.height || 0,
          image_width: data.width || 0,
        };
      case MediaType.VIDEO:
        return {
          id: data.fileAsset!.id,
          mimetype: data.contentType,
          filename: data.name,
          key: data.fileAsset!.key,
          metadata: {
            format: 'video',
          },
          video_html_height: data.height || 0,
          video_html_width: data.width || 0,
          video_url: data.fileAsset!.url,
          size: data.sizeInBytes,
          video_duration_ms: data.fileAsset!.metadata?.durationInMs ?? 0,
          thumbnail_url: data.fileAsset!.thumbnailUrl ?? '',
        };
      case MediaType.AUDIO:
        return {
          id: data.fileAsset!.id,
          mimetype: data.contentType,
          filename: data.name,
          isValid: true,
          key: data.fileAsset!.key,
          metadata: {
            format: 'audio',
          },
          url: data.fileAsset!.url,
          size: data.sizeInBytes,
          audio_duration_ms: data.fileAsset!.metadata?.durationInMs ?? 0,
        };
      case MediaType.GIF:
        return {
          id: data.fileAsset!.id,
          mimetype: data.contentType,
          filename: data.name,
          key: data.fileAsset!.key,
          metadata: {
            format: 'gif',
          },
          external_reference: data.fileAsset!.externalReference!,
          image_height: data.height || 0,
          image_width: data.width || 0,
          image_bytes: data.sizeInBytes,
          is_available: true,
        };
      default:
        return {
          id: data.fileAsset!.id,
          mimetype: data.contentType,
          filename: data.name,
          key: data.fileAsset!.key,
          metadata: {
            format: 'file',
          },
          url: data.fileAsset!.url,
          size: data.sizeInBytes,
        };
    }
  });

export const mapMediaToAsset = (media: MediaEntity, channel: string): Asset => {
  const url = `${API_URL}/marty/assets/${
    media.fileAsset!.id
  }/conversation/${channel}`;

  const filename = replaceFilenameSpaces(media.name);
  switch (media.type) {
    case MediaType.IMAGE:
      return {
        id: media.fileAsset!.id,
        mimetype: media.contentType,
        filename,
        image_url: url,
        image_bytes: media.sizeInBytes,
        image_height: media.height || 0,
        image_width: media.width || 0,
        metadata: {
          format: 'image',
        },
      };
    case MediaType.VIDEO:
      return {
        id: media.fileAsset!.id,
        mimetype: media.contentType,
        filename,
        video_html_height: media.height || 0,
        video_html_width: media.width || 0,
        video_url: url,
        size: media.sizeInBytes,
        video_duration_ms: media.fileAsset!.metadata?.durationInMs ?? 0,
        metadata: {
          format: 'video',
        },
        thumbnail_url: media.fileAsset!.thumbnailUrl ?? '',
      };
    case MediaType.AUDIO:
      return {
        id: media.fileAsset!.id,
        mimetype: media.contentType,
        filename,
        size: media.sizeInBytes,
        isValid: true,
        url,
        metadata: {
          format: 'audio',
        },
        audio_duration_ms: media.fileAsset!.metadata?.durationInMs ?? 0,
      };
    case MediaType.GIF:
      return {
        id: media.fileAsset!.id,
        mimetype: media.contentType,
        filename,
        image_height: media.height || 0,
        image_width: media.width || 0,
        image_bytes: media.sizeInBytes,
        is_available: true,
        metadata: {
          format: 'gif',
        },
        external_reference: media.fileAsset!.externalReference!,
      };
    default:
      return {
        id: media.fileAsset!.id,
        mimetype: media.contentType,
        filename,
        url,
        size: media.sizeInBytes,
        metadata: {
          format: 'file',
        },
      };
  }
};

export const mapMediaArrayToAssets = (
  media: MediaEntity[],
  channel: string,
): Asset[] => media.map(data => mapMediaToAsset(data, channel));

export const mapAssetFromService = (assets: AssetFromServer[]): Asset[] => {
  return sortAssets(
    assets.map(asset => {
      if (isVideoAsset(asset)) {
        return {
          ...asset,
          video_url: asset.video_url,
          video_duration_ms: asset.video_duration_ms,
          thumbnail_url: (asset as VideoAssetFromServer).thumb_url,
          metadata: {
            format: 'video',
          },
        } as VideoAsset;
      } else if (isAudioAsset(asset)) {
        return {
          ...asset,
          audio_duration_ms: asset.audio_duration_ms,
          filename: asset.filename.replace('.webm', '.mp4'),
          isValid: asset.metadata.alternative_extensions.some(ext =>
            AUDIO_EXTENSIONS.includes(ext.toUpperCase()),
          ),
          metadata: {
            format: 'audio',
          },
        } as AudioAsset;
      } else if (isGifAsset(asset)) {
        const {width, height} = calculateGifWidthAndHeight(
          asset.image_width / asset.image_height,
        );
        return {
          ...asset,
          image_width: width,
          image_height: height,
          is_available: true,
          metadata: {
            format: 'gif',
          },
        } as GifAsset;
      }
      return asset as Asset;
    }),
  );
};

// TODO: improve how we handle urls
export const mapAssetToUrl = (asset: Asset) => {
  return isPictureAsset(asset)
    ? asset.image_url
    : isAudioAsset(asset)
    ? asset.url
    : isDocumentAsset(asset)
    ? asset.url
    : isVideoAsset(asset)
    ? asset.video_url
    : (asset as unknown as {url: string}).url;
};

export const flattenMessageAssets = <
  T extends {messagesToAssets?: {asset: SelectAsset}[]},
>(
  message: T,
): Omit<T, 'messagesToAssets'> & {assets: SelectAsset[]} => {
  if (message.messagesToAssets) {
    return {
      ...message,
      assets: message.messagesToAssets.map(
        (mta: {asset: SelectAsset}) => mta.asset,
      ),
      messagesToAssets: undefined,
    } as Omit<T, 'messagesToAssets'> & {assets: SelectAsset[]};
  }
  return message as unknown as Omit<T, 'messagesToAssets'> & {
    assets: SelectAsset[];
  };
};

export const mapPendingAsset = (pendingAsset: MediaEntity): Asset => {
  return {
    id: parseInt(pendingAsset.id, 10),
    mimetype: pendingAsset.contentType,
    filename: pendingAsset.name,
    size: pendingAsset.sizeInBytes,
    isValid: true,
    url: pendingAsset.localUri,
    metadata: {
      format: 'audio',
    },
    audio_duration_ms: pendingAsset.metadata?.durationInMs ?? 0,
  };
};
