import { z } from "zod";
import { eq } from "drizzle-orm";
import { createTRPCRouter, protectedProcedure } from "../trpc";
import { clients, invoices } from "~/server/db/schema";
import { TRPCError } from "@trpc/server";

const createClientSchema = z.object({
  name: z.string().min(1, "Name is required").max(255, "Name is too long"),
  email: z.string().email("Invalid email").optional().or(z.literal("")),
  phone: z
    .string()
    .max(50, "Phone number is too long")
    .optional()
    .or(z.literal("")),
  addressLine1: z
    .string()
    .max(255, "Address is too long")
    .optional()
    .or(z.literal("")),
  addressLine2: z
    .string()
    .max(255, "Address is too long")
    .optional()
    .or(z.literal("")),
  city: z
    .string()
    .max(100, "City name is too long")
    .optional()
    .or(z.literal("")),
  state: z
    .string()
    .max(50, "State name is too long")
    .optional()
    .or(z.literal("")),
  postalCode: z
    .string()
    .max(20, "Postal code is too long")
    .optional()
    .or(z.literal("")),
  country: z
    .string()
    .max(100, "Country name is too long")
    .optional()
    .or(z.literal("")),
  defaultHourlyRate: z.number().min(0, "Rate must be positive").default(100),
});

const updateClientSchema = createClientSchema.partial().extend({
  id: z.string(),
});

export const clientsRouter = createTRPCRouter({
  getAll: protectedProcedure.query(async ({ ctx }) => {
    try {
      return await ctx.db.query.clients.findMany({
        where: eq(clients.createdById, ctx.session.user.id),
        orderBy: (clients, { desc }) => [desc(clients.createdAt)],
      });
    } catch (error) {
      throw new TRPCError({
        code: "INTERNAL_SERVER_ERROR",
        message: "Failed to fetch clients",
        cause: error,
      });
    }
  }),

  getById: protectedProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      try {
        const client = await ctx.db.query.clients.findFirst({
          where: eq(clients.id, input.id),
          with: {
            invoices: {
              orderBy: (invoices, { desc }) => [desc(invoices.createdAt)],
            },
          },
        });

        if (!client) {
          throw new TRPCError({
            code: "NOT_FOUND",
            message: "Client not found",
          });
        }

        // Check if user owns this client
        if (client.createdById !== ctx.session.user.id) {
          throw new TRPCError({
            code: "FORBIDDEN",
            message: "You don't have permission to view this client",
          });
        }

        return client;
      } catch (error) {
        if (error instanceof TRPCError) throw error;
        throw new TRPCError({
          code: "INTERNAL_SERVER_ERROR",
          message: "Failed to fetch client",
          cause: error,
        });
      }
    }),

  create: protectedProcedure
    .input(createClientSchema)
    .mutation(async ({ ctx, input }) => {
      try {
        // Clean up empty strings to null, but preserve required fields
        const cleanInput = Object.fromEntries(
          Object.entries(input).map(([key, value]) => [
            key,
            value === "" ? null : value,
          ]),
        );

        const [client] = await ctx.db
          .insert(clients)
          .values({
            name: input.name, // Ensure name is included
            ...cleanInput,
            createdById: ctx.session.user.id,
          })
          .returning();

        if (!client) {
          throw new TRPCError({
            code: "INTERNAL_SERVER_ERROR",
            message: "Failed to create client",
          });
        }

        return client;
      } catch (error) {
        if (error instanceof TRPCError) throw error;
        throw new TRPCError({
          code: "INTERNAL_SERVER_ERROR",
          message: "Failed to create client",
          cause: error,
        });
      }
    }),

  update: protectedProcedure
    .input(updateClientSchema)
    .mutation(async ({ ctx, input }) => {
      try {
        const { id, ...data } = input;

        // Verify client exists and belongs to user
        const existingClient = await ctx.db.query.clients.findFirst({
          where: eq(clients.id, id),
        });

        if (!existingClient) {
          throw new TRPCError({
            code: "NOT_FOUND",
            message: "Client not found",
          });
        }

        if (existingClient.createdById !== ctx.session.user.id) {
          throw new TRPCError({
            code: "FORBIDDEN",
            message: "You don't have permission to update this client",
          });
        }

        // Clean up empty strings to null
        const cleanData = Object.fromEntries(
          Object.entries(data).map(([key, value]) => [
            key,
            value === "" ? null : value,
          ]),
        );

        const [updatedClient] = await ctx.db
          .update(clients)
          .set({
            ...cleanData,
            updatedAt: new Date(),
          })
          .where(eq(clients.id, id))
          .returning();

        if (!updatedClient) {
          throw new TRPCError({
            code: "INTERNAL_SERVER_ERROR",
            message: "Failed to update client",
          });
        }

        return updatedClient;
      } catch (error) {
        if (error instanceof TRPCError) throw error;
        throw new TRPCError({
          code: "INTERNAL_SERVER_ERROR",
          message: "Failed to update client",
          cause: error,
        });
      }
    }),

  delete: protectedProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      try {
        // Verify client exists and belongs to user
        const client = await ctx.db.query.clients.findFirst({
          where: eq(clients.id, input.id),
        });

        if (!client) {
          throw new TRPCError({
            code: "NOT_FOUND",
            message: "Client not found",
          });
        }

        if (client.createdById !== ctx.session.user.id) {
          throw new TRPCError({
            code: "FORBIDDEN",
            message: "You don't have permission to delete this client",
          });
        }

        // Check if client has invoices
        const clientInvoices = await ctx.db.query.invoices.findMany({
          where: eq(invoices.clientId, input.id),
        });

        if (clientInvoices.length > 0) {
          throw new TRPCError({
            code: "BAD_REQUEST",
            message:
              "Cannot delete client with existing invoices. Please delete or reassign the invoices first.",
          });
        }

        await ctx.db.delete(clients).where(eq(clients.id, input.id));

        return { success: true };
      } catch (error) {
        if (error instanceof TRPCError) throw error;
        throw new TRPCError({
          code: "INTERNAL_SERVER_ERROR",
          message: "Failed to delete client",
          cause: error,
        });
      }
    }),
});
