import { eq, and, desc, sql } from "drizzle-orm";
import {
  musicaArtistas,
  musicaAlbuns,
  musicaFaixas,
  userFavoritosMusica,
  userHistoricoMusica,
  userPlaylistItemsMusica,
  type MusicaArtista,
  type MusicaAlbum,
  type MusicaFaixa,
} from "../drizzle/schema";
import { getDb } from "./db";

/**
 * Público — Catálogo (Fase 2, Etapa 3)
 *
 * Espelha getApresentadores/getApresentadorById/getProgramas/getProgramaById
 * de podcast-db.ts. Só retorna itens com ativo = 1 (mesma regra do Podcast).
 */

export async function getArtistas(page: number = 1, limit: number = 30) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const offset = (page - 1) * limit;

  const artistas = await db
    .select()
    .from(musicaArtistas)
    .where(eq(musicaArtistas.ativo, 1))
    .orderBy(desc(musicaArtistas.reproducoes), desc(musicaArtistas.id))
    .limit(limit)
    .offset(offset);

  const total = await db
    .select({ count: sql<number>`count(*)` })
    .from(musicaArtistas)
    .where(eq(musicaArtistas.ativo, 1));

  return {
    artistas,
    total: total[0]?.count || 0,
    page,
    pages: Math.ceil((total[0]?.count || 0) / limit),
  };
}

export async function getArtistaById(id: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const artista = await db
    .select()
    .from(musicaArtistas)
    .where(and(eq(musicaArtistas.id, id), eq(musicaArtistas.ativo, 1)))
    .limit(1);

  if (!artista[0]) return null;

  const albuns = await db
    .select()
    .from(musicaAlbuns)
    .where(and(eq(musicaAlbuns.artistaId, id), eq(musicaAlbuns.ativo, 1)))
    .orderBy(desc(musicaAlbuns.anoLancamento));

  const faixas = await db
    .select()
    .from(musicaFaixas)
    .where(and(eq(musicaFaixas.artistaId, id), eq(musicaFaixas.ativo, 1)))
    .orderBy(desc(musicaFaixas.reproducoes), desc(musicaFaixas.id));

  return { artista: artista[0], albuns, faixas };
}

export async function getAlbuns(page: number = 1, limit: number = 30) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const offset = (page - 1) * limit;

  const albuns = await db
    .select()
    .from(musicaAlbuns)
    .where(eq(musicaAlbuns.ativo, 1))
    .orderBy(desc(musicaAlbuns.anoLancamento), desc(musicaAlbuns.id))
    .limit(limit)
    .offset(offset);

  const total = await db
    .select({ count: sql<number>`count(*)` })
    .from(musicaAlbuns)
    .where(eq(musicaAlbuns.ativo, 1));

  return {
    albuns,
    total: total[0]?.count || 0,
    page,
    pages: Math.ceil((total[0]?.count || 0) / limit),
  };
}

export async function getFaixas(page: number = 1, limit: number = 30) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const offset = (page - 1) * limit;

  const faixas = await db
    .select()
    .from(musicaFaixas)
    .where(eq(musicaFaixas.ativo, 1))
    .orderBy(desc(musicaFaixas.reproducoes), desc(musicaFaixas.id))
    .limit(limit)
    .offset(offset);

  const total = await db
    .select({ count: sql<number>`count(*)` })
    .from(musicaFaixas)
    .where(eq(musicaFaixas.ativo, 1));

  return {
    faixas,
    total: total[0]?.count || 0,
    page,
    pages: Math.ceil((total[0]?.count || 0) / limit),
  };
}

export async function getAlbumById(id: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const album = await db
    .select()
    .from(musicaAlbuns)
    .where(and(eq(musicaAlbuns.id, id), eq(musicaAlbuns.ativo, 1)))
    .limit(1);

  if (!album[0]) return null;

  const artista = album[0].artistaId
    ? (
        await db
          .select()
          .from(musicaArtistas)
          .where(eq(musicaArtistas.id, album[0].artistaId))
          .limit(1)
      )[0] ?? null
    : null;

  const faixas = await db
    .select()
    .from(musicaFaixas)
    .where(and(eq(musicaFaixas.albumId, id), eq(musicaFaixas.ativo, 1)))
    .orderBy(sql`${musicaFaixas.numeroFaixa} is null`, musicaFaixas.numeroFaixa, musicaFaixas.ordem);

  return { album: album[0], artista, faixas };
}

/**
 * Detalhe de uma faixa — espelha getEpisodioById + getEpisodiosRelacionados.
 */
export async function getFaixaById(id: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const faixa = await db
    .select()
    .from(musicaFaixas)
    .where(and(eq(musicaFaixas.id, id), eq(musicaFaixas.ativo, 1)))
    .limit(1);

  if (!faixa[0]) return null;

  const artista = faixa[0].artistaId
    ? (
        await db.select().from(musicaArtistas).where(eq(musicaArtistas.id, faixa[0].artistaId)).limit(1)
      )[0] ?? null
    : null;

  const album = faixa[0].albumId
    ? (await db.select().from(musicaAlbuns).where(eq(musicaAlbuns.id, faixa[0].albumId)).limit(1))[0] ?? null
    : null;

  // Relacionadas: outras faixas do mesmo álbum (ou do mesmo artista, se não
  // houver álbum) — mesmo critério de getEpisodiosRelacionados.
  const where = faixa[0].albumId
    ? and(
        eq(musicaFaixas.albumId, faixa[0].albumId),
        eq(musicaFaixas.ativo, 1),
        sql`${musicaFaixas.id} != ${id}`
      )
    : faixa[0].artistaId
      ? and(
          eq(musicaFaixas.artistaId, faixa[0].artistaId),
          eq(musicaFaixas.ativo, 1),
          sql`${musicaFaixas.id} != ${id}`
        )
      : and(eq(musicaFaixas.ativo, 1), sql`${musicaFaixas.id} != ${id}`);

  const relacionadas = await db.select().from(musicaFaixas).where(where).limit(6);

  return { faixa: faixa[0], artista, album, relacionadas };
}

/**
 * Dados mínimos para Open Graph (usado por server/_core/share.ts, rota
 * /share/faixa/:id).
 *
 * Por que não reaproveitar getFaixaById: aquela função faz 4 consultas
 * MySQL EM SÉRIE (faixa → artista → álbum → relacionadas), pensada pra
 * montar a página completa da faixa. O card de compartilhamento só
 * precisa de título, capa e nome do artista — pagar o custo das 4
 * consultas (incluindo "relacionadas", que o card nem usa) foi a causa
 * raiz do timeout que fazia o WhatsApp mostrar o card genérico em vez da
 * capa real. Esta função faz o equivalente em 1 única consulta com
 * LEFT JOIN.
 */
export async function getFaixaOgData(id: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const rows = await db
    .select({
      titulo: musicaFaixas.titulo,
      capaFaixa: musicaFaixas.capa,
      capaAlbum: musicaAlbuns.capa,
      artistaNome: musicaArtistas.nome,
    })
    .from(musicaFaixas)
    .leftJoin(musicaAlbuns, eq(musicaFaixas.albumId, musicaAlbuns.id))
    .leftJoin(musicaArtistas, eq(musicaFaixas.artistaId, musicaArtistas.id))
    .where(and(eq(musicaFaixas.id, id), eq(musicaFaixas.ativo, 1)))
    .limit(1);

  const row = rows[0];
  if (!row) return null;

  return {
    titulo: row.titulo,
    capa: row.capaFaixa ?? row.capaAlbum ?? null,
    artistaNome: row.artistaNome ?? null,
  };
}

/**
 * Top faixas — usado no carrossel "Música" da Home, mesmo padrão de
 * getTopEpisodios.
 */
export async function getTopFaixas(limit: number = 20) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  return await db
    .select()
    .from(musicaFaixas)
    .where(eq(musicaFaixas.ativo, 1))
    .orderBy(desc(musicaFaixas.reproducoes), desc(musicaFaixas.id))
    .limit(limit);
}

/**
 * Incrementa reprodução da faixa e do artista associado — mesmo padrão de
 * incrementarReproducoes (Podcast).
 */
export async function incrementarReproducoesFaixa(faixaId: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  await db
    .update(musicaFaixas)
    .set({ reproducoes: sql`${musicaFaixas.reproducoes} + 1` })
    .where(eq(musicaFaixas.id, faixaId));

  const faixa = await db.select().from(musicaFaixas).where(eq(musicaFaixas.id, faixaId)).limit(1);
  if (faixa[0]?.artistaId) {
    await db
      .update(musicaArtistas)
      .set({ reproducoes: sql`${musicaArtistas.reproducoes} + 1` })
      .where(eq(musicaArtistas.id, faixa[0].artistaId));
  }
}

/**
 * Admin CRUD (Fase 2, Etapa 2) — espelha exatamente o padrão de
 * podcast-db.ts (Apresentadores/Programas/Episódios).
 */
function slugify(value: string): string {
  return value
    .toLowerCase()
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "")
    .slice(0, 250);
}

/**
 * Artistas
 */
export type ArtistaInput = {
  nome: string;
  slug?: string | null;
  bio?: string | null;
  foto?: string | null;
  ativo?: number;
};

export async function criarArtista(input: ArtistaInput) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const slug = input.slug?.trim() || slugify(input.nome);

  const result = await db.insert(musicaArtistas).values({
    nome: input.nome,
    slug,
    bio: input.bio ?? null,
    foto: input.foto ?? null,
    ativo: input.ativo ?? 1,
  });

  return { id: Number((result as any).insertId ?? 0) };
}

export async function atualizarArtista(id: number, input: Partial<ArtistaInput>) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const values: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(input)) {
    if (value !== undefined) values[key] = value;
  }
  if (Object.keys(values).length === 0) return;

  await db.update(musicaArtistas).set(values).where(eq(musicaArtistas.id, id));
}

export async function deletarArtista(id: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  await db.delete(musicaArtistas).where(eq(musicaArtistas.id, id));
}

/**
 * Estatísticas — usado pelo card "Música" do AdminDashboard, mesmo padrão
 * do stats do Podcast.
 */
export async function getEstatisticas() {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const [totalFaixas] = await db.select({ count: sql<number>`count(*)` }).from(musicaFaixas);
  const [totalArtistas] = await db.select({ count: sql<number>`count(*)` }).from(musicaArtistas);
  const [totalAlbuns] = await db.select({ count: sql<number>`count(*)` }).from(musicaAlbuns);
  const [reproducoes] = await db
    .select({ sum: sql<number>`coalesce(sum(${musicaFaixas.reproducoes}), 0)` })
    .from(musicaFaixas);

  return {
    totalFaixas: totalFaixas?.count || 0,
    totalArtistas: totalArtistas?.count || 0,
    totalAlbuns: totalAlbuns?.count || 0,
    totalReproducoes: reproducoes?.sum || 0,
  };
}

/**
 * Favoritos e Histórico (Biblioteca) — espelha exatamente o padrão de
 * podcast-db.ts (adicionarFavorito/removerFavorito/isFavorito/
 * getFavoritosComEpisodios/adicionarAoHistorico/getHistoricoComEpisodios),
 * usando as tabelas paralelas já criadas na Etapa 1 (ADR-003):
 * user_favoritos_musica / user_historico_musica.
 */
export async function adicionarFavorito(userId: number, faixaId: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  await db.insert(userFavoritosMusica).values({ userId, faixaId });
}

export async function removerFavorito(userId: number, faixaId: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  await db
    .delete(userFavoritosMusica)
    .where(and(eq(userFavoritosMusica.userId, userId), eq(userFavoritosMusica.faixaId, faixaId)));
}

export async function isFavorito(userId: number, faixaId: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const result = await db
    .select()
    .from(userFavoritosMusica)
    .where(and(eq(userFavoritosMusica.userId, userId), eq(userFavoritosMusica.faixaId, faixaId)))
    .limit(1);

  return result.length > 0;
}

export async function getFavoritosComFaixas(userId: number, page: number = 1, limit: number = 30) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const offset = (page - 1) * limit;

  const favoritos = await db
    .select({ faixa: musicaFaixas, favoritadoEm: userFavoritosMusica.criadoEm })
    .from(userFavoritosMusica)
    .innerJoin(musicaFaixas, eq(userFavoritosMusica.faixaId, musicaFaixas.id))
    .where(eq(userFavoritosMusica.userId, userId))
    .orderBy(desc(userFavoritosMusica.criadoEm))
    .limit(limit)
    .offset(offset);

  const total = await db
    .select({ count: sql<number>`count(*)` })
    .from(userFavoritosMusica)
    .where(eq(userFavoritosMusica.userId, userId));

  return {
    faixas: favoritos.map(f => f.faixa),
    total: total[0]?.count || 0,
    page,
    pages: Math.ceil((total[0]?.count || 0) / limit),
  };
}

export async function adicionarAoHistorico(userId: number, faixaId: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  await db.insert(userHistoricoMusica).values({ userId, faixaId });
}

export async function getHistoricoComFaixas(userId: number, limit: number = 30) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const historico = await db
    .select({ faixa: musicaFaixas, tocadoEm: userHistoricoMusica.tocadoEm })
    .from(userHistoricoMusica)
    .innerJoin(musicaFaixas, eq(userHistoricoMusica.faixaId, musicaFaixas.id))
    .where(eq(userHistoricoMusica.userId, userId))
    .orderBy(desc(userHistoricoMusica.tocadoEm))
    .limit(limit);

  return historico.map(h => h.faixa);
}

/**
 * Playlists — Música (ADR-003)
 *
 * O container (user_playlists) já é genérico e compartilhado com o Podcast —
 * criar/listar/renomear playlist continua passando por trpc.podcast.playlists.
 * Só os ITENS são paralelos: user_playlist_items_musica, para não tornar
 * user_playlist_items (do Podcast) polimórfica.
 */
export async function getPlaylistItemsComFaixas(playlistId: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const itens = await db
    .select({ faixa: musicaFaixas, artistaNome: musicaArtistas.nome })
    .from(userPlaylistItemsMusica)
    .innerJoin(musicaFaixas, eq(userPlaylistItemsMusica.faixaId, musicaFaixas.id))
    .leftJoin(musicaArtistas, eq(musicaFaixas.artistaId, musicaArtistas.id))
    .where(eq(userPlaylistItemsMusica.playlistId, playlistId))
    .orderBy(userPlaylistItemsMusica.adicionadoEm);

  return itens.map(item => ({ ...item.faixa, artistaNome: item.artistaNome ?? null }));
}

export async function adicionarFaixaAPlaylist(playlistId: number, faixaId: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  await db.insert(userPlaylistItemsMusica).values({ playlistId, faixaId });
}

export async function removerFaixaDaPlaylist(playlistId: number, faixaId: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  await db
    .delete(userPlaylistItemsMusica)
    .where(
      and(eq(userPlaylistItemsMusica.playlistId, playlistId), eq(userPlaylistItemsMusica.faixaId, faixaId))
    );
}

export async function getArtistasAdmin(page: number = 1, limit: number = 30) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const offset = (page - 1) * limit;

  const artistas = await db
    .select()
    .from(musicaArtistas)
    .orderBy(desc(musicaArtistas.id))
    .limit(limit)
    .offset(offset);

  const total = await db.select({ count: sql<number>`count(*)` }).from(musicaArtistas);

  return {
    artistas,
    total: total[0]?.count || 0,
    page,
    pages: Math.ceil((total[0]?.count || 0) / limit),
  };
}

/**
 * Álbuns
 */
export type AlbumInput = {
  titulo: string;
  slug?: string | null;
  descricao?: string | null;
  capa?: string | null;
  anoLancamento?: number | null;
  genero?: string | null;
  artistaId?: number | null;
  status?: MusicaAlbum["status"];
  ativo?: number;
};

export async function criarAlbum(input: AlbumInput) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const slug = input.slug?.trim() || slugify(input.titulo);

  const result = await db.insert(musicaAlbuns).values({
    titulo: input.titulo,
    slug,
    descricao: input.descricao ?? null,
    capa: input.capa ?? null,
    anoLancamento: input.anoLancamento ?? null,
    genero: input.genero ?? null,
    artistaId: input.artistaId ?? null,
    status: input.status ?? "publicado",
    ativo: input.ativo ?? 1,
  });

  return { id: Number((result as any).insertId ?? 0) };
}

export async function atualizarAlbum(id: number, input: Partial<AlbumInput>) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const values: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(input)) {
    if (value !== undefined) values[key] = value;
  }
  if (Object.keys(values).length === 0) return;

  await db.update(musicaAlbuns).set(values).where(eq(musicaAlbuns.id, id));
}

export async function deletarAlbum(id: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  await db.delete(musicaAlbuns).where(eq(musicaAlbuns.id, id));
}

export async function getAlbunsAdmin(page: number = 1, limit: number = 30) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const offset = (page - 1) * limit;

  const albuns = await db
    .select()
    .from(musicaAlbuns)
    .orderBy(desc(musicaAlbuns.id))
    .limit(limit)
    .offset(offset);

  const total = await db.select({ count: sql<number>`count(*)` }).from(musicaAlbuns);

  return {
    albuns,
    total: total[0]?.count || 0,
    page,
    pages: Math.ceil((total[0]?.count || 0) / limit),
  };
}

/**
 * Faixas
 */
export type FaixaInput = {
  titulo: string;
  slug?: string | null;
  descricao?: string | null;
  arquivo?: string | null;
  tipoMidia?: "audio" | "video" | "audio_video";
  videoUrl?: string | null;
  capa?: string | null;
  duracao?: number | null;
  numeroFaixa?: number | null;
  ordem?: number | null;
  letra?: string | null;
  tags?: string | null;
  artistaId?: number | null;
  albumId?: number | null;
  status?: MusicaFaixa["status"];
  ativo?: number;
};

export async function criarFaixa(input: FaixaInput) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const slug = input.slug?.trim() || slugify(input.titulo);

  const result = await db.insert(musicaFaixas).values({
    titulo: input.titulo,
    slug,
    descricao: input.descricao ?? null,
    arquivo: input.arquivo ?? null,
    tipoMidia: input.tipoMidia ?? "audio",
    videoUrl: input.videoUrl ?? null,
    capa: input.capa ?? null,
    duracao: input.duracao ?? null,
    numeroFaixa: input.numeroFaixa ?? null,
    ordem: input.ordem ?? 0,
    letra: input.letra ?? null,
    tags: input.tags ?? null,
    artistaId: input.artistaId ?? null,
    albumId: input.albumId ?? null,
    status: input.status ?? "publicado",
    ativo: input.ativo ?? 1,
  });

  return { id: Number((result as any).insertId ?? 0) };
}

export async function atualizarFaixa(id: number, input: Partial<FaixaInput>) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const values: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(input)) {
    if (value !== undefined) values[key] = value;
  }
  if (Object.keys(values).length === 0) return;

  await db.update(musicaFaixas).set(values).where(eq(musicaFaixas.id, id));
}

export async function deletarFaixa(id: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  await db.delete(musicaFaixas).where(eq(musicaFaixas.id, id));
}

export async function getFaixasAdmin(page: number = 1, limit: number = 30) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const offset = (page - 1) * limit;

  const faixas = await db
    .select()
    .from(musicaFaixas)
    .orderBy(desc(musicaFaixas.id))
    .limit(limit)
    .offset(offset);

  const total = await db.select({ count: sql<number>`count(*)` }).from(musicaFaixas);

  return {
    faixas,
    total: total[0]?.count || 0,
    page,
    pages: Math.ceil((total[0]?.count || 0) / limit),
  };
}
