import { eq, and, desc, asc, sql } from 'drizzle-orm';
import { db } from 'officerdb/db';
import { musicFavorites, musicNowPlaying, musicPlaylists, musicPlaylistItems } from './schema';

export type FavoriteKind = 'track' | 'album' | 'artist';
export type GroupedFavorites = { tracks: string[]; albums: string[]; artists: string[] };

/** All of a user's favorites, grouped by kind, newest first within each group. */
export async function getMusicFavorites(userId: number): Promise<GroupedFavorites> {
  const rows = await db
    .select({ kind: musicFavorites.kind, key: musicFavorites.key })
    .from(musicFavorites)
    .where(eq(musicFavorites.userId, userId))
    .orderBy(desc(musicFavorites.createdAt));
  const grouped: GroupedFavorites = { tracks: [], albums: [], artists: [] };
  for (const r of rows) {
    if (r.kind === 'track') grouped.tracks.push(r.key);
    else if (r.kind === 'album') grouped.albums.push(r.key);
    else if (r.kind === 'artist') grouped.artists.push(r.key);
  }
  return grouped;
}

/** Add a favorite (idempotent — a repeat add is a no-op via the unique constraint). */
export async function addMusicFavorite(userId: number, kind: FavoriteKind, key: string): Promise<void> {
  await db.insert(musicFavorites).values({ userId, kind, key }).onConflictDoNothing();
}

/** Remove a favorite (no-op if it wasn't set). */
export async function removeMusicFavorite(userId: number, kind: FavoriteKind, key: string): Promise<void> {
  await db
    .delete(musicFavorites)
    .where(and(eq(musicFavorites.userId, userId), eq(musicFavorites.kind, kind), eq(musicFavorites.key, key)));
}

export type NowPlaying = {
  homePath: string;
  dir: string;
  title: string;
  artist: string;
  album: string;
  durationSec: number;
  positionSec: number;
  updatedAt: Date;
};

export type NowPlayingInput = {
  homePath: string;
  dir?: string;
  title?: string;
  artist?: string;
  album?: string;
  durationSec?: number;
  positionSec?: number;
};

/** This (user, device)'s last "currently playing" snapshot, or null if none. */
export async function getNowPlaying(userId: number, device: string): Promise<NowPlaying | null> {
  const [row] = await db
    .select({
      homePath: musicNowPlaying.homePath,
      dir: musicNowPlaying.dir,
      title: musicNowPlaying.title,
      artist: musicNowPlaying.artist,
      album: musicNowPlaying.album,
      durationSec: musicNowPlaying.durationSec,
      positionSec: musicNowPlaying.positionSec,
      updatedAt: musicNowPlaying.updatedAt,
    })
    .from(musicNowPlaying)
    .where(and(eq(musicNowPlaying.userId, userId), eq(musicNowPlaying.device, device)));
  return row ?? null;
}

/** Upsert this (user, device)'s "currently playing" snapshot (one row per user+device). */
export async function setNowPlaying(userId: number, device: string, np: NowPlayingInput): Promise<void> {
  const values = {
    userId,
    device,
    homePath: np.homePath,
    dir: np.dir ?? '',
    title: np.title ?? '',
    artist: np.artist ?? '',
    album: np.album ?? '',
    durationSec: np.durationSec ?? 0,
    positionSec: np.positionSec ?? 0,
    updatedAt: new Date(),
  };
  await db
    .insert(musicNowPlaying)
    .values(values)
    .onConflictDoUpdate({
      target: [musicNowPlaying.userId, musicNowPlaying.device],
      set: {
        homePath: values.homePath,
        dir: values.dir,
        title: values.title,
        artist: values.artist,
        album: values.album,
        durationSec: values.durationSec,
        positionSec: values.positionSec,
        updatedAt: values.updatedAt,
      },
    });
}

/** Clear this (user, device)'s "currently playing" (on close/stop). */
export async function clearNowPlaying(userId: number, device: string): Promise<void> {
  await db.delete(musicNowPlaying).where(and(eq(musicNowPlaying.userId, userId), eq(musicNowPlaying.device, device)));
}

// ── Named playlists ────────────────────────────────────────────────────────────────────────────────

export type PlaylistSummary = { id: number; name: string; count: number; createdAt: Date; updatedAt: Date };
export type Playlist = { id: number; name: string; items: string[]; createdAt: Date; updatedAt: Date };

/** All of a user's playlists with item counts, most-recently-updated first. */
export async function getPlaylists(userId: number): Promise<PlaylistSummary[]> {
  return db
    .select({
      id: musicPlaylists.id,
      name: musicPlaylists.name,
      count: sql<number>`count(${musicPlaylistItems.id})::int`,
      createdAt: musicPlaylists.createdAt,
      updatedAt: musicPlaylists.updatedAt,
    })
    .from(musicPlaylists)
    .leftJoin(musicPlaylistItems, eq(musicPlaylistItems.playlistId, musicPlaylists.id))
    .where(eq(musicPlaylists.userId, userId))
    .groupBy(musicPlaylists.id)
    .orderBy(desc(musicPlaylists.updatedAt));
}

/** One playlist with its ordered item keys, or null if it doesn't exist or isn't this user's. */
export async function getPlaylist(userId: number, id: number): Promise<Playlist | null> {
  const [header] = await db
    .select({
      id: musicPlaylists.id,
      name: musicPlaylists.name,
      createdAt: musicPlaylists.createdAt,
      updatedAt: musicPlaylists.updatedAt,
    })
    .from(musicPlaylists)
    .where(and(eq(musicPlaylists.id, id), eq(musicPlaylists.userId, userId)));
  if (!header) return null;
  const rows = await db
    .select({ key: musicPlaylistItems.key })
    .from(musicPlaylistItems)
    .where(eq(musicPlaylistItems.playlistId, id))
    .orderBy(asc(musicPlaylistItems.position));
  return { ...header, items: rows.map((r) => r.key) };
}

/** Create a named playlist. Returns the new row, or null if the name is already taken for this user. */
export async function createPlaylist(userId: number, name: string): Promise<PlaylistSummary | null> {
  const [row] = await db
    .insert(musicPlaylists)
    .values({ userId, name })
    .onConflictDoNothing({ target: [musicPlaylists.userId, musicPlaylists.name] })
    .returning({
      id: musicPlaylists.id,
      name: musicPlaylists.name,
      createdAt: musicPlaylists.createdAt,
      updatedAt: musicPlaylists.updatedAt,
    });
  return row ? { ...row, count: 0 } : null;
}

/** Rename a playlist. Returns false if it isn't this user's, or the new name collides. */
export async function renamePlaylist(userId: number, id: number, name: string): Promise<boolean> {
  const existing = await db
    .select({ id: musicPlaylists.id })
    .from(musicPlaylists)
    .where(and(eq(musicPlaylists.userId, userId), eq(musicPlaylists.name, name)));
  if (existing.some((r) => r.id !== id)) return false; // name taken by a different playlist
  const updated = await db
    .update(musicPlaylists)
    .set({ name, updatedAt: new Date() })
    .where(and(eq(musicPlaylists.id, id), eq(musicPlaylists.userId, userId)))
    .returning({ id: musicPlaylists.id });
  return updated.length > 0;
}

/** Delete a playlist (items cascade). Returns false if it wasn't this user's. */
export async function deletePlaylist(userId: number, id: number): Promise<boolean> {
  const deleted = await db
    .delete(musicPlaylists)
    .where(and(eq(musicPlaylists.id, id), eq(musicPlaylists.userId, userId)))
    .returning({ id: musicPlaylists.id });
  return deleted.length > 0;
}

/** Verify a playlist belongs to the user; returns its id or null. */
async function ownedPlaylist(userId: number, id: number): Promise<number | null> {
  const [row] = await db
    .select({ id: musicPlaylists.id })
    .from(musicPlaylists)
    .where(and(eq(musicPlaylists.id, id), eq(musicPlaylists.userId, userId)));
  return row?.id ?? null;
}

/** Append keys to the end of a playlist, preserving order. Returns the new count, or null if not the user's. */
export async function addPlaylistItems(userId: number, id: number, keys: string[]): Promise<number | null> {
  if ((await ownedPlaylist(userId, id)) === null) return null;
  return db.transaction(async (tx) => {
    const [{ next } = { next: 0 }] = await tx
      .select({ next: sql<number>`coalesce(max(${musicPlaylistItems.position}) + 1, 0)::int` })
      .from(musicPlaylistItems)
      .where(eq(musicPlaylistItems.playlistId, id));
    if (keys.length) {
      await tx.insert(musicPlaylistItems).values(keys.map((key, i) => ({ playlistId: id, key, position: next + i })));
    }
    await tx.update(musicPlaylists).set({ updatedAt: new Date() }).where(eq(musicPlaylists.id, id));
    const [{ total } = { total: 0 }] = await tx
      .select({ total: sql<number>`count(*)::int` })
      .from(musicPlaylistItems)
      .where(eq(musicPlaylistItems.playlistId, id));
    return total;
  });
}

/** Replace a playlist's entire ordered item list (covers reorder + remove). Returns the new count, or null. */
export async function setPlaylistItems(userId: number, id: number, keys: string[]): Promise<number | null> {
  if ((await ownedPlaylist(userId, id)) === null) return null;
  return db.transaction(async (tx) => {
    await tx.delete(musicPlaylistItems).where(eq(musicPlaylistItems.playlistId, id));
    if (keys.length) {
      await tx.insert(musicPlaylistItems).values(keys.map((key, i) => ({ playlistId: id, key, position: i })));
    }
    await tx.update(musicPlaylists).set({ updatedAt: new Date() }).where(eq(musicPlaylists.id, id));
    return keys.length;
  });
}
