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

const invoiceItemSchema = z.object({
  date: z.date(),
  description: z.string().min(1, "Description is required"),
  hours: z.number().min(0, "Hours must be positive"),
  rate: z.number().min(0, "Rate must be positive"),
});

const createInvoiceSchema = z.object({
  invoiceNumber: z.string().min(1, "Invoice number is required"),
  businessId: z.string().min(1, "Business is required").optional(),
  clientId: z.string().min(1, "Client is required"),
  issueDate: z.date(),
  dueDate: z.date(),
  status: z.enum(["draft", "sent", "paid", "overdue"]).default("draft"),
  notes: z.string().optional(),
  taxRate: z.number().min(0).max(100).default(0),
  items: z.array(invoiceItemSchema).min(1, "At least one item is required"),
});

const updateInvoiceSchema = createInvoiceSchema.partial().extend({
  id: z.string(),
  clientId: z.string().min(1, "Client is required"), // Keep clientId required for updates
});

const updateStatusSchema = z.object({
  id: z.string(),
  status: z.enum(["draft", "sent", "paid", "overdue"]),
});

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

  getCurrentOpen: protectedProcedure.query(async ({ ctx }) => {
    try {
      // Get the most recent draft invoice
      const currentInvoice = await ctx.db.query.invoices.findFirst({
        where: eq(invoices.createdById, ctx.session.user.id),
        with: {
          business: true,
          client: true,
          items: {
            orderBy: (items, { asc }) => [asc(items.position)],
          },
        },
        orderBy: (invoices, { desc }) => [desc(invoices.createdAt)],
      });

      // Return null if no draft invoice exists
      if (!currentInvoice || currentInvoice.status !== "draft") {
        return null;
      }

      return currentInvoice;
    } catch (error) {
      throw new TRPCError({
        code: "INTERNAL_SERVER_ERROR",
        message: "Failed to fetch current open invoice",
        cause: error,
      });
    }
  }),

  getById: protectedProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      try {
        const invoice = await ctx.db.query.invoices.findFirst({
          where: eq(invoices.id, input.id),
          with: {
            business: true,
            client: true,
            items: {
              orderBy: (items, { asc }) => [asc(items.position)],
            },
          },
        });

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

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

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

  create: protectedProcedure
    .input(createInvoiceSchema)
    .mutation(async ({ ctx, input }) => {
      try {
        const { items, ...invoiceData } = input;

        // Verify business exists and belongs to user (if provided)
        if (invoiceData.businessId) {
          const business = await ctx.db.query.businesses.findFirst({
            where: eq(businesses.id, invoiceData.businessId),
          });

          if (!business) {
            throw new TRPCError({
              code: "BAD_REQUEST",
              message: "Business not found",
            });
          }

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

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

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

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

        // Calculate subtotal and tax
        const subtotal = items.reduce(
          (sum, item) => sum + item.hours * item.rate,
          0,
        );
        const taxAmount = (subtotal * invoiceData.taxRate) / 100;
        const totalAmount = subtotal + taxAmount;

        // Create invoice
        const [invoice] = await ctx.db
          .insert(invoices)
          .values({
            ...invoiceData,
            totalAmount,
            createdById: ctx.session.user.id,
          })
          .returning();

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

        // Create invoice items
        const itemsToInsert = items.map((item, idx) => ({
          ...item,
          invoiceId: invoice.id,
          amount: item.hours * item.rate,
          position: idx,
        }));

        await ctx.db.insert(invoiceItems).values(itemsToInsert);

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

  update: protectedProcedure
    .input(updateInvoiceSchema)
    .mutation(async ({ ctx, input }) => {
      try {
        const { id, items, ...invoiceData } = input;

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

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

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

        // If business is being updated, verify it belongs to user
        if (invoiceData.businessId) {
          const business = await ctx.db.query.businesses.findFirst({
            where: eq(businesses.id, invoiceData.businessId),
          });

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

        // If client is being updated, verify it belongs to user
        if (invoiceData.clientId) {
          const client = await ctx.db.query.clients.findFirst({
            where: eq(clients.id, invoiceData.clientId),
          });

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

        if (items) {
          // Calculate subtotal and tax
          const subtotal = items.reduce(
            (sum, item) => sum + item.hours * item.rate,
            0,
          );
          const taxAmount =
            (subtotal * (invoiceData.taxRate ?? existingInvoice.taxRate)) / 100;
          const totalAmount = subtotal + taxAmount;

          // Update invoice
          await ctx.db
            .update(invoices)
            .set({
              ...invoiceData,
              totalAmount,
              updatedAt: new Date(),
            })
            .where(eq(invoices.id, id));

          // Delete existing items and create new ones
          await ctx.db
            .delete(invoiceItems)
            .where(eq(invoiceItems.invoiceId, id));

          const itemsToInsert = items.map((item, idx) => ({
            ...item,
            invoiceId: id,
            amount: item.hours * item.rate,
            position: idx,
          }));

          await ctx.db.insert(invoiceItems).values(itemsToInsert);
        } else {
          // Update invoice without items
          await ctx.db
            .update(invoices)
            .set({
              ...invoiceData,
              updatedAt: new Date(),
            })
            .where(eq(invoices.id, id));
        }

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

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

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

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

        // Items will be deleted automatically due to cascade
        await ctx.db.delete(invoices).where(eq(invoices.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 invoice",
          cause: error,
        });
      }
    }),

  updateStatus: protectedProcedure
    .input(updateStatusSchema)
    .mutation(async ({ ctx, input }) => {
      try {
        // Verify invoice exists and belongs to user
        const invoice = await ctx.db.query.invoices.findFirst({
          where: eq(invoices.id, input.id),
        });

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

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

        await ctx.db
          .update(invoices)
          .set({
            status: input.status,
            updatedAt: new Date(),
          })
          .where(eq(invoices.id, input.id));

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