import { SkillLevel, Skill, Platform, PaymentType } from "@prisma/client";
import { TRPCError } from "@trpc/server";
import { z } from "zod";

import {
  createTRPCRouter,
  protectedProcedure,
  publicProcedure,
} from "~/server/api/trpc";

const emptyStringToUndefined = (val: string | undefined | null) => {
  if (val?.length === 0) {
    return undefined;
  }

  return val;
};

const AuditoryResourceSchema = z.object({
  icon: z.string().min(1).optional(),
  name: z.string().min(1),
  description: z.string().min(1),
  manufacturer: z.object({
    name: z.string().min(1),
    required: z.boolean().default(false),
    notice: z.string().nullable().transform(emptyStringToUndefined),
  }),
  ages: z.object({ min: z.number().int(), max: z.number().int() }).refine(
    (ages) => {
      return ages.min < ages.max;
    },
    {
      message: "Minimum supported age must be less than maximum supported age.",
    }
  ),
  skills: z.array(z.nativeEnum(Skill)),
  skill_levels: z.array(z.nativeEnum(SkillLevel)),
  payment_options: z.array(z.nativeEnum(PaymentType)),
  photo: z
    .object({
      name: z.string(),
      data: z.instanceof(Buffer),
    })
    .nullable(),
  platform_links: z
    .array(
      z.object({
        platform: z.nativeEnum(Platform),
        link: z.string().min(1),
      })
    )
    .default([]),
});

export const auditoryResourceRouter = createTRPCRouter({
  byId: publicProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ input, ctx }) => {
      try {
        const resource = await ctx.prisma.auditoryResource.findUnique({
          where: {
            id: input.id,
          },
        });

        if (!resource) {
          throw Error("not found");
        }

        return resource;
      } catch (e) {
        throw new TRPCError({
          code: "NOT_FOUND",
          message: "The resource you are looking for does not exist.",
          cause: e,
        });
      }
    }),

  getAll: publicProcedure.query(({ ctx }) => {
    return ctx.prisma.auditoryResource.findMany();
  }),

  create: protectedProcedure
    .input(AuditoryResourceSchema)
    .mutation(async ({ input, ctx }) => {
      return await ctx.prisma.auditoryResource.create({
        data: input,
      });
    }),

  update: protectedProcedure
    .input(AuditoryResourceSchema.partial().extend({ id: z.string() }))
    .mutation(async ({ input, ctx }) => {
      return await ctx.prisma.auditoryResource.update({
        where: {
          id: input.id,
        },
        data: Object.fromEntries(
          Object.entries(input).filter(([key, _value]) => {
            return key !== "id";
          })
        ),
      });
    }),

  search: publicProcedure
    .input(
      z.object({
        take: z.number().int(),
        skip: z.number().int(),
        ages: z
          .object({
            min: z.number().int(),
            max: z.number().int(),
          })
          .optional(),
        platforms: z.nativeEnum(Platform).array().optional(),
        skill_levels: z.nativeEnum(SkillLevel).array().optional(),
        skills: z.nativeEnum(Skill).array().optional(),
      })
    )
    .query(async ({ input, ctx }) => {
      const search = {
        ages: {
          is: {
            min: {
              lte: input.ages?.min,
            },
            max: {
              gte: input.ages?.max,
            },
          },
        },
        skill_levels: {
          hasEvery: input.skill_levels ?? [],
        },
        platform_links: {
          some: {
            platform: {
              in: input.platforms,
            },
          },
        },
        ...(input.skills ? { skills: { hasSome: input.skills } } : {}),
      };

      const [count, resources] = await ctx.prisma.$transaction([
        ctx.prisma.auditoryResource.count({
          where: search,
        }),
        ctx.prisma.auditoryResource.findMany({
          skip: input.skip,
          take: input.take,
          where: search,
        }),
      ]);

      // TODO: The issue here is the photo binary data can't be sent over tRPC which will cause the request to be unparsable by the client
      return {
        count,
        resources,
      };
    }),
});
