import { eq, and, desc, asc, like, sql, inArray } from "drizzle-orm";
import {
  podcastEpisodios,
  podcastApresentadores,
  podcastProgramas,
  podcastVinhetas,
  userFavoritos,
  userHistorico,
  userPlaylists,
  userPlaylistItems,
  type PodcastEpisodio,
  type PodcastPrograma,
  type PodcastVinheta,
} from "../drizzle/schema";
import { getDb } from "./db";

/**
 * Episódios
 */
export async function getEpisodios(
  page: number = 1,
  limit: number = 30,
  filtros?: { apresentadorId?: number; programaId?: number }
) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const offset = (page - 1) * limit;
  const where = [eq(podcastEpisodios.ativo, 1)];

  if (filtros?.apresentadorId) {
    where.push(eq(podcastEpisodios.apresentadorId, filtros.apresentadorId));
  }
  if (filtros?.programaId) {
    where.push(eq(podcastEpisodios.programaId, filtros.programaId));
  }

  const episodios = await db
    .select()
    .from(podcastEpisodios)
    .where(and(...where))
    .orderBy(desc(podcastEpisodios.reproducoes), desc(podcastEpisodios.id))
    .limit(limit)
    .offset(offset);

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

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

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

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

  return episodio[0] || null;
}

/**
 * Episódios relacionados: outros episódios do mesmo programa (ou, se o
 * episódio não pertence a um programa, do mesmo apresentador).
 */
export async function getEpisodiosRelacionados(episodio: PodcastEpisodio, limit: number = 6) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const where = episodio.programaId
    ? and(
        eq(podcastEpisodios.programaId, episodio.programaId),
        eq(podcastEpisodios.ativo, 1),
        sql`${podcastEpisodios.id} != ${episodio.id}`
      )
    : episodio.apresentadorId
      ? and(
          eq(podcastEpisodios.apresentadorId, episodio.apresentadorId),
          eq(podcastEpisodios.ativo, 1),
          sql`${podcastEpisodios.id} != ${episodio.id}`
        )
      : and(eq(podcastEpisodios.ativo, 1), sql`${podcastEpisodios.id} != ${episodio.id}`);

  return await db
    .select()
    .from(podcastEpisodios)
    .where(where)
    .orderBy(asc(podcastEpisodios.numeroEpisodio), asc(podcastEpisodios.id))
    .limit(limit);
}

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

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

/**
 * Registra uma reprodução: incrementa o contador do episódio e, se houver,
 * do apresentador associado (usado para "Mais tocadas" / "Apresentadores em
 * destaque").
 */
export async function incrementarReproducoes(episodioId: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

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

  const episodio = await getEpisodioById(episodioId);
  if (episodio?.apresentadorId) {
    await db
      .update(podcastApresentadores)
      .set({ reproducoes: sql`${podcastApresentadores.reproducoes} + 1` })
      .where(eq(podcastApresentadores.id, episodio.apresentadorId));
  }
}

/**
 * Admin: cria, atualiza ou remove episódios.
 */
export type EpisodioInput = {
  titulo: string;
  slug?: string | null;
  descricao?: string | null;
  arquivo?: string | null;
  // tipoMidia e videoUrl: adicionados para suporte a vídeo via link externo.
  // Fase 1: quando PlayableItem for implementado, tipoMidia alimenta o campo `kind`.
  tipoMidia?: "audio" | "video" | "audio_video";
  videoUrl?: string | null;
  capa?: string | null;
  duracao?: number | null;
  numeroEpisodio?: number | null;
  ordem?: number | null;
  tags?: string | null;
  transcricao?: string | null;
  apresentadorId?: number | null;
  programaId?: number | null;
  status?: PodcastEpisodio["status"];
  ativo?: number;
};

function slugify(value: string): string {
  return value
    .toLowerCase()
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "")
    .slice(0, 250);
}

export async function criarEpisodio(input: EpisodioInput) {
  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(podcastEpisodios).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,
    numeroEpisodio: input.numeroEpisodio ?? null,
    ordem: input.ordem ?? 0,
    tags: input.tags ?? null,
    transcricao: input.transcricao ?? null,
    apresentadorId: input.apresentadorId ?? null,
    programaId: input.programaId ?? null,
    status: input.status ?? "publicado",
    ativo: input.ativo ?? 1,
  });

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

export async function atualizarEpisodio(id: number, input: Partial<EpisodioInput>) {
  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(podcastEpisodios).set(values).where(eq(podcastEpisodios.id, id));
}

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

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

/**
 * Admin: lista episódios independentemente de `ativo`/`status`, mais
 * recentes primeiro.
 */
export async function getEpisodiosAdmin(page: number = 1, limit: number = 30) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const offset = (page - 1) * limit;

  const episodios = await db
    .select()
    .from(podcastEpisodios)
    .orderBy(desc(podcastEpisodios.id))
    .limit(limit)
    .offset(offset);

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

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

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

  const offset = (page - 1) * limit;

  const apresentadores = await db
    .select()
    .from(podcastApresentadores)
    .where(eq(podcastApresentadores.ativo, 1))
    .orderBy(desc(podcastApresentadores.reproducoes), desc(podcastApresentadores.nome))
    .limit(limit)
    .offset(offset);

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

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

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

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

  return apresentador[0] || null;
}

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

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

export type ApresentadorInput = {
  nome: string;
  slug?: string | null;
  bio?: string | null;
  foto?: string | null;
  ativo?: number;
};

export async function criarApresentador(input: ApresentadorInput) {
  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(podcastApresentadores).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 atualizarApresentador(
  id: number,
  input: Partial<ApresentadorInput>
) {
  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(podcastApresentadores)
    .set(values)
    .where(eq(podcastApresentadores.id, id));
}

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

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

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

  const offset = (page - 1) * limit;

  const apresentadores = await db
    .select()
    .from(podcastApresentadores)
    .orderBy(desc(podcastApresentadores.id))
    .limit(limit)
    .offset(offset);

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

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

/**
 * Programas/Séries
 */
export async function getProgramas(page: number = 1, limit: number = 30) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const offset = (page - 1) * limit;

  const programas = await db
    .select()
    .from(podcastProgramas)
    .where(eq(podcastProgramas.ativo, 1))
    .orderBy(desc(podcastProgramas.ano), desc(podcastProgramas.titulo))
    .limit(limit)
    .offset(offset);

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

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

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

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

  return programa[0] || null;
}

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

  return await db
    .select()
    .from(podcastProgramas)
    .where(eq(podcastProgramas.ativo, 1))
    .orderBy(desc(podcastProgramas.ano), desc(podcastProgramas.titulo))
    .limit(limit);
}

export type ProgramaInput = {
  titulo: string;
  slug?: string | null;
  descricao?: string | null;
  capa?: string | null;
  ano?: number | null;
  genero?: string | null;
  categoria?: string | null;
  apresentadorId?: number | null;
  status?: PodcastPrograma["status"];
  configAutodj?: number;
  configSequencial?: number;
  configVinheta?: number;
  ativo?: number;
};

export async function criarPrograma(input: ProgramaInput) {
  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(podcastProgramas).values({
    titulo: input.titulo,
    slug,
    descricao: input.descricao ?? null,
    capa: input.capa ?? null,
    ano: input.ano ?? null,
    genero: input.genero ?? null,
    categoria: input.categoria ?? "podcast",
    apresentadorId: input.apresentadorId ?? null,
    status: input.status ?? "publicado",
    configAutodj: input.configAutodj ?? 1,
    configSequencial: input.configSequencial ?? 1,
    configVinheta: input.configVinheta ?? 0,
    ativo: input.ativo ?? 1,
  });

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

export async function atualizarPrograma(id: number, input: Partial<ProgramaInput>) {
  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(podcastProgramas).set(values).where(eq(podcastProgramas.id, id));
}

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

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

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

  const offset = (page - 1) * limit;

  const programas = await db
    .select()
    .from(podcastProgramas)
    .orderBy(desc(podcastProgramas.id))
    .limit(limit)
    .offset(offset);

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

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

/**
 * AutoDJ — decide o que tocar a seguir dentro de um programa, respeitando
 * as configurações `config_autodj`, `config_sequencial` e `config_vinheta`.
 *
 * Retorna `null` se o AutoDJ estiver desligado para o programa ou se não
 * houver outro episódio para tocar.
 */
export async function getProximoConteudoAutoDJ(
  programaId: number,
  episodioAtualId: number
): Promise<{ vinhetas: PodcastVinheta[]; proximoEpisodio: PodcastEpisodio | null } | null> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const programa = await getProgramaById(programaId);
  if (!programa || !programa.configAutodj) return null;

  const episodiosDoPrograma = await db
    .select()
    .from(podcastEpisodios)
    .where(
      and(eq(podcastEpisodios.programaId, programaId), eq(podcastEpisodios.ativo, 1))
    )
    .orderBy(
      asc(podcastEpisodios.numeroEpisodio),
      asc(podcastEpisodios.ordem),
      asc(podcastEpisodios.id)
    );

  if (episodiosDoPrograma.length <= 1) return null;

  let proximoEpisodio: PodcastEpisodio | null = null;

  if (programa.configSequencial) {
    const idx = episodiosDoPrograma.findIndex(ep => ep.id === episodioAtualId);
    const nextIdx = idx === -1 ? 0 : (idx + 1) % episodiosDoPrograma.length;
    proximoEpisodio = episodiosDoPrograma[nextIdx] ?? null;
  } else {
    const candidatos = episodiosDoPrograma.filter(ep => ep.id !== episodioAtualId);
    const pool = candidatos.length > 0 ? candidatos : episodiosDoPrograma;
    proximoEpisodio = pool[Math.floor(Math.random() * pool.length)] ?? null;
  }

  // Só séries com o interruptor "tocar vinheta" ligado no admin recebem
  // vinhetas automáticas — decisão confirmada com o Marsan (não é pra
  // todas as séries, só as que optarem).
  let vinhetas: PodcastVinheta[] = [];
  if (programa.configVinheta) {
    vinhetas = await getProximasVinhetas(2);
  }

  return { vinhetas, proximoEpisodio };
}

/**
 * Vinhetas
 */
export type VinhetaCategoria = NonNullable<PodcastVinheta["categoria"]>;

export async function getVinhetas(categoria?: VinhetaCategoria) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const where = categoria
    ? and(eq(podcastVinhetas.ativo, 1), eq(podcastVinhetas.categoria, categoria))
    : eq(podcastVinhetas.ativo, 1);

  return await db
    .select()
    .from(podcastVinhetas)
    .where(where)
    .orderBy(desc(podcastVinhetas.criadoEm));
}

export async function getVinhetaAleatoria(
  categoria: VinhetaCategoria
): Promise<PodcastVinheta | null> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const vinhetas = await db
    .select()
    .from(podcastVinhetas)
    .where(and(eq(podcastVinhetas.ativo, 1), eq(podcastVinhetas.categoria, categoria)));

  if (vinhetas.length === 0) return null;
  return vinhetas[Math.floor(Math.random() * vinhetas.length)] ?? null;
}

/**
 * Rodízio de vinhetas (jul/2026) — "avisos da plataforma" tocando em
 * intervalo automático (a cada troca de episódio nas séries com
 * config_vinheta ligado, e a cada 3 faixas em Música). Um único rodízio,
 * COMPARTILHADO por todos os ouvintes (como uma rádio real alternando
 * spot 1, spot 2, spot 3...), não um contador por pessoa. O índice fica
 * em memória — reinicia do zero se o processo do Node reiniciar, o que é
 * aceitável para uma sequência de rodízio.
 *
 * CORREÇÃO (jul/2026): antes filtrava só a categoria "transicao" por
 * padrão — se as vinhetas cadastradas não estivessem todas nessa
 * categoria específica, o rodízio via menos opções do que realmente
 * existiam e repetia a mesma vinheta. Agora, sem `categoria` informada,
 * usa TODAS as vinhetas ativas (é esse o "programa de apresentação": um
 * pool único de avisos da plataforma, igual rotação de vinhetas/spots
 * numa rádio automatizada — sempre a próxima da fila, nunca repete até
 * passar por todas as outras).
 */
let vinhetaRotationIndex = 0;

export async function getProximasVinhetas(
  quantidade: number = 2,
  categoria?: VinhetaCategoria
): Promise<PodcastVinheta[]> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const where = categoria
    ? and(eq(podcastVinhetas.ativo, 1), eq(podcastVinhetas.categoria, categoria))
    : eq(podcastVinhetas.ativo, 1);

  const vinhetas = await db
    .select()
    .from(podcastVinhetas)
    .where(where)
    .orderBy(asc(podcastVinhetas.id));

  if (vinhetas.length === 0) return [];

  const resultado: PodcastVinheta[] = [];
  for (let i = 0; i < quantidade; i++) {
    resultado.push(vinhetas[vinhetaRotationIndex % vinhetas.length]!);
    vinhetaRotationIndex++;
  }
  return resultado;
}

export type VinhetaInput = {
  titulo: string;
  arquivo: string;
  categoria?: VinhetaCategoria;
  ativo?: number;
};

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

  const result = await db.insert(podcastVinhetas).values({
    titulo: input.titulo,
    arquivo: input.arquivo,
    categoria: input.categoria ?? "transicao",
    ativo: input.ativo ?? 1,
  });

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

export async function atualizarVinheta(id: number, input: Partial<VinhetaInput>) {
  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(podcastVinhetas).set(values).where(eq(podcastVinhetas.id, id));
}

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

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

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

  return await db.select().from(podcastVinhetas).orderBy(desc(podcastVinhetas.id));
}

/**
 * Busca
 */
export async function buscar(query: string, limit: number = 15) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const searchPattern = `%${query}%`;

  const [episodios, apresentadores, programas] = await Promise.all([
    db
      .select()
      .from(podcastEpisodios)
      .where(
        and(
          eq(podcastEpisodios.ativo, 1),
          like(podcastEpisodios.titulo, searchPattern)
        )
      )
      .limit(limit),
    db
      .select()
      .from(podcastApresentadores)
      .where(
        and(
          eq(podcastApresentadores.ativo, 1),
          like(podcastApresentadores.nome, searchPattern)
        )
      )
      .limit(limit),
    db
      .select()
      .from(podcastProgramas)
      .where(
        and(
          eq(podcastProgramas.ativo, 1),
          like(podcastProgramas.titulo, searchPattern)
        )
      )
      .limit(limit),
  ]);

  return { episodios, apresentadores, programas };
}

/**
 * Favoritos
 */
export async function adicionarFavorito(userId: number, episodioId: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  await db.insert(userFavoritos).values({ userId, episodioId });
}

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

  await db
    .delete(userFavoritos)
    .where(
      and(
        eq(userFavoritos.userId, userId),
        eq(userFavoritos.episodioId, episodioId)
      )
    );
}

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

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

  return result.length > 0;
}

export async function getFavoritos(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()
    .from(userFavoritos)
    .where(eq(userFavoritos.userId, userId))
    .orderBy(desc(userFavoritos.criadoEm))
    .limit(limit)
    .offset(offset);

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

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

/**
 * Favoritos com os dados do episódio (usado na Biblioteca).
 */
export async function getFavoritosComEpisodios(
  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 rows = await db
    .select({ episodio: podcastEpisodios, favoritadoEm: userFavoritos.criadoEm })
    .from(userFavoritos)
    .innerJoin(podcastEpisodios, eq(userFavoritos.episodioId, podcastEpisodios.id))
    .where(eq(userFavoritos.userId, userId))
    .orderBy(desc(userFavoritos.criadoEm))
    .limit(limit)
    .offset(offset);

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

  return {
    episodios: rows.map(row => ({ ...row.episodio, favoritadoEm: row.favoritadoEm })),
    total: total[0]?.count || 0,
    page,
    pages: Math.ceil((total[0]?.count || 0) / limit),
  };
}

/**
 * Histórico
 */
export async function adicionarAoHistorico(userId: number, episodioId: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  await db.insert(userHistorico).values({ userId, episodioId });
}

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

  return await db
    .select()
    .from(userHistorico)
    .where(eq(userHistorico.userId, userId))
    .orderBy(desc(userHistorico.tocadoEm))
    .limit(limit);
}

/**
 * Histórico com os dados do episódio (usado na Biblioteca). Mostra apenas a
 * reprodução mais recente de cada episódio.
 */
export async function getHistoricoComEpisodios(userId: number, limit: number = 50) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const rows = await db
    .select({ episodio: podcastEpisodios, tocadoEm: userHistorico.tocadoEm })
    .from(userHistorico)
    .innerJoin(podcastEpisodios, eq(userHistorico.episodioId, podcastEpisodios.id))
    .where(eq(userHistorico.userId, userId))
    .orderBy(desc(userHistorico.tocadoEm))
    .limit(limit * 3); // margem para deduplicar por episódio

  const vistos = new Set<number>();
  const episodios: Array<PodcastEpisodio & { tocadoEm: Date | null }> = [];

  for (const row of rows) {
    if (vistos.has(row.episodio.id)) continue;
    vistos.add(row.episodio.id);
    episodios.push({ ...row.episodio, tocadoEm: row.tocadoEm });
    if (episodios.length >= limit) break;
  }

  return episodios;
}

/**
 * Playlists
 */
export async function criarPlaylist(userId: number, titulo: string, descricao?: string) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const result = await db.insert(userPlaylists).values({
    userId,
    titulo,
    descricao,
  });

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

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

  return await db
    .select()
    .from(userPlaylists)
    .where(eq(userPlaylists.userId, userId))
    .orderBy(desc(userPlaylists.criadoEm));
}

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

  const playlist = await db
    .select()
    .from(userPlaylists)
    .where(eq(userPlaylists.id, playlistId))
    .limit(1);

  return playlist[0] || null;
}

export async function atualizarPlaylist(
  playlistId: number,
  input: { titulo?: string; descricao?: string | null; capa?: string | null }
) {
  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(userPlaylists).set(values).where(eq(userPlaylists.id, playlistId));
}

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

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

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

  const maxOrdem = await db
    .select({ max: sql<number>`MAX(ordem)` })
    .from(userPlaylistItems)
    .where(eq(userPlaylistItems.playlistId, playlistId));

  const ordem = (maxOrdem[0]?.max || 0) + 1;

  await db.insert(userPlaylistItems).values({
    playlistId,
    episodioId,
    ordem,
  });
}

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

  await db
    .delete(userPlaylistItems)
    .where(
      and(
        eq(userPlaylistItems.playlistId, playlistId),
        eq(userPlaylistItems.episodioId, episodioId)
      )
    );
}

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

  return await db
    .select()
    .from(userPlaylistItems)
    .where(eq(userPlaylistItems.playlistId, playlistId))
    .orderBy(userPlaylistItems.ordem);
}

/**
 * Itens da playlist com os dados do episódio (usado na Biblioteca e na
 * página da playlist).
 */
export async function getPlaylistItemsComEpisodios(playlistId: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const rows = await db
    .select({ episodio: podcastEpisodios, ordem: userPlaylistItems.ordem })
    .from(userPlaylistItems)
    .innerJoin(podcastEpisodios, eq(userPlaylistItems.episodioId, podcastEpisodios.id))
    .where(eq(userPlaylistItems.playlistId, playlistId))
    .orderBy(asc(userPlaylistItems.ordem));

  return rows.map(row => row.episodio);
}

/**
 * Playlists do usuário, já com a contagem de episódios em cada uma.
 */
export async function getPlaylistsComContagem(userId: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const playlists = await db
    .select()
    .from(userPlaylists)
    .where(eq(userPlaylists.userId, userId))
    .orderBy(desc(userPlaylists.criadoEm));

  if (playlists.length === 0) return [];

  const ids = playlists.map(p => p.id);
  const counts = await db
    .select({
      playlistId: userPlaylistItems.playlistId,
      total: sql<number>`count(*)`,
    })
    .from(userPlaylistItems)
    .where(inArray(userPlaylistItems.playlistId, ids))
    .groupBy(userPlaylistItems.playlistId);

  const countMap = new Map(counts.map(c => [c.playlistId, Number(c.total)]));

  return playlists.map(p => ({ ...p, totalEpisodios: countMap.get(p.id) ?? 0 }));
}
