import { router, protectedProcedure } from "@/src/trpc/trpc-index"
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { ApiError } from "@/src/lib/api-error"
import { getAppUrl } from "@/src/lib/env-exporter"
// import redisClient from "@/src/lib/redis-client"
// import { getSlotSequenceKey } from "@/src/lib/redisKeyGetters"
import { scheduleStoreInitialization } from '@/src/stores/store-initializer'
import {
  getActiveSlotsWithProducts as getActiveSlotsWithProductsInDb,
  getActiveSlots as getActiveSlotsInDb,
  getSlotsAfterDate as getSlotsAfterDateInDb,
  getSlotByIdWithRelations as getSlotByIdWithRelationsInDb,
  createSlotWithRelations as createSlotWithRelationsInDb,
  updateSlotWithRelations as updateSlotWithRelationsInDb,
  deleteSlotById as deleteSlotByIdInDb,
  updateSlotCapacity as updateSlotCapacityInDb,
  getSlotDeliverySequence as getSlotDeliverySequenceInDb,
  updateSlotDeliverySequence as updateSlotDeliverySequenceInDb,
  updateSlotProducts as updateSlotProductsInDb,
  getSlotsProductIds as getSlotsProductIdsInDb,
  staleSlotsCleanup,
} from '@/src/dbService'
import type {
  AdminDeliverySequenceResult,
  AdminSlotResult,
  AdminSlotsResult,
  AdminSlotsListResult,
  AdminSlotCreateResult,
  AdminSlotUpdateResult,
  AdminSlotDeleteResult,
  AdminUpdateDeliverySequenceResult,
  AdminUpdateSlotCapacityResult,
  AdminSlotsProductIdsResult,
  AdminUpdateSlotProductsResult,
} from '@packages/shared'


interface CachedDeliverySequence {
  [userId: string]: number[];
}

const cachedSequenceSchema = z.record(z.string(), z.array(z.number()));

const createSlotSchema = z.object({
  deliveryTime: z.string(),
  freezeTime: z.string(),
  isActive: z.boolean().optional(),
  productIds: z.array(z.number()).optional(),
  vendorSnippets: z.array(z.object({
    name: z.string().min(1),
    productIds: z.array(z.number().int().positive()).min(1),
    validTill: z.string().optional(),
  })).optional(),
  groupIds: z.array(z.number()).optional(),
});

const getSlotByIdSchema = z.object({
  id: z.number(),
});

const updateSlotSchema = z.object({
  id: z.number(),
  deliveryTime: z.string(),
  freezeTime: z.string(),
  isActive: z.boolean().optional(),
  productIds: z.array(z.number()).optional(),
  vendorSnippets: z.array(z.object({
    name: z.string().min(1),
    productIds: z.array(z.number().int().positive()).min(1),
    validTill: z.string().optional(),
  })).optional(),
  groupIds: z.array(z.number()).optional(),
});

const deleteSlotSchema = z.object({
  id: z.number(),
});

const getDeliverySequenceSchema = z.object({
  id: z.string(),
});

const updateDeliverySequenceSchema = z.object({
  id: z.number(),
  // deliverySequence: z.array(z.number()),
  deliverySequence: z.any(),
});

export const slotsRouter = router({
  // Exact replica of GET /av/slots
  getAll: protectedProcedure.query(async ({ ctx }): Promise<AdminSlotsResult> => {
    if (!ctx.staffUser?.id) {
      throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
    }

    const slots = await getActiveSlotsWithProductsInDb(20)

    /*
    // Old implementation - direct DB queries:
    const slots = await db.query.deliverySlotInfo
      .findMany({
        where: eq(deliverySlotInfo.isActive, true),
        orderBy: desc(deliverySlotInfo.deliveryTime),
        with: {
          productSlots: {
            with: {
              product: {
                columns: {
                  id: true,
                  name: true,
                  images: true,
                },
              },
            },
          },
        },
      })
      .then((slots) =>
        slots.map((slot) => ({
          ...slot,
          deliverySequence: slot.deliverySequence as number[],
          products: slot.productSlots.map((ps) => ps.product),
        }))
      );
    */

    return {
      slots,
      count: slots.length,
    }
  }),

  // Exact replica of POST /av/products/slots/product-ids
  getSlotsProductIds: protectedProcedure
    .input(z.object({ slotIds: z.array(z.number()) }))
    .query(async ({ input, ctx }): Promise<AdminSlotsProductIdsResult> => {
      if (!ctx.staffUser?.id) {
        throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
      }

      const { slotIds } = input;

      if (!Array.isArray(slotIds)) {
        throw new TRPCError({
          code: "BAD_REQUEST",
          message: "slotIds must be an array",
        });
      }

      const result = await getSlotsProductIdsInDb(slotIds)

      /*
      // Old implementation - direct DB queries:
      if (slotIds.length === 0) {
        return {};
      }

      // Fetch all associations for the requested slots
      const associations = await db.query.productSlots.findMany({
        where: inArray(productSlots.slotId, slotIds),
        columns: {
          slotId: true,
          productId: true,
        },
      });

      // Group by slotId
      const result = associations.reduce((acc, assoc) => {
        if (!acc[assoc.slotId]) {
          acc[assoc.slotId] = [];
        }
        acc[assoc.slotId].push(assoc.productId);
        return acc;
      }, {} as Record<number, number[]>);

      // Ensure all requested slots have entries (even if empty)
      slotIds.forEach((slotId) => {
        if (!result[slotId]) {
          result[slotId] = [];
        }
      });

      return result;
      */

      return result
    }),

  // Exact replica of PUT /av/products/slots/:slotId/products
  updateSlotProducts: protectedProcedure
    .input(
      z.object({
        slotId: z.number(),
        productIds: z.array(z.number()),
      })
    )
    .mutation(async ({ input, ctx }): Promise<AdminUpdateSlotProductsResult> => {
      if (!ctx.staffUser?.id) {
        throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
      }

      const { slotId, productIds } = input;

      if (!Array.isArray(productIds)) {
        throw new TRPCError({
          code: "BAD_REQUEST",
          message: "productIds must be an array",
        });
      }

      const result = await updateSlotProductsInDb(String(slotId), productIds.map(String))

      /*
      // Old implementation - direct DB queries:
      // Get current associations
      const currentAssociations = await db.query.productSlots.findMany({
        where: eq(productSlots.slotId, slotId),
        columns: {
          productId: true,
        },
      });

      const currentProductIds = currentAssociations.map(
        (assoc) => assoc.productId
      );
      const newProductIds = productIds;

      // Find products to add and remove
      const productsToAdd = newProductIds.filter(
        (id) => !currentProductIds.includes(id)
      );
      const productsToRemove = currentProductIds.filter(
        (id) => !newProductIds.includes(id)
      );

      // Remove associations for products that are no longer selected
      if (productsToRemove.length > 0) {
        await db
          .delete(productSlots)
          .where(
            and(
              eq(productSlots.slotId, slotId),
              inArray(productSlots.productId, productsToRemove)
            )
          );
      }

      // Add associations for newly selected products
      if (productsToAdd.length > 0) {
        const newAssociations = productsToAdd.map((productId) => ({
          productId,
          slotId,
        }));

        await db.insert(productSlots).values(newAssociations);
      }

      // Reinitialize stores to reflect changes
      await scheduleStoreInitialization()

      return {
        message: "Slot products updated successfully",
        added: productsToAdd.length,
        removed: productsToRemove.length,
      };
      */

      await scheduleStoreInitialization()

      return {
        message: result.message,
        added: result.added,
        removed: result.removed,
      }
    }),

  createSlot: protectedProcedure
    .input(createSlotSchema)
    .mutation(async ({ input, ctx }): Promise<AdminSlotCreateResult> => {
      if (!ctx.staffUser?.id) {
        throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
      }

      const { deliveryTime, freezeTime, isActive, productIds, vendorSnippets: snippets, groupIds } = input;

      // Validate required fields
      if (!deliveryTime || !freezeTime) {
        throw new ApiError("Delivery time and orders close time are required", 400);
      }

      const result = await createSlotWithRelationsInDb({
        deliveryTime,
        freezeTime,
        isActive,
        productIds,
        vendorSnippets: snippets,
        groupIds,
      })

      /*
      // Old implementation - direct DB queries:
      const result = await db.transaction(async (tx) => {
        // Create slot
        const [newSlot] = await tx
          .insert(deliverySlotInfo)
          .values({
            deliveryTime: new Date(deliveryTime),
            freezeTime: new Date(freezeTime),
            isActive: isActive !== undefined ? isActive : true,
            groupIds: groupIds !== undefined ? groupIds : [],
          })
          .returning();

        // Insert product associations if provided
        if (productIds && productIds.length > 0) {
          const associations = productIds.map((productId) => ({
            productId,
            slotId: newSlot.id,
          }));
          await tx.insert(productSlots).values(associations);
        }

        // Create vendor snippets if provided
        let createdSnippets: any[] = [];
        if (snippets && snippets.length > 0) {
          for (const snippet of snippets) {
            // Validate products exist
            const products = await tx.query.productInfo.findMany({
              where: inArray(productInfo.id, snippet.productIds),
            });
            if (products.length !== snippet.productIds.length) {
              throw new ApiError(`One or more invalid product IDs in snippet "${snippet.name}"`, 400);
            }

            // Check if snippet name already exists
            const existingSnippet = await tx.query.vendorSnippets.findFirst({
              where: eq(vendorSnippets.snippetCode, snippet.name),
            });
            if (existingSnippet) {
              throw new ApiError(`Snippet name "${snippet.name}" already exists`, 400);
            }

            const [createdSnippet] = await tx.insert(vendorSnippets).values({
              snippetCode: snippet.name,
              slotId: newSlot.id,
              productIds: snippet.productIds,
              validTill: snippet.validTill ? new Date(snippet.validTill) : undefined,
            }).returning();

            createdSnippets.push(createdSnippet);
          }
        }

        return {
          slot: newSlot,
          createdSnippets,
          message: "Slot created successfully",
        };
      });
      */

      // Reinitialize stores to reflect changes (outside transaction)
      await scheduleStoreInitialization()

      // Fire and forget: cleanup stale product slot associations
      staleSlotsCleanup().catch((error) => {
        console.error('Failed to cleanup stale slots:', error)
      })

      return result
    }),

  getSlots: protectedProcedure.query(async ({ ctx }): Promise<AdminSlotsListResult> => {
    if (!ctx.staffUser?.id) {
      throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
    }

    const slots = await getActiveSlotsInDb()

    /*
    // Old implementation - direct DB queries:
    const slots = await db.query.deliverySlotInfo.findMany({
      where: eq(deliverySlotInfo.isActive, true),
    });
    */

    return {
      slots,
      count: slots.length,
    }
  }),

  getSlotById: protectedProcedure
    .input(getSlotByIdSchema)
    .query(async ({ input, ctx }): Promise<AdminSlotResult> => {
      if (!ctx.staffUser?.id) {
        throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
      }

      const { id } = input;

      const slot = await getSlotByIdWithRelationsInDb(id)

      /*
      // Old implementation - direct DB queries:
      const slot = await db.query.deliverySlotInfo.findFirst({
        where: eq(deliverySlotInfo.id, id),
        with: {
          productSlots: {
            with: {
              product: {
                columns: {
                  id: true,
                  name: true,
                  images: true,
                },
              },
            },
          },
          vendorSnippets: true,
        },
      });
      */

      if (!slot) {
        throw new ApiError('Slot not found', 404)
      }

      return {
        slot: {
          ...slot,
          vendorSnippets: slot.vendorSnippets.map(snippet => ({
            ...snippet,
            accessUrl: `${getAppUrl()}/vendor-order-list?id=${snippet.snippetCode}`,
          })),
        },
      }
    }),

  updateSlot: protectedProcedure
    .input(updateSlotSchema)
    .mutation(async ({ input, ctx }): Promise<AdminSlotUpdateResult> => {
      if (!ctx.staffUser?.id) {
        throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
      }
      try{
      const { id, deliveryTime, freezeTime, isActive, productIds, vendorSnippets: snippets, groupIds } = input;

      if (!deliveryTime || !freezeTime) {
        throw new ApiError("Delivery time and orders close time are required", 400);
      }

      const result = await updateSlotWithRelationsInDb({
        id,
        deliveryTime,
        freezeTime,
        isActive,
        productIds,
        vendorSnippets: snippets,
        groupIds,
      })

      /*
      // Old implementation - direct DB queries:
      // Filter groupIds to only include valid (existing) groups
      let validGroupIds = groupIds;
      if (groupIds && groupIds.length > 0) {
        const existingGroups = await db.query.productGroupInfo.findMany({
          where: inArray(productGroupInfo.id, groupIds),
          columns: { id: true },
        });
        validGroupIds = existingGroups.map(g => g.id);
      }

      const result = await db.transaction(async (tx) => {
        const [updatedSlot] = await tx
          .update(deliverySlotInfo)
          .set({
            deliveryTime: new Date(deliveryTime),
            freezeTime: new Date(freezeTime),
            isActive: isActive !== undefined ? isActive : true,
            groupIds: validGroupIds !== undefined ? validGroupIds : [],
          })
          .where(eq(deliverySlotInfo.id, id))
          .returning();

        if (!updatedSlot) {
          throw new ApiError("Slot not found", 404);
        }

        // Update product associations
        if (productIds !== undefined) {
          // Delete existing associations
          await tx.delete(productSlots).where(eq(productSlots.slotId, id));

          // Insert new associations
          if (productIds.length > 0) {
            const associations = productIds.map((productId) => ({
              productId,
              slotId: id,
            }));
            await tx.insert(productSlots).values(associations);
          }
        }

        // Create vendor snippets if provided
        let createdSnippets: any[] = [];
        if (snippets && snippets.length > 0) {
          for (const snippet of snippets) {
            // Validate products exist
            const products = await tx.query.productInfo.findMany({
              where: inArray(productInfo.id, snippet.productIds),
            });
            if (products.length !== snippet.productIds.length) {
              throw new ApiError(`One or more invalid product IDs in snippet "${snippet.name}"`, 400);
            }

            // Check if snippet name already exists
            const existingSnippet = await tx.query.vendorSnippets.findFirst({
              where: eq(vendorSnippets.snippetCode, snippet.name),
            });
            if (existingSnippet) {
              throw new ApiError(`Snippet name "${snippet.name}" already exists`, 400);
            }
            
            const [createdSnippet] = await tx.insert(vendorSnippets).values({
              snippetCode: snippet.name,
              slotId: id,
              productIds: snippet.productIds,
              validTill: snippet.validTill ? new Date(snippet.validTill) : undefined,

            }).returning();

            createdSnippets.push(createdSnippet);
          }
        }

        return {
          slot: updatedSlot,
          createdSnippets,
          message: "Slot updated successfully",
        };
      });
      */

      if (!result) {
        throw new ApiError('Slot not found', 404)
      }

      // Reinitialize stores to reflect changes (outside transaction)
      await scheduleStoreInitialization()

      return result
    }
    catch(e) {
      console.log(e)
      throw new ApiError("Unable to Update Slot");
    }
    }),

  deleteSlot: protectedProcedure
    .input(deleteSlotSchema)
    .mutation(async ({ input, ctx }): Promise<AdminSlotDeleteResult> => {
      if (!ctx.staffUser?.id) {
        throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
      }

      const { id } = input;

      const deletedSlot = await deleteSlotByIdInDb(id)

      /*
      // Old implementation - direct DB queries:
      const [deletedSlot] = await db
        .update(deliverySlotInfo)
        .set({ isActive: false })
        .where(eq(deliverySlotInfo.id, id))
        .returning();

      if (!deletedSlot) {
        throw new ApiError("Slot not found", 404);
      }
      */

      if (!deletedSlot) {
        throw new ApiError('Slot not found', 404)
      }

      // Reinitialize stores to reflect changes
      await scheduleStoreInitialization()

      return {
        message: 'Slot deleted successfully',
      }
    }),

  getDeliverySequence: protectedProcedure
    .input(getDeliverySequenceSchema)
    .query(async ({ input, ctx }): Promise<AdminDeliverySequenceResult> => {

      const { id } = input;
      const slotId = parseInt(id);
      // const cacheKey = getSlotSequenceKey(slotId);

      // try {
      //   const cached = await redisClient.get(cacheKey);
      //   if (cached) {
      //     const parsed = JSON.parse(cached);
      //     const validated = cachedSequenceSchema.parse(parsed);
      //     console.log('sending cached response')
      //     
      //     return { deliverySequence: validated };
      //   }
      // } catch (error) {
      //   console.warn('Redis cache read/validation failed, falling back to DB:', error);
      //   // Continue to DB fallback
      // }

      // Fallback to DB
      const slot = await getSlotDeliverySequenceInDb(slotId)

      /*
      // Old implementation - direct DB queries:
      const slot = await db.query.deliverySlotInfo.findFirst({
        where: eq(deliverySlotInfo.id, slotId),
      });

      if (!slot) {
        throw new ApiError("Slot not found", 404);
      }

      const sequence = cachedSequenceSchema.parse(slot.deliverySequence || {});
      */

      if (!slot) {
        throw new ApiError('Slot not found', 404)
      }

      const sequence = (slot.deliverySequence || {}) as CachedDeliverySequence;

      // Cache the validated result
      // try {
      //   const validated = cachedSequenceSchema.parse(sequence);
      //   await redisClient.set(cacheKey, JSON.stringify(validated), 3600);
      // } catch (cacheError) {
      //   console.warn('Redis cache write failed:', cacheError);
      // }

      return { deliverySequence: sequence }
    }),

  updateDeliverySequence: protectedProcedure
    .input(updateDeliverySequenceSchema)
    .mutation(async ({ input, ctx }): Promise<AdminUpdateDeliverySequenceResult> => {
      if (!ctx.staffUser?.id) {
        throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
      }

      const { id, deliverySequence } = input;

      const updatedSlot = await updateSlotDeliverySequenceInDb(id, deliverySequence)

      /*
      // Old implementation - direct DB queries:
      const [updatedSlot] = await db
        .update(deliverySlotInfo)
        .set({ deliverySequence })
        .where(eq(deliverySlotInfo.id, id))
        .returning({
          id: deliverySlotInfo.id,
          deliverySequence: deliverySlotInfo.deliverySequence,
        });

      if (!updatedSlot) {
        throw new ApiError("Slot not found", 404);
      }
      */

      if (!updatedSlot) {
        throw new ApiError('Slot not found', 404)
      }

      // Cache the updated sequence
      // const cacheKey = getSlotSequenceKey(id);
      // try {
      //   const validated = cachedSequenceSchema.parse(deliverySequence);
      //   await redisClient.set(cacheKey, JSON.stringify(validated), 3600);
      // } catch (cacheError) {
      //   console.warn('Redis cache write failed:', cacheError);
      // }

      return {
        slot: updatedSlot,
        message: 'Delivery sequence updated successfully',
      }
    }),

  updateSlotCapacity: protectedProcedure
    .input(z.object({
      slotId: z.number(),
      isCapacityFull: z.boolean(),
    }))
    .mutation(async ({ input, ctx }): Promise<AdminUpdateSlotCapacityResult> => {
      if (!ctx.staffUser?.id) {
        throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
      }

      const { slotId, isCapacityFull } = input;

      const result = await updateSlotCapacityInDb(slotId, isCapacityFull)

      /*
      // Old implementation - direct DB queries:
      const [updatedSlot] = await db
        .update(deliverySlotInfo)
        .set({ isCapacityFull })
        .where(eq(deliverySlotInfo.id, slotId))
        .returning();

      if (!updatedSlot) {
        throw new ApiError("Slot not found", 404);
      }

      // Reinitialize stores to reflect changes
      await scheduleStoreInitialization()

      return {
        success: true,
        slot: updatedSlot,
        message: `Slot ${isCapacityFull ? 'marked as full capacity' : 'capacity reset'}`,
      };
      */

      if (!result) {
        throw new ApiError('Slot not found', 404)
      }

      await scheduleStoreInitialization()

      return result
    }),
});
