import type { components, paths } from "./jellyfin.generated"; // generated by openapi-typescript
import createClient from "openapi-fetch";
import { query } from "@solidjs/router";
import { Entry } from "../types";

type ItemImageType =
  | "Primary"
  | "Art"
  | "Backdrop"
  | "Banner"
  | "Logo"
  | "Thumb"
  | "Disc"
  | "Box"
  | "Screenshot"
  | "Menu"
  | "Chapter"
  | "BoxRear"
  | "Profile";

const getBaseUrl = () => {
  "use server";

  return process.env.JELLYFIN_BASE_URL;
};

const getClient = () => {
  "use server";

  return createClient<paths>({
    baseUrl: getBaseUrl(),
    headers: {
      Authorization: `MediaBrowser DeviceId="Streamarr", Token="${process.env.JELLYFIN_API_KEY}"`,
      "Content-Type": 'application/json; profile="CamelCase"',
    },
  });
};

export const getCurrentUser = query(async () => {
  "use server";

  const { data } = await getClient().GET("/Users/Public", {
    params: {},
  });

  return data;
}, "jellyfin.getCurrentUser");

export const listUsers = query(async () => {
  "use server";

  const { data, error } = await getClient().GET("/Users", {
    params: {},
  });

  return data ?? [];
}, "jellyfin.listUsers");

export const listItemIds = query(
  async (): Promise<Record<string, { jellyfin: string }>> => {
    "use server";

    const { data, error } = await getClient().GET("/Items", {
      params: {
        query: {
          hasImdbId: true,
          recursive: true,
          includeItemTypes: ["Movie", "Series"],
          fields: [
            "ProviderIds",
            "Genres",
            "DateLastMediaAdded",
            "DateCreated",
            "MediaSources",
          ],
        },
      },
    });

    if (data === undefined) {
      return {};
    }

    return Object.fromEntries(
      data.Items?.map((item) => {
        const type = {
          Movie: 'movie',
          Series: 'tv',
        }[item.Type as string] ?? 'unknown';

        return [
          `${type}-${item.ProviderIds!["Tmdb"]!}`,
          { jellyfin: item.Id! },
        ];
      }) ?? []
    );
  },
  "jellyfin.listItemIds",
);

export const listItems = query(
  async (userId: string): Promise<Entry[] | undefined> => {
    "use server";

    const { data, error } = await getClient().GET("/Items", {
      params: {
        query: {
          userId,
          hasTmdbInfo: true,
          recursive: true,
          includeItemTypes: ["Movie", "Series"],
          fields: [
            "ProviderIds",
            "Genres",
            "DateLastMediaAdded",
            "DateCreated",
            "MediaSources",
          ],
        },
      },
    });

    if (data === undefined) {
      return undefined;
    }

    return (
      data.Items?.map((item) => mapToEntry(item)) ?? []
    );
  },
  "jellyfin.listItems",
);

export const getRandomItem = query(
  async (userId: string): Promise<Entry | undefined> => {
    "use server";

    return getRandomItems(userId, 1).then((items) => items?.at(0));
  },
  "jellyfin.listRandomItem",
);

export const getRandomItems = query(
  async (userId: string, limit: number = 20): Promise<Entry[]> => {
    "use server";

    const { data, error } = await getClient().GET("/Items", {
      params: {
        query: {
          userId,
          hasTmdbInfo: true,
          recursive: true,
          limit,
          sortBy: ["Random"],
          includeItemTypes: ["Movie", "Series"],
          imageTypes: ["Primary", "Backdrop", "Thumb"],
          fields: [
            "ProviderIds",
            "Genres",
            "DateLastMediaAdded",
            "DateCreated",
            "MediaSources",
          ],
        },
      },
    });

    return (
      data?.Items?.map((item) => mapToEntry(item)) ?? []
    );
  },
  "jellyfin.listRandomItems",
);

export const getItem = query(
  async (userId: string, itemId: string): Promise<Entry | undefined> => {
    "use server";

    const { data, error } = await getClient().GET("/Items/{itemId}", {
      params: {
        path: {
          itemId,
        },
        query: {
          userId,
          hasTmdbInfo: true,
          recursive: true,
          includeItemTypes: ["Movie", "Series"],
          fields: [
            "ProviderIds",
            "Genres",
            "DateLastMediaAdded",
            "DateCreated",
            "MediaSources",
          ],
        },
      },
    });

    if (data === undefined) {
      return undefined;
    }

    return mapToEntry(data);
  },
  "jellyfin.getItem",
);


export const getItemStream = query(
  async (itemId: string, range: string): Promise<Response> => {
    "use server";

    // I don't really know what is the big difference between mp4 and mkv.
    // But mkv is able to use ranges an can report the video's length, whereas mp4 doesn't
    const { response } = await getClient().GET("/Videos/{itemId}/stream", {
      params: {
        path: {
          itemId,
        },
        query: {
          static: true,
          // container: 'mkv',
        },
      },
      parseAs: 'stream',
      headers: {
        Range: range
      }
    });

    return response;
  },
  "jellyfin.getItemStream",
);

export const getItemImage = query(
  async (
    itemId: string,
    imageType: ItemImageType,
  ): Promise<any | undefined> => {
    "use server";

    const { data, error } = await getClient().GET(
      "/Items/{itemId}/Images/{imageType}",
      {
        parseAs: "blob",
        params: {
          path: {
            itemId,
            imageType,
          },
          query: {},
        },
      },
    );

    return data;
  },
  "jellyfin.getItemImage",
);

export const getItemPlaybackInfo = query(
  async (userId: string, itemId: string): Promise<any | undefined> => {
    "use server";

    const { data, error, response } = await getClient().GET(
      "/Items/{itemId}/PlaybackInfo",
      {
        parseAs: "text",

        params: {
          path: {
            itemId,
          },
          query: {
            userId,
          },
        },
      },
    );

    return undefined;
  },
  "jellyfin.getItemPlaybackInfo",
);

export const queryItems = query(async () => {
  "use server";

  const { data, error } = await getClient().GET("/Items", {
    params: {
      query: {
        mediaTypes: ["Video"],
        isUnaired: true,
        limit: 10,
        // fields: ["ProviderIds", "Genres"],
        includeItemTypes: ["Series", "Movie"],
        recursive: true,
      },
    },
  });
}, "jellyfin.queryItems");

export const getContinueWatching = query(
  async (userId: string): Promise<Entry[]> => {
    "use server";

    const { data, error } = await getClient().GET("/UserItems/Resume", {
      params: {
        query: {
          userId,
          mediaTypes: ["Video"],
          // fields: ["ProviderIds", "Genres"],
          // includeItemTypes: ["Series", "Movie"]
        },
      },
    });

    if (Array.isArray(data?.Items) !== true) {
      return [];
    }

    const uniqueIds = new Set<string>(
      data.Items.map((item) =>
        item.Type === "Episode" ? item.SeriesId! : item.Id!,
      ),
    );
    const results = await Promise.allSettled(
      uniqueIds
        .values()
        .map((id) => getItem(userId, id))
        .toArray(),
    );

    assertNoErrors(results);

    return results
      .filter(
        (result): result is PromiseFulfilledResult<Entry> =>
          result.value !== undefined,
      )
      .map(({ value }) => value);
  },
  "jellyfin.continueWatching",
);

function assertNoErrors<T>(
  results: PromiseSettledResult<T>[],
): asserts results is PromiseFulfilledResult<T>[] {
  if (results.some(({ status }) => status !== "fulfilled")) {
    throw new Error("one or more promices failed", {
      cause: results
        .filter((r): r is PromiseRejectedResult => r.status === "rejected")
        .map((r) => r.reason),
    });
  }
}

const mapToEntry = (item: components['schemas']['BaseItemDto']): Entry => {
  const type = {
    Movie: 'movie',
    Series: 'tv',
  }[item.Type as string] as any;

  return {
      type,
      id: item.ProviderIds!["Tmdb"]!,
      title: item.Name!,
      overview: item.Overview!,
      thumbnail: new URL(`/Items/${item.Id}/Images/Primary`, getBaseUrl()), //await getItemImage(data.Id!, 'Primary'),
      image: new URL(`/Items/${item.Id}/Images/Backdrop`, getBaseUrl()),
      providers: {
        jellyfin: item.Id
      }
    };
};