import { groupBy, map, pipe, sum } from "ramda";
import { StrictOmit } from "ts-essentials";
import { UploadStatisticsMimeRecord } from "@upload-io/upload-shared-iso/statistics/storage/dtos/UploadStatisticsMimeRecord";
import { StorageStatisticsSegmentDto } from "@upload-io/upload-shared-iso/statistics/storage/dtos/StorageStatisticsSegmentDto";
import { UploadStatisticsDto } from "@upload-io/upload-shared-iso/statistics/storage/dtos/UploadStatisticsDto";
import { UploadStatisticsMimeSegment } from "@upload-io/upload-shared-iso/statistics/storage/dtos/UploadStatisticsMimeSegment";
import { FileInfo } from "@upload-io/upload-shared-iso/statistics/storage/FileInfo";
import { EpochMillis, SizeInBytes } from "@upload-io/upload-shared-iso/common/model/Primitives";
import { NonEmpty, NonEmptyArray } from "@upload-io/upload-shared-iso/common/model/NonEmptyArray";

type Predicate<T> = (item: T) => boolean;
type MimeFilters = StrictOmit<UploadStatisticsMimeRecord<Predicate<FileInfo>>, "other">;
type EventsByMime = UploadStatisticsMimeRecord<FileInfo[]>;
type StatisticsByMime = UploadStatisticsMimeRecord<StorageStatisticsSegmentDto>;
type EventsFunc<T> = (events: FileInfo[]) => T;

const mimeFilters: MimeFilters = {
  audio: x => x.mime.startsWith("audio/"),
  image: x => x.mime.startsWith("image/"),
  video: x => x.mime.startsWith("video/"),
  document: x =>
    [
      "application/x-abiword",
      "application/msword",
      "application/vnd.ms-",
      "application/vnd.openxmlformats",
      "application/vnd.oasis.opendocument",
      "application/pdf"
    ].some(prefix => x.mime.startsWith(prefix))
};

const mimeFiltersEntries: Array<[UploadStatisticsMimeSegment, Predicate<FileInfo>]> = Object.entries(
  mimeFilters
).map(([k, v]) => [k as UploadStatisticsMimeSegment, v]);

export class UploadStatisticsCalculator {
  static calculateStatistics(newEvents: NonEmptyArray<FileInfo>, valueMultiplier: 1 | -1): UploadStatisticsDto {
    const calculateSegment = (segment: NonEmptyArray<FileInfo>): StorageStatisticsSegmentDto => ({
      firstFileCreated: EpochMillis(Math.min(...segment.map(x => x.created))),
      lastFileCreated: EpochMillis(Math.max(...segment.map(x => x.created))),
      totalFileCount: valueMultiplier * segment.length,
      totalFileSize: SizeInBytes(valueMultiplier * sum(segment.map(x => x.size))) // Can go negative (in a shard)
    });

    const groupByMime: EventsFunc<EventsByMime> = groupBy(event => {
      const match = mimeFiltersEntries.find(([_, filter]) => filter(event));
      if (match !== undefined) {
        return match[0];
      }
      return "other";
    });

    const calculateStatisticsForMime = map<EventsByMime, StatisticsByMime>(
      (events: FileInfo[]) => calculateSegment(NonEmpty(events)) // Input is from a groupBy, so we know is non-empty.
    );

    const newStatisticsMime: Partial<StatisticsByMime> = pipe(groupByMime, calculateStatisticsForMime)(newEvents);

    return {
      all: calculateSegment(newEvents),
      mime: {
        ...UploadStatisticsDto.empty.mime,
        ...newStatisticsMime
      }
    };
  }
}
