import { COOKIE_NAME, ONE_YEAR_MS } from "@shared/const";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { getSessionCookieOptions } from "./_core/cookies";
import { systemRouter } from "./_core/systemRouter";
import { adminProcedure, protectedProcedure, publicProcedure, router } from "./_core/trpc";
import * as auth from "./_core/auth";
import * as db from "./podcast-db";
import * as musicaDb from "./musica-db";
import * as userDb from "./db";
import type { User } from "../drizzle/schema";

const emailSchema = z.string().trim().toLowerCase().email("E-mail inválido");
const passwordSchema = z.string().min(8, "A senha deve ter pelo menos 8 caracteres");
const nameSchema = z.string().trim().min(2, "Informe seu nome").max(255);

const episodioStatusSchema = z.enum(["rascunho", "publicado", "agendado"]);
const programaStatusSchema = z.enum(["rascunho", "publicado", "arquivado"]);
const vinhetaCategoriaSchema = z.enum(["abertura", "transicao", "encerramento"]);
const musicaAlbumStatusSchema = z.enum(["rascunho", "publicado", "arquivado"]);
const musicaFaixaStatusSchema = z.enum(["rascunho", "publicado", "agendado"]);

function publicUser(user: User) {
  return {
    id: user.id,
    name: user.name,
    email: user.email,
    role: user.role,
  };
}

async function startSession(
  ctx: { req: import("./_core/context").TrpcContext["req"]; res: import("./_core/context").TrpcContext["res"] },
  userId: number
) {
  const token = await auth.createSessionToken(userId);
  const cookieOptions = getSessionCookieOptions(ctx.req);
  ctx.res.cookie(COOKIE_NAME, token, { ...cookieOptions, maxAge: ONE_YEAR_MS });
}

const paginationInput = z.object({
  page: z.number().int().min(1).default(1),
  limit: z.number().int().min(1).max(60).default(24),
});

export const appRouter = router({
  system: systemRouter,

  auth: router({
    me: publicProcedure.query(opts => opts.ctx.user),

    register: publicProcedure
      .input(
        z.object({
          name: nameSchema,
          email: emailSchema,
          password: passwordSchema,
        })
      )
      .mutation(async ({ input, ctx }) => {
        const existing = await userDb.getUserByEmail(input.email);
        if (existing) {
          throw new TRPCError({
            code: "CONFLICT",
            message: "Já existe uma conta com este e-mail.",
          });
        }

        // The very first account created becomes the site administrator.
        const isFirstUser = (await userDb.countUsers()) === 0;
        const passwordHash = await auth.hashPassword(input.password);

        const user = await userDb.createLocalUser({
          name: input.name,
          email: input.email,
          passwordHash,
          role: isFirstUser ? "admin" : "user",
        });

        await startSession(ctx, user.id);
        return publicUser(user);
      }),

    login: publicProcedure
      .input(
        z.object({
          email: emailSchema,
          password: z.string().min(1, "Informe sua senha"),
        })
      )
      .mutation(async ({ input, ctx }) => {
        const user = await userDb.getUserByEmail(input.email);
        const valid = await auth.verifyPassword(input.password, user?.password);

        if (!user || !valid) {
          throw new TRPCError({
            code: "UNAUTHORIZED",
            message: "E-mail ou senha inválidos.",
          });
        }

        await userDb.atualizarUltimoLogin(user.id);
        await startSession(ctx, user.id);
        return publicUser(user);
      }),

    logout: publicProcedure.mutation(({ ctx }) => {
      const cookieOptions = getSessionCookieOptions(ctx.req);
      ctx.res.clearCookie(COOKIE_NAME, { ...cookieOptions, maxAge: -1 });
      return {
        success: true,
      } as const;
    }),
  }),

  podcast: router({
    getHomeData: publicProcedure.query(async () => {
      const [topEpisodios, apresentadores, programas] = await Promise.all([
        db.getTopEpisodios(8),
        db.getApresentadoresDestaque(8),
        db.getProgramasRecentes(8),
      ]);
      return { topEpisodios, apresentadores, programas };
    }),

    getEpisodios: publicProcedure
      .input(
        paginationInput.extend({
          apresentadorId: z.number().int().optional(),
          programaId: z.number().int().optional(),
        })
      )
      .query(async ({ input }) => {
        return await db.getEpisodios(input.page, input.limit, {
          apresentadorId: input.apresentadorId,
          programaId: input.programaId,
        });
      }),

    getEpisodio: publicProcedure
      .input(z.object({ id: z.number() }))
      .query(async ({ input }) => {
        const episodio = await db.getEpisodioById(input.id);
        if (!episodio) return null;

        const [programa, apresentador, relacionados] = await Promise.all([
          episodio.programaId ? db.getProgramaById(episodio.programaId) : null,
          episodio.apresentadorId ? db.getApresentadorById(episodio.apresentadorId) : null,
          db.getEpisodiosRelacionados(episodio, 6),
        ]);

        return { episodio, programa, apresentador, relacionados };
      }),

    getApresentadores: publicProcedure.input(paginationInput).query(async ({ input }) => {
      return await db.getApresentadores(input.page, input.limit);
    }),

    getApresentador: publicProcedure
      .input(z.object({ id: z.number() }))
      .query(async ({ input }) => {
        const apresentador = await db.getApresentadorById(input.id);
        const episodios = await db.getEpisodios(1, 50, { apresentadorId: input.id });
        const programas = await db.getProgramas(1, 50);
        return {
          apresentador,
          episodios: episodios.episodios,
          programas: programas.programas.filter(p => p.apresentadorId === input.id),
        };
      }),

    getProgramas: publicProcedure.input(paginationInput).query(async ({ input }) => {
      return await db.getProgramas(input.page, input.limit);
    }),

    getPrograma: publicProcedure
      .input(z.object({ id: z.number() }))
      .query(async ({ input }) => {
        const programa = await db.getProgramaById(input.id);
        const episodios = await db.getEpisodios(1, 100, { programaId: input.id });
        return { programa, episodios: episodios.episodios };
      }),

    getVinhetas: publicProcedure
      .input(z.object({ categoria: vinhetaCategoriaSchema.optional() }).optional())
      .query(async ({ input }) => {
        return await db.getVinhetas(input?.categoria);
      }),

    // Rodízio de vinhetas — usado tanto pelo AutoDJ de Podcast (via
    // getProximoConteudoAutoDJ) quanto pelo intervalo automático de Música
    // (a cada 3 faixas). Vinhetas são um recurso da plataforma como um
    // todo, não exclusivas de um módulo.
    getProximasVinhetas: publicProcedure
      .input(z.object({ quantidade: z.number().int().min(1).max(5).default(2) }))
      .query(async ({ input }) => {
        return await db.getProximasVinhetas(input.quantidade);
      }),

    getProximoConteudoAutoDJ: publicProcedure
      .input(z.object({ programaId: z.number(), episodioAtualId: z.number() }))
      .query(async ({ input }) => {
        return await db.getProximoConteudoAutoDJ(input.programaId, input.episodioAtualId);
      }),

    buscar: publicProcedure
      .input(z.object({ query: z.string() }))
      .query(async ({ input }) => {
        if (!input.query.trim()) {
          return { episodios: [], apresentadores: [], programas: [] };
        }
        return await db.buscar(input.query.trim());
      }),

    // Registra uma reprodução: incrementa contadores e, se houver usuário
    // autenticado, registra no histórico.
    reproduzir: publicProcedure
      .input(z.object({ episodioId: z.number() }))
      .mutation(async ({ input, ctx }) => {
        await db.incrementarReproducoes(input.episodioId);
        if (ctx.user) {
          await db.adicionarAoHistorico(ctx.user.id, input.episodioId);
        }
        return { success: true } as const;
      }),

    // Favoritos
    toggleFavorito: protectedProcedure
      .input(z.object({ episodioId: z.number() }))
      .mutation(async ({ input, ctx }) => {
        const userId = ctx.user.id;
        const favorito = await db.isFavorito(userId, input.episodioId);
        if (favorito) {
          await db.removerFavorito(userId, input.episodioId);
          return { favorited: false };
        } else {
          await db.adicionarFavorito(userId, input.episodioId);
          return { favorited: true };
        }
      }),

    getFavoritos: protectedProcedure
      .input(paginationInput.partial().optional())
      .query(async ({ input, ctx }) => {
        return await db.getFavoritosComEpisodios(ctx.user.id, input?.page ?? 1, input?.limit ?? 30);
      }),

    isFavorito: protectedProcedure
      .input(z.object({ episodioId: z.number() }))
      .query(async ({ input, ctx }) => {
        return await db.isFavorito(ctx.user.id, input.episodioId);
      }),

    // Histórico
    getHistorico: protectedProcedure
      .input(z.object({ limit: z.number().int().min(1).max(100).default(30) }).optional())
      .query(async ({ input, ctx }) => {
        return await db.getHistoricoComEpisodios(ctx.user.id, input?.limit ?? 30);
      }),

    // Playlists
    playlists: router({
      listar: protectedProcedure.query(async ({ ctx }) => {
        return await db.getPlaylistsComContagem(ctx.user.id);
      }),

      obter: protectedProcedure
        .input(z.object({ id: z.number() }))
        .query(async ({ input, ctx }) => {
          const playlist = await db.getPlaylistById(input.id);
          if (!playlist || playlist.userId !== ctx.user.id) {
            throw new TRPCError({ code: "NOT_FOUND", message: "Playlist não encontrada." });
          }
          const episodios = await db.getPlaylistItemsComEpisodios(input.id);
          return { playlist, episodios };
        }),

      criar: protectedProcedure
        .input(z.object({ titulo: z.string().trim().min(1).max(255), descricao: z.string().trim().max(2000).optional() }))
        .mutation(async ({ input, ctx }) => {
          return await db.criarPlaylist(ctx.user.id, input.titulo, input.descricao);
        }),

      atualizar: protectedProcedure
        .input(
          z.object({
            id: z.number(),
            titulo: z.string().trim().min(1).max(255).optional(),
            descricao: z.string().trim().max(2000).nullable().optional(),
            capa: z.string().trim().max(500).nullable().optional(),
          })
        )
        .mutation(async ({ input, ctx }) => {
          const playlist = await db.getPlaylistById(input.id);
          if (!playlist || playlist.userId !== ctx.user.id) {
            throw new TRPCError({ code: "NOT_FOUND", message: "Playlist não encontrada." });
          }
          await db.atualizarPlaylist(input.id, {
            titulo: input.titulo,
            descricao: input.descricao,
            capa: input.capa,
          });
          return { success: true } as const;
        }),

      deletar: protectedProcedure
        .input(z.object({ id: z.number() }))
        .mutation(async ({ input, ctx }) => {
          const playlist = await db.getPlaylistById(input.id);
          if (!playlist || playlist.userId !== ctx.user.id) {
            throw new TRPCError({ code: "NOT_FOUND", message: "Playlist não encontrada." });
          }
          await db.deletarPlaylist(input.id);
          return { success: true } as const;
        }),

      adicionarEpisodio: protectedProcedure
        .input(z.object({ playlistId: z.number(), episodioId: z.number() }))
        .mutation(async ({ input, ctx }) => {
          const playlist = await db.getPlaylistById(input.playlistId);
          if (!playlist || playlist.userId !== ctx.user.id) {
            throw new TRPCError({ code: "NOT_FOUND", message: "Playlist não encontrada." });
          }
          await db.adicionarAPlaylist(input.playlistId, input.episodioId);
          return { success: true } as const;
        }),

      removerEpisodio: protectedProcedure
        .input(z.object({ playlistId: z.number(), episodioId: z.number() }))
        .mutation(async ({ input, ctx }) => {
          const playlist = await db.getPlaylistById(input.playlistId);
          if (!playlist || playlist.userId !== ctx.user.id) {
            throw new TRPCError({ code: "NOT_FOUND", message: "Playlist não encontrada." });
          }
          await db.removerDaPlaylist(input.playlistId, input.episodioId);
          return { success: true } as const;
        }),
    }),

    // Admin routes
    admin: router({
      stats: adminProcedure.query(async () => {
        const [episodios, apresentadores, programas] = await Promise.all([
          db.getEpisodiosAdmin(1, 1),
          db.getApresentadoresAdmin(1, 1),
          db.getProgramasAdmin(1, 1),
        ]);
        const topEpisodios = await db.getTopEpisodios(1000);
        const totalReproducoes = topEpisodios.reduce((acc, ep) => acc + (ep.reproducoes || 0), 0);

        return {
          totalEpisodios: episodios.total,
          totalApresentadores: apresentadores.total,
          totalProgramas: programas.total,
          totalReproducoes,
        };
      }),

      episodios: router({
        listar: adminProcedure.input(paginationInput).query(async ({ input }) => {
          return await db.getEpisodiosAdmin(input.page, input.limit);
        }),

        criar: adminProcedure
          .input(
            z.object({
              titulo: z.string().trim().min(1).max(255),
              slug: z.string().trim().max(255).optional(),
              descricao: z.string().optional().nullable(),
              arquivo: z.string().trim().max(500).optional().nullable(),
              tipoMidia: z.enum(["audio", "video", "audio_video"]).optional(),
              videoUrl: z.string().trim().max(500).url().optional().nullable(),
              capa: z.string().trim().max(500).optional().nullable(),
              duracao: z.number().int().min(0).optional().nullable(),
              numeroEpisodio: z.number().int().optional().nullable(),
              ordem: z.number().int().optional().nullable(),
              tags: z.string().optional().nullable(),
              transcricao: z.string().optional().nullable(),
              apresentadorId: z.number().int().optional().nullable(),
              programaId: z.number().int().optional().nullable(),
              status: episodioStatusSchema.optional(),
              ativo: z.number().int().min(0).max(1).optional(),
            })
          )
          .mutation(async ({ input }) => {
            return await db.criarEpisodio(input);
          }),

        atualizar: adminProcedure
          .input(
            z.object({
              id: z.number(),
              titulo: z.string().trim().min(1).max(255).optional(),
              slug: z.string().trim().max(255).optional(),
              descricao: z.string().optional().nullable(),
              arquivo: z.string().trim().max(500).optional().nullable(),
              tipoMidia: z.enum(["audio", "video", "audio_video"]).optional(),
              videoUrl: z.string().trim().max(500).url().optional().nullable(),
              capa: z.string().trim().max(500).optional().nullable(),
              duracao: z.number().int().min(0).optional().nullable(),
              numeroEpisodio: z.number().int().optional().nullable(),
              ordem: z.number().int().optional().nullable(),
              tags: z.string().optional().nullable(),
              transcricao: z.string().optional().nullable(),
              apresentadorId: z.number().int().optional().nullable(),
              programaId: z.number().int().optional().nullable(),
              status: episodioStatusSchema.optional(),
              ativo: z.number().int().min(0).max(1).optional(),
            })
          )
          .mutation(async ({ input }) => {
            const { id, ...rest } = input;
            await db.atualizarEpisodio(id, rest);
            return { success: true } as const;
          }),

        deletar: adminProcedure
          .input(z.object({ id: z.number() }))
          .mutation(async ({ input }) => {
            await db.deletarEpisodio(input.id);
            return { success: true } as const;
          }),
      }),

      apresentadores: router({
        listar: adminProcedure.input(paginationInput).query(async ({ input }) => {
          return await db.getApresentadoresAdmin(input.page, input.limit);
        }),

        criar: adminProcedure
          .input(
            z.object({
              nome: z.string().trim().min(1).max(255),
              slug: z.string().trim().max(255).optional(),
              bio: z.string().optional().nullable(),
              foto: z.string().trim().max(500).optional().nullable(),
              ativo: z.number().int().min(0).max(1).optional(),
            })
          )
          .mutation(async ({ input }) => {
            return await db.criarApresentador(input);
          }),

        atualizar: adminProcedure
          .input(
            z.object({
              id: z.number(),
              nome: z.string().trim().min(1).max(255).optional(),
              slug: z.string().trim().max(255).optional(),
              bio: z.string().optional().nullable(),
              foto: z.string().trim().max(500).optional().nullable(),
              ativo: z.number().int().min(0).max(1).optional(),
            })
          )
          .mutation(async ({ input }) => {
            const { id, ...rest } = input;
            await db.atualizarApresentador(id, rest);
            return { success: true } as const;
          }),

        deletar: adminProcedure
          .input(z.object({ id: z.number() }))
          .mutation(async ({ input }) => {
            await db.deletarApresentador(input.id);
            return { success: true } as const;
          }),
      }),

      programas: router({
        listar: adminProcedure.input(paginationInput).query(async ({ input }) => {
          return await db.getProgramasAdmin(input.page, input.limit);
        }),

        criar: adminProcedure
          .input(
            z.object({
              titulo: z.string().trim().min(1).max(255),
              slug: z.string().trim().max(255).optional(),
              descricao: z.string().optional().nullable(),
              capa: z.string().trim().max(500).optional().nullable(),
              ano: z.number().int().optional().nullable(),
              genero: z.string().trim().max(100).optional().nullable(),
              categoria: z.string().trim().max(100).optional().nullable(),
              apresentadorId: z.number().int().optional().nullable(),
              status: programaStatusSchema.optional(),
              configAutodj: z.number().int().min(0).max(1).optional(),
              configSequencial: z.number().int().min(0).max(1).optional(),
              configVinheta: z.number().int().min(0).max(1).optional(),
              ativo: z.number().int().min(0).max(1).optional(),
            })
          )
          .mutation(async ({ input }) => {
            return await db.criarPrograma(input);
          }),

        atualizar: adminProcedure
          .input(
            z.object({
              id: z.number(),
              titulo: z.string().trim().min(1).max(255).optional(),
              slug: z.string().trim().max(255).optional(),
              descricao: z.string().optional().nullable(),
              capa: z.string().trim().max(500).optional().nullable(),
              ano: z.number().int().optional().nullable(),
              genero: z.string().trim().max(100).optional().nullable(),
              categoria: z.string().trim().max(100).optional().nullable(),
              apresentadorId: z.number().int().optional().nullable(),
              status: programaStatusSchema.optional(),
              configAutodj: z.number().int().min(0).max(1).optional(),
              configSequencial: z.number().int().min(0).max(1).optional(),
              configVinheta: z.number().int().min(0).max(1).optional(),
              ativo: z.number().int().min(0).max(1).optional(),
            })
          )
          .mutation(async ({ input }) => {
            const { id, ...rest } = input;
            await db.atualizarPrograma(id, rest);
            return { success: true } as const;
          }),

        deletar: adminProcedure
          .input(z.object({ id: z.number() }))
          .mutation(async ({ input }) => {
            await db.deletarPrograma(input.id);
            return { success: true } as const;
          }),
      }),

      vinhetas: router({
        listar: adminProcedure.query(async () => {
          return await db.getVinhetasAdmin();
        }),

        criar: adminProcedure
          .input(
            z.object({
              titulo: z.string().trim().min(1).max(255),
              arquivo: z.string().trim().min(1).max(500),
              categoria: vinhetaCategoriaSchema.optional(),
              ativo: z.number().int().min(0).max(1).optional(),
            })
          )
          .mutation(async ({ input }) => {
            return await db.criarVinheta(input);
          }),

        atualizar: adminProcedure
          .input(
            z.object({
              id: z.number(),
              titulo: z.string().trim().min(1).max(255).optional(),
              arquivo: z.string().trim().min(1).max(500).optional(),
              categoria: vinhetaCategoriaSchema.optional(),
              ativo: z.number().int().min(0).max(1).optional(),
            })
          )
          .mutation(async ({ input }) => {
            const { id, ...rest } = input;
            await db.atualizarVinheta(id, rest);
            return { success: true } as const;
          }),

        deletar: adminProcedure
          .input(z.object({ id: z.number() }))
          .mutation(async ({ input }) => {
            await db.deletarVinheta(input.id);
            return { success: true } as const;
          }),
      }),
    }),
  }),

  musica: router({
    admin: router({
      stats: adminProcedure.query(async () => {
        return await musicaDb.getEstatisticas();
      }),

      artistas: router({
        listar: adminProcedure.input(paginationInput).query(async ({ input }) => {
          return await musicaDb.getArtistasAdmin(input.page, input.limit);
        }),

        criar: adminProcedure
          .input(
            z.object({
              nome: z.string().trim().min(1).max(255),
              slug: z.string().trim().max(255).optional(),
              bio: z.string().optional().nullable(),
              foto: z.string().trim().max(500).optional().nullable(),
              ativo: z.number().int().min(0).max(1).optional(),
            })
          )
          .mutation(async ({ input }) => {
            return await musicaDb.criarArtista(input);
          }),

        atualizar: adminProcedure
          .input(
            z.object({
              id: z.number(),
              nome: z.string().trim().min(1).max(255).optional(),
              slug: z.string().trim().max(255).optional(),
              bio: z.string().optional().nullable(),
              foto: z.string().trim().max(500).optional().nullable(),
              ativo: z.number().int().min(0).max(1).optional(),
            })
          )
          .mutation(async ({ input }) => {
            const { id, ...rest } = input;
            await musicaDb.atualizarArtista(id, rest);
            return { success: true } as const;
          }),

        deletar: adminProcedure
          .input(z.object({ id: z.number() }))
          .mutation(async ({ input }) => {
            await musicaDb.deletarArtista(input.id);
            return { success: true } as const;
          }),
      }),

      albuns: router({
        listar: adminProcedure.input(paginationInput).query(async ({ input }) => {
          return await musicaDb.getAlbunsAdmin(input.page, input.limit);
        }),

        criar: adminProcedure
          .input(
            z.object({
              titulo: z.string().trim().min(1).max(255),
              slug: z.string().trim().max(255).optional(),
              descricao: z.string().optional().nullable(),
              capa: z.string().trim().max(500).optional().nullable(),
              anoLancamento: z.number().int().optional().nullable(),
              genero: z.string().trim().max(100).optional().nullable(),
              artistaId: z.number().int().optional().nullable(),
              status: musicaAlbumStatusSchema.optional(),
              ativo: z.number().int().min(0).max(1).optional(),
            })
          )
          .mutation(async ({ input }) => {
            return await musicaDb.criarAlbum(input);
          }),

        atualizar: adminProcedure
          .input(
            z.object({
              id: z.number(),
              titulo: z.string().trim().min(1).max(255).optional(),
              slug: z.string().trim().max(255).optional(),
              descricao: z.string().optional().nullable(),
              capa: z.string().trim().max(500).optional().nullable(),
              anoLancamento: z.number().int().optional().nullable(),
              genero: z.string().trim().max(100).optional().nullable(),
              artistaId: z.number().int().optional().nullable(),
              status: musicaAlbumStatusSchema.optional(),
              ativo: z.number().int().min(0).max(1).optional(),
            })
          )
          .mutation(async ({ input }) => {
            const { id, ...rest } = input;
            await musicaDb.atualizarAlbum(id, rest);
            return { success: true } as const;
          }),

        deletar: adminProcedure
          .input(z.object({ id: z.number() }))
          .mutation(async ({ input }) => {
            await musicaDb.deletarAlbum(input.id);
            return { success: true } as const;
          }),
      }),

      faixas: router({
        listar: adminProcedure.input(paginationInput).query(async ({ input }) => {
          return await musicaDb.getFaixasAdmin(input.page, input.limit);
        }),

        criar: adminProcedure
          .input(
            z.object({
              titulo: z.string().trim().min(1).max(255),
              slug: z.string().trim().max(255).optional(),
              descricao: z.string().optional().nullable(),
              arquivo: z.string().trim().max(500).optional().nullable(),
              tipoMidia: z.enum(["audio", "video", "audio_video"]).optional(),
              videoUrl: z.string().trim().max(500).url().optional().nullable(),
              capa: z.string().trim().max(500).optional().nullable(),
              duracao: z.number().int().min(0).optional().nullable(),
              numeroFaixa: z.number().int().optional().nullable(),
              ordem: z.number().int().optional().nullable(),
              letra: z.string().optional().nullable(),
              tags: z.string().optional().nullable(),
              artistaId: z.number().int().optional().nullable(),
              albumId: z.number().int().optional().nullable(),
              status: musicaFaixaStatusSchema.optional(),
              ativo: z.number().int().min(0).max(1).optional(),
            })
          )
          .mutation(async ({ input }) => {
            return await musicaDb.criarFaixa(input);
          }),

        atualizar: adminProcedure
          .input(
            z.object({
              id: z.number(),
              titulo: z.string().trim().min(1).max(255).optional(),
              slug: z.string().trim().max(255).optional(),
              descricao: z.string().optional().nullable(),
              arquivo: z.string().trim().max(500).optional().nullable(),
              tipoMidia: z.enum(["audio", "video", "audio_video"]).optional(),
              videoUrl: z.string().trim().max(500).url().optional().nullable(),
              capa: z.string().trim().max(500).optional().nullable(),
              duracao: z.number().int().min(0).optional().nullable(),
              numeroFaixa: z.number().int().optional().nullable(),
              ordem: z.number().int().optional().nullable(),
              letra: z.string().optional().nullable(),
              tags: z.string().optional().nullable(),
              artistaId: z.number().int().optional().nullable(),
              albumId: z.number().int().optional().nullable(),
              status: musicaFaixaStatusSchema.optional(),
              ativo: z.number().int().min(0).max(1).optional(),
            })
          )
          .mutation(async ({ input }) => {
            const { id, ...rest } = input;
            await musicaDb.atualizarFaixa(id, rest);
            return { success: true } as const;
          }),

        deletar: adminProcedure
          .input(z.object({ id: z.number() }))
          .mutation(async ({ input }) => {
            await musicaDb.deletarFaixa(input.id);
            return { success: true } as const;
          }),
      }),
    }),

  getArtistas: publicProcedure.input(paginationInput).query(async ({ input }) => {
    return await musicaDb.getArtistas(input.page, input.limit);
  }),

  getArtista: publicProcedure.input(z.object({ id: z.number() })).query(async ({ input }) => {
    return await musicaDb.getArtistaById(input.id);
  }),

  getAlbuns: publicProcedure.input(paginationInput).query(async ({ input }) => {
    return await musicaDb.getAlbuns(input.page, input.limit);
  }),

  getFaixas: publicProcedure.input(paginationInput).query(async ({ input }) => {
    return await musicaDb.getFaixas(input.page, input.limit);
  }),

  getAlbum: publicProcedure.input(z.object({ id: z.number() })).query(async ({ input }) => {
    return await musicaDb.getAlbumById(input.id);
  }),

  getFaixa: publicProcedure.input(z.object({ id: z.number() })).query(async ({ input }) => {
    return await musicaDb.getFaixaById(input.id);
  }),

  getTopFaixas: publicProcedure.query(async () => {
    return await musicaDb.getTopFaixas(20);
  }),

  reproduzir: publicProcedure
    .input(z.object({ faixaId: z.number() }))
    .mutation(async ({ input, ctx }) => {
      await musicaDb.incrementarReproducoesFaixa(input.faixaId);
      if (ctx.user) {
        await musicaDb.adicionarAoHistorico(ctx.user.id, input.faixaId);
      }
      return { success: true } as const;
    }),

  toggleFavorito: protectedProcedure
    .input(z.object({ faixaId: z.number() }))
    .mutation(async ({ input, ctx }) => {
      const userId = ctx.user.id;
      const favorito = await musicaDb.isFavorito(userId, input.faixaId);
      if (favorito) {
        await musicaDb.removerFavorito(userId, input.faixaId);
        return { favorited: false };
      } else {
        await musicaDb.adicionarFavorito(userId, input.faixaId);
        return { favorited: true };
      }
    }),

  getFavoritos: protectedProcedure
    .input(paginationInput.partial().optional())
    .query(async ({ input, ctx }) => {
      return await musicaDb.getFavoritosComFaixas(ctx.user.id, input?.page ?? 1, input?.limit ?? 30);
    }),

  isFavorito: protectedProcedure
    .input(z.object({ faixaId: z.number() }))
    .query(async ({ input, ctx }) => {
      return await musicaDb.isFavorito(ctx.user.id, input.faixaId);
    }),

  getHistorico: protectedProcedure
    .input(z.object({ limit: z.number().int().min(1).max(100).default(30) }).optional())
    .query(async ({ input, ctx }) => {
      return await musicaDb.getHistoricoComFaixas(ctx.user.id, input?.limit ?? 30);
    }),

  playlists: router({
    obterFaixas: protectedProcedure
      .input(z.object({ playlistId: z.number() }))
      .query(async ({ input, ctx }) => {
        const playlist = await db.getPlaylistById(input.playlistId);
        if (!playlist || playlist.userId !== ctx.user.id) {
          throw new TRPCError({ code: "NOT_FOUND", message: "Playlist não encontrada." });
        }
        return await musicaDb.getPlaylistItemsComFaixas(input.playlistId);
      }),

    adicionarFaixa: protectedProcedure
      .input(z.object({ playlistId: z.number(), faixaId: z.number() }))
      .mutation(async ({ input, ctx }) => {
        const playlist = await db.getPlaylistById(input.playlistId);
        if (!playlist || playlist.userId !== ctx.user.id) {
          throw new TRPCError({ code: "NOT_FOUND", message: "Playlist não encontrada." });
        }
        await musicaDb.adicionarFaixaAPlaylist(input.playlistId, input.faixaId);
        return { success: true } as const;
      }),

    removerFaixa: protectedProcedure
      .input(z.object({ playlistId: z.number(), faixaId: z.number() }))
      .mutation(async ({ input, ctx }) => {
        const playlist = await db.getPlaylistById(input.playlistId);
        if (!playlist || playlist.userId !== ctx.user.id) {
          throw new TRPCError({ code: "NOT_FOUND", message: "Playlist não encontrada." });
        }
        await musicaDb.removerFaixaDaPlaylist(input.playlistId, input.faixaId);
        return { success: true } as const;
      }),
  }),
  }),
});

export type AppRouter = typeof appRouter;
