import { auth, signOut } from "@/auth";
import { db } from "@/db/client";
import { adminOverrides, externalIds, mediaItems, users, watchingNowItems } from "@/db/schema";
import { getCatalogItems, type CatalogKind } from "@/lib/catalog";
import { and, eq, gt, inArray, isNull, or } from "drizzle-orm";
import Image from "next/image";
import Link from "next/link";
import { redirect } from "next/navigation";
import { CatalogBoard } from "./catalog-board";
import { CatalogControls } from "./catalog-controls";

type PageProps = {
  searchParams?: Promise<Record<string, string | string[] | undefined>>;
};

type WatchNowItem = {
  id: string;
  title: string;
  mediaType: "tv_series" | "movie";
  year: number | null;
  posterPath: string | null;
  slotNumber: number | null;
};

const tabs: Array<{ key: CatalogKind; label: string }> = [
  { key: "television", label: "Television" },
  { key: "movies", label: "Movies" },
  { key: "music", label: "Music" },
];

function singleParam(value: string | string[] | undefined) {
  return Array.isArray(value) ? value[0] : value;
}

function activeKind(value: string | string[] | undefined): CatalogKind {
  const tab = singleParam(value);

  if (tab === "movies" || tab === "music") {
    return tab;
  }

  return "television";
}

function mediaTypeForKind(kind: CatalogKind) {
  return kind === "movies" ? "movie" : "tv_series";
}

function hrefFor(next: Record<string, string | null>) {
  const params = new URLSearchParams();

  for (const [key, value] of Object.entries(next)) {
    if (value) {
      params.set(key, value);
    }
  }

  const query = params.toString();
  return query ? "/?" + query : "/";
}

async function getCollectionIds(kind: CatalogKind, ids: string[]) {
  if (kind === "music" || ids.length === 0) {
    return new Set<string>();
  }

  const rows = await db
    .select({ externalId: externalIds.externalId })
    .from(externalIds)
    .innerJoin(mediaItems, eq(mediaItems.id, externalIds.mediaItemId))
    .where(
      and(
        eq(externalIds.source, "tmdb"),
        eq(mediaItems.mediaType, mediaTypeForKind(kind)),
        inArray(externalIds.externalId, ids),
      ),
    );

  return new Set(rows.map((row) => row.externalId));
}

async function getSlotCount(userId: string, kind: CatalogKind) {
  if (kind === "music") {
    return 0;
  }

  const [row] = await db
    .select({ tv: users.watchingNowTvSlots, movies: users.watchingNowMovieSlots })
    .from(users)
    .where(eq(users.id, userId))
    .limit(1);

  return kind === "movies" ? row?.movies ?? 10 : row?.tv ?? 5;
}

async function getWatchNow(userId: string, kind: CatalogKind): Promise<WatchNowItem[]> {
  if (kind === "music") {
    return [];
  }

  const rows = await db
    .select({
      id: watchingNowItems.id,
      title: mediaItems.title,
      mediaType: mediaItems.mediaType,
      year: mediaItems.year,
      posterPath: mediaItems.posterPath,
      slotNumber: watchingNowItems.slotNumber,
    })
    .from(watchingNowItems)
    .innerJoin(mediaItems, eq(mediaItems.id, watchingNowItems.mediaItemId))
    .where(
      and(
        eq(watchingNowItems.userId, userId),
        isNull(watchingNowItems.removedAt),
        eq(mediaItems.mediaType, mediaTypeForKind(kind)),
      ),
    );

  return rows;
}

// Purged titles, for the admin buttons. An expired override is not in force, so
// it must not show as purged -- the classifier applies the same test.
async function getPurgedExternalIds(kind: CatalogKind) {
  if (kind === "music") {
    return new Set<string>();
  }

  const rows = await db
    .select({ externalId: externalIds.externalId })
    .from(adminOverrides)
    .innerJoin(mediaItems, eq(mediaItems.id, adminOverrides.mediaItemId))
    .innerJoin(externalIds, eq(externalIds.mediaItemId, mediaItems.id))
    .where(
      and(
        eq(adminOverrides.overrideType, "purge"),
        eq(mediaItems.mediaType, mediaTypeForKind(kind)),
        eq(externalIds.source, "tmdb"),
        or(isNull(adminOverrides.expiresAt), gt(adminOverrides.expiresAt, new Date())),
      ),
    );

  return new Set(rows.map((row) => row.externalId));
}

async function getWatchNowExternalIds(userId: string, kind: CatalogKind) {
  if (kind === "music") {
    return new Set<string>();
  }

  const rows = await db
    .select({ externalId: externalIds.externalId })
    .from(watchingNowItems)
    .innerJoin(mediaItems, eq(mediaItems.id, watchingNowItems.mediaItemId))
    .innerJoin(externalIds, eq(externalIds.mediaItemId, mediaItems.id))
    .where(
      and(
        eq(watchingNowItems.userId, userId),
        isNull(watchingNowItems.removedAt),
        eq(mediaItems.mediaType, mediaTypeForKind(kind)),
        eq(externalIds.source, "tmdb"),
      ),
    );

  return new Set(rows.map((row) => row.externalId));
}

export default async function Home({ searchParams }: PageProps) {
  const session = await auth();
  if (!session) redirect("/login");

  const resolvedSearchParams = (await searchParams) ?? {};
  const kind = activeKind(resolvedSearchParams.tab);
  const query = singleParam(resolvedSearchParams.q)?.trim() ?? "";
  const collectionOnly = singleParam(resolvedSearchParams.collection) === "1";
  // Set by the links on /profile, which point at a title rather than a tab.
  const openExternalId = singleParam(resolvedSearchParams.open)?.trim() || null;
  // The same flag the admin layout and the server actions check, rather than
  // re-deriving it from the group list: a button that appears under one rule and
  // an action that refuses under another is the worst version of this.
  const isAdmin = Boolean(session.user.isAdmin);

  const [catalogItems, watchNowItems, watchNowExternalIds, purgedExternalIds, slotCount] = await Promise.all([
    getCatalogItems(kind, query),
    getWatchNow(session.user.id, kind),
    getWatchNowExternalIds(session.user.id, kind),
    isAdmin ? getPurgedExternalIds(kind) : Promise.resolve(new Set<string>()),
    getSlotCount(session.user.id, kind),
  ]);

  const collectionIds = await getCollectionIds(kind, catalogItems.map((item) => item.id));
  const visibleItems = collectionOnly
    ? catalogItems.filter((item) => collectionIds.has(item.id))
    : catalogItems;

  return (
    <main className="ampelos-app pb-44 text-ampelos-parchment">
      <header className="ampelos-topbar">
        <div className="mx-auto flex max-w-7xl flex-col gap-4 px-4 py-5 md:flex-row md:items-center md:justify-between md:px-6">
          <div className="flex items-center gap-4">
            <Image
              src="/ampelos.png"
              alt="Ampelos"
              width={64}
              height={64}
              priority
              className="h-14 w-14 shrink-0 rounded-full border border-ampelos-gold/35 bg-ampelos-ink/45 object-cover p-0.5 shadow-[0_8px_24px_rgba(0,0,0,0.45)]"
            />
            <div>
              <p className="text-xs font-semibold uppercase tracking-[0.32em] text-ampelos-gold">Ampelos</p>
              <h1 className="font-serif text-3xl font-semibold tracking-normal text-ampelos-parchment">Tend the vine. Set Dionysus&apos;s table.</h1>
            </div>
          </div>
          <div className="flex flex-wrap items-center gap-2 text-sm">
            <Link href="/profile" className="ampelos-ghost-button px-3 py-2 font-medium">
              Profile
            </Link>
            {isAdmin && (
              <Link href="/admin" className="ampelos-ghost-button px-3 py-2 font-medium">
                Admin
              </Link>
            )}
            <form action={async () => { "use server"; await signOut({ redirectTo: "/login" }); }}>
              <button type="submit" className="ampelos-ghost-button px-3 py-2 font-medium">
                Sign out
              </button>
            </form>
          </div>
        </div>
      </header>

      <section className="mx-auto max-w-7xl px-4 py-6 md:px-6">
        <nav className="flex gap-2 overflow-x-auto" aria-label="Media type">
          {tabs.map((tab) => {
            const active = tab.key === kind;
            return (
              <Link
                key={tab.key}
                href={hrefFor({ tab: tab.key, q: query || null, collection: collectionOnly ? "1" : null })}
                className={
                  active
                    ? "ampelos-stone-button px-4 py-2 text-sm"
                    : "ampelos-ghost-button px-4 py-2 text-sm font-medium"
                }
              >
                {tab.label}
              </Link>
            );
          })}
        </nav>

        <CatalogControls key={kind} kind={kind} query={query} collectionOnly={collectionOnly} />

        <div className="mt-6 flex items-end justify-between gap-4">
          <div>
            <h2 className="font-serif text-2xl font-semibold text-ampelos-parchment">
              {query ? "Results for “" + query + "”" : kind === "movies" ? "Popular movies" : kind === "television" ? "Popular television" : "Music"}
            </h2>
            <p className="mt-1 text-sm text-ampelos-muted">
              {collectionOnly
                ? "Showing titles Ampelos already recognizes as part of the local collection."
                : "Popular picks are sourced from TMDB while search is open-ended."}
            </p>
          </div>
          {query && (
            <Link href={hrefFor({ tab: kind, q: null, collection: collectionOnly ? "1" : null })} className="text-sm font-medium text-ampelos-gold hover:text-ampelos-parchment">
              Clear search
            </Link>
          )}
        </div>

        <CatalogBoard
          kind={kind}
          visibleItems={visibleItems}
          collectionIds={[...collectionIds]}
          watchNowExternalIds={[...watchNowExternalIds]}
          purgedExternalIds={[...purgedExternalIds]}
          watchNowItems={watchNowItems}
          slotCount={slotCount}
          isAdmin={isAdmin}
          openExternalId={openExternalId}
        />
      </section>
    </main>
  );
}
