import { db } from '../db/db_index'
import {
  productInfo,
  units,
  specialDeals,
  deliverySlotInfo,
  productTags,
  productReviews,
  productGroupInfo,
  productGroupMembership,
  productTagInfo,
  users,
  storeInfo,
} from '../db/schema'
import { and, desc, eq, inArray, sql } from 'drizzle-orm'
import type { InferInsertModel, InferSelectModel } from 'drizzle-orm'
import type {
  AdminProduct,
  AdminProductGroupInfo,
  AdminProductTagInfo,
  AdminProductTagWithProducts,
  AdminProductReview,
  AdminProductWithDetails,
  AdminProductWithRelations,
  AdminSpecialDeal,
  AdminUnit,
  AdminUpdateSlotProductsResult,
  Store,
} from '@packages/shared'

type ProductRow = InferSelectModel<typeof productInfo>
type UnitRow = InferSelectModel<typeof units>
type StoreRow = InferSelectModel<typeof storeInfo>
type SpecialDealRow = InferSelectModel<typeof specialDeals>
type ProductTagInfoRow = InferSelectModel<typeof productTagInfo>
type ProductTagRow = InferSelectModel<typeof productTags>
type ProductGroupRow = InferSelectModel<typeof productGroupInfo>
type ProductGroupMembershipRow = InferSelectModel<typeof productGroupMembership>
type ProductReviewRow = InferSelectModel<typeof productReviews>

const getStringArray = (value: unknown): string[] | null => {
  if (!Array.isArray(value)) return null
  return value.map((item) => String(item))
}

const mapUnit = (unit: UnitRow): AdminUnit => ({
  id: unit.id,
  shortNotation: unit.shortNotation,
  fullName: unit.fullName,
})

const mapStore = (store: StoreRow): Store => ({
  id: store.id,
  name: store.name,
  description: store.description,
  imageUrl: store.imageUrl,
  owner: store.owner,
  createdAt: store.createdAt,
  // updatedAt: store.createdAt,
})

const mapProduct = (product: ProductRow): AdminProduct => ({
  id: product.id,
  name: product.name,
  shortDescription: product.shortDescription ?? null,
  longDescription: product.longDescription ?? null,
  unitId: product.unitId,
  price: product.price.toString(),
  marketPrice: product.marketPrice ? product.marketPrice.toString() : null,
  images: getStringArray(product.images),
  imageKeys: getStringArray(product.images),
  isOutOfStock: product.isOutOfStock,
  isSuspended: product.isSuspended,
  isFlashAvailable: product.isFlashAvailable,
  flashPrice: product.flashPrice ? product.flashPrice.toString() : null,
  createdAt: product.createdAt,
  incrementStep: product.incrementStep,
  productQuantity: product.productQuantity,
  storeId: product.storeId,
})

const mapSpecialDeal = (deal: SpecialDealRow): AdminSpecialDeal => ({
  id: deal.id,
  productId: deal.productId,
  quantity: deal.quantity.toString(),
  price: deal.price.toString(),
  validTill: deal.validTill,
})

const mapTagInfo = (tag: ProductTagInfoRow): AdminProductTagInfo => ({
  id: tag.id,
  tagName: tag.tagName,
  tagDescription: tag.tagDescription ?? null,
  imageUrl: tag.imageUrl ?? null,
  isDashboardTag: tag.isDashboardTag,
  relatedStores: tag.relatedStores,
  createdAt: tag.createdAt,
})

export async function getAllProducts(): Promise<AdminProductWithRelations[]> {
  const products = await db.query.productInfo.findMany({
    orderBy: productInfo.name,
    with: {
      unit: true,
      store: true,
    },
  })

  return products.map((product) => ({
    ...mapProduct(product),
    unit: mapUnit(product.unit),
    store: product.store ? mapStore(product.store) : null,
  }))
}

export async function getProductById(id: number): Promise<AdminProductWithDetails | null> {
  const product = await db.query.productInfo.findFirst({
    where: eq(productInfo.id, id),
    with: {
      unit: true,
    },
  })

  if (!product) {
    return null
  }

  const deals = await db.query.specialDeals.findMany({
    where: eq(specialDeals.productId, id),
    orderBy: specialDeals.quantity,
  })

  const productTagsData = await db.query.productTags.findMany({
    where: eq(productTags.productId, id),
    with: {
      tag: true,
    },
  })

  return {
    ...mapProduct(product),
    unit: mapUnit(product.unit),
    deals: deals.map(mapSpecialDeal),
    tags: productTagsData.map((tag) => mapTagInfo(tag.tag)),
  }
}

export async function deleteProduct(id: number): Promise<AdminProduct | null> {
  const [deletedProduct] = await db
    .delete(productInfo)
    .where(eq(productInfo.id, id))
    .returning()

  if (!deletedProduct) {
    return null
  }

  return mapProduct(deletedProduct)
}

type ProductInfoInsert = InferInsertModel<typeof productInfo>
type ProductInfoUpdate = Partial<ProductInfoInsert>

export async function createProduct(input: ProductInfoInsert): Promise<AdminProduct> {
  const [product] = await db.insert(productInfo).values(input).returning()
  return mapProduct(product)
}

export async function updateProduct(id: number, updates: ProductInfoUpdate): Promise<AdminProduct | null> {
  const [product] = await db.update(productInfo)
    .set(updates)
    .where(eq(productInfo.id, id))
    .returning()
  if (!product) {
    return null
  }

  return mapProduct(product)
}

export async function toggleProductOutOfStock(id: number): Promise<AdminProduct | null> {
  const product = await db.query.productInfo.findFirst({
    where: eq(productInfo.id, id),
  })

  if (!product) {
    return null
  }

  const [updatedProduct] = await db
    .update(productInfo)
    .set({
      isOutOfStock: !product.isOutOfStock,
    })
    .where(eq(productInfo.id, id))
    .returning()

  if (!updatedProduct) {
    return null
  }

  return mapProduct(updatedProduct)
}

export async function updateSlotProducts(slotId: string, productIds: string[]): Promise<AdminUpdateSlotProductsResult> {
  const slot = await db.query.deliverySlotInfo.findFirst({
    where: eq(deliverySlotInfo.id, parseInt(slotId)),
  })

  if (!slot) {
    throw new Error(`Slot ${slotId} not found`)
  }

  const currentProductIds = slot.productIds || []
  const newProductIds = productIds.map((id) => parseInt(id))

  // Simply update the productIds array
  await db.update(deliverySlotInfo)
    .set({ productIds: newProductIds })
    .where(eq(deliverySlotInfo.id, parseInt(slotId)))

  const productsToAdd = newProductIds.filter((id) => !currentProductIds.includes(id))
  const productsToRemove = currentProductIds.filter((id) => !newProductIds.includes(id))

  return {
    message: 'Slot products updated successfully',
    added: productsToAdd.length,
    removed: productsToRemove.length,
  }
}

export async function getSlotProductIds(slotId: string): Promise<number[]> {
  const slot = await db.query.deliverySlotInfo.findFirst({
    where: eq(deliverySlotInfo.id, parseInt(slotId)),
  })

  return slot?.productIds || []
}

export async function getAllUnits(): Promise<AdminUnit[]> {
  const allUnits = await db.query.units.findMany({
    orderBy: units.shortNotation,
  })

  return allUnits.map(mapUnit)
}

export async function getAllProductTags(): Promise<AdminProductTagWithProducts[]> {
  const tags = await db.query.productTagInfo.findMany({
    with: {
      products: {
        with: {
          product: true,
        },
      },
    },
  })

  return tags.map((tag) => ({
    ...mapTagInfo(tag),
    products: tag.products.map((assignment) => ({
      productId: assignment.productId,
      tagId: assignment.tagId,
      assignedAt: assignment.assignedAt,
      product: mapProduct(assignment.product),
    })),
  }))
}

export async function getAllProductTagInfos(): Promise<AdminProductTagInfo[]> {
  const tags = await db.query.productTagInfo.findMany({
    orderBy: productTagInfo.tagName,
  })

  return tags.map(mapTagInfo)
}

export async function getProductTagInfoById(tagId: number): Promise<AdminProductTagInfo | null> {
  const tag = await db.query.productTagInfo.findFirst({
    where: eq(productTagInfo.id, tagId),
  })

  if (!tag) {
    return null
  }

  return mapTagInfo(tag)
}

export async function getSlotsProductIds(slotIds: number[]): Promise<Record<number, number[]>> {
  if (slotIds.length === 0) {
    return {}
  }

  const slots = await db.query.deliverySlotInfo.findMany({
    where: inArray(deliverySlotInfo.id, slotIds),
  })

  const result: Record<number, number[]> = {}
  for (const slot of slots) {
    result[slot.id] = slot.productIds || []
  }

  slotIds.forEach((slotId) => {
    if (!result[slotId]) {
      result[slotId] = []
    }
  })

  return result
}

export async function getProductReviews(productId: number, limit: number, offset: number) {
  const reviews = await db
    .select({
      id: productReviews.id,
      reviewBody: productReviews.reviewBody,
      ratings: productReviews.ratings,
      imageUrls: productReviews.imageUrls,
      reviewTime: productReviews.reviewTime,
      adminResponse: productReviews.adminResponse,
      adminResponseImages: productReviews.adminResponseImages,
      userName: users.name,
    })
    .from(productReviews)
    .innerJoin(users, eq(productReviews.userId, users.id))
    .where(eq(productReviews.productId, productId))
    .orderBy(desc(productReviews.reviewTime))
    .limit(limit)
    .offset(offset)

  const totalCountResult = await db
    .select({ count: sql`count(*)` })
    .from(productReviews)
    .where(eq(productReviews.productId, productId))

  const totalCount = Number(totalCountResult[0].count)

  const mappedReviews: AdminProductReview[] = reviews.map((review) => ({
    id: review.id,
    reviewBody: review.reviewBody,
    ratings: review.ratings,
    imageUrls: review.imageUrls,
    reviewTime: review.reviewTime,
    adminResponse: review.adminResponse ?? null,
    adminResponseImages: review.adminResponseImages,
    userName: review.userName ?? null,
  }))

  return {
    reviews: mappedReviews,
    totalCount,
  }
}

export async function respondToReview(
  reviewId: number,
  adminResponse: string | undefined,
  adminResponseImages: string[]
): Promise<AdminProductReview | null> {
  const [updatedReview] = await db
    .update(productReviews)
    .set({
      adminResponse,
      adminResponseImages,
    })
    .where(eq(productReviews.id, reviewId))
    .returning()

  if (!updatedReview) {
    return null
  }

  return {
    id: updatedReview.id,
    reviewBody: updatedReview.reviewBody,
    ratings: updatedReview.ratings,
    imageUrls: updatedReview.imageUrls,
    reviewTime: updatedReview.reviewTime,
    adminResponse: updatedReview.adminResponse ?? null,
    adminResponseImages: updatedReview.adminResponseImages,
    userName: null,
  }
}

export async function getAllProductGroups() {
  const groups = await db.query.productGroupInfo.findMany({
    with: {
      memberships: {
        with: {
          product: true,
        },
      },
    },
    orderBy: desc(productGroupInfo.createdAt),
  })

  return groups.map((group) => ({
    id: group.id,
    groupName: group.groupName,
    description: group.description ?? null,
    createdAt: group.createdAt,
    products: group.memberships.map((membership) => mapProduct(membership.product)),
    productCount: group.memberships.length,
    memberships: group.memberships
  }))
}

export async function createProductGroup(
  groupName: string,
  description: string | undefined,
  productIds: number[]
): Promise<AdminProductGroupInfo> {
  const [newGroup] = await db
    .insert(productGroupInfo)
    .values({
      groupName,
      description,
    })
    .returning()

  if (productIds.length > 0) {
    const memberships = productIds.map((productId) => ({
      productId,
      groupId: newGroup.id,
    }))

    await db.insert(productGroupMembership).values(memberships)
  }

  return {
    id: newGroup.id,
    groupName: newGroup.groupName,
    description: newGroup.description ?? null,
    createdAt: newGroup.createdAt,
  }
}

export async function updateProductGroup(
  id: number,
  groupName: string | undefined,
  description: string | undefined,
  productIds: number[] | undefined
): Promise<AdminProductGroupInfo | null> {
  const updateData: Partial<{
    groupName: string
    description: string | null
  }> = {}

  if (groupName !== undefined) updateData.groupName = groupName
  if (description !== undefined) updateData.description = description

  const [updatedGroup] = await db
    .update(productGroupInfo)
    .set(updateData)
    .where(eq(productGroupInfo.id, id))
    .returning()

  if (!updatedGroup) {
    return null
  }

  if (productIds !== undefined) {
    await db.delete(productGroupMembership).where(eq(productGroupMembership.groupId, id))

    if (productIds.length > 0) {
      const memberships = productIds.map((productId) => ({
        productId,
        groupId: id,
      }))

      await db.insert(productGroupMembership).values(memberships)
    }
  }

  return {
    id: updatedGroup.id,
    groupName: updatedGroup.groupName,
    description: updatedGroup.description ?? null,
    createdAt: updatedGroup.createdAt,
  }
}

export async function deleteProductGroup(id: number): Promise<AdminProductGroupInfo | null> {
  await db.delete(productGroupMembership).where(eq(productGroupMembership.groupId, id))

  const [deletedGroup] = await db
    .delete(productGroupInfo)
    .where(eq(productGroupInfo.id, id))
    .returning()

  if (!deletedGroup) {
    return null
  }

  return {
    id: deletedGroup.id,
    groupName: deletedGroup.groupName,
    description: deletedGroup.description ?? null,
    createdAt: deletedGroup.createdAt,
  }
}

export async function addProductToGroup(groupId: number, productId: number): Promise<void> {
  await db.insert(productGroupMembership).values({ groupId, productId })
}

export async function removeProductFromGroup(groupId: number, productId: number): Promise<void> {
  await db.delete(productGroupMembership)
    .where(and(
      eq(productGroupMembership.groupId, groupId),
      eq(productGroupMembership.productId, productId)
    ))
}

export async function updateProductPrices(updates: Array<{
  productId: number
  price?: number
  marketPrice?: number | null
  flashPrice?: number | null
  isFlashAvailable?: boolean
}>) {
  if (updates.length === 0) {
    return { updatedCount: 0, invalidIds: [] }
  }

  const productIds = updates.map((update) => update.productId)
  const existingProducts = await db.query.productInfo.findMany({
    where: inArray(productInfo.id, productIds),
    columns: { id: true },
  })

  const existingIds = new Set(existingProducts.map((product) => product.id))
  const invalidIds = productIds.filter((id) => !existingIds.has(id))

  if (invalidIds.length > 0) {
    return { updatedCount: 0, invalidIds }
  }

  const updatePromises = updates.map((update) => {
    const { productId, price, marketPrice, flashPrice, isFlashAvailable } = update
    const updateData: Partial<Pick<ProductInfoInsert, 'price' | 'marketPrice' | 'flashPrice' | 'isFlashAvailable'>> = {}

    if (price !== undefined) updateData.price = price.toString()
    if (marketPrice !== undefined) updateData.marketPrice = marketPrice === null ? null : marketPrice.toString()
    if (flashPrice !== undefined) updateData.flashPrice = flashPrice === null ? null : flashPrice.toString()
    if (isFlashAvailable !== undefined) updateData.isFlashAvailable = isFlashAvailable

    return db
      .update(productInfo)
      .set(updateData)
      .where(eq(productInfo.id, productId))
  })

  await Promise.all(updatePromises)

  return { updatedCount: updates.length, invalidIds: [] }
}

// ============================================================================
// Product Tag CRUD Operations
// ============================================================================

export interface CreateProductTagInput {
  tagName: string
  tagDescription?: string | null
  imageUrl?: string | null
  isDashboardTag?: boolean
  relatedStores?: number[]
}

export async function createProductTag(input: CreateProductTagInput): Promise<AdminProductTagWithProducts> {
  const [tag] = await db.insert(productTagInfo).values({
    tagName: input.tagName,
    tagDescription: input.tagDescription || null,
    imageUrl: input.imageUrl || null,
    isDashboardTag: input.isDashboardTag || false,
    relatedStores: input.relatedStores || [],
  }).returning()

  return {
    ...mapTagInfo(tag),
    products: [],
  }
}

export async function getProductTagById(tagId: number): Promise<AdminProductTagWithProducts | null> {
  const tag = await db.query.productTagInfo.findFirst({
    where: eq(productTagInfo.id, tagId),
    with: {
      products: {
        with: {
          product: true,
        },
      },
    },
  })

  if (!tag) {
    return null
  }

  return {
    ...mapTagInfo(tag),
    products: tag.products.map((assignment) => ({
      productId: assignment.productId,
      tagId: assignment.tagId,
      assignedAt: assignment.assignedAt,
      product: mapProduct(assignment.product),
    })),
  }
}

export interface UpdateProductTagInput {
  tagName?: string
  tagDescription?: string | null
  imageUrl?: string | null
  isDashboardTag?: boolean
  relatedStores?: number[]
}

export async function updateProductTag(tagId: number, input: UpdateProductTagInput): Promise<AdminProductTagWithProducts> {
  const [tag] = await db.update(productTagInfo).set({
    ...(input.tagName !== undefined && { tagName: input.tagName }),
    ...(input.tagDescription !== undefined && { tagDescription: input.tagDescription }),
    ...(input.imageUrl !== undefined && { imageUrl: input.imageUrl }),
    ...(input.isDashboardTag !== undefined && { isDashboardTag: input.isDashboardTag }),
    ...(input.relatedStores !== undefined && { relatedStores: input.relatedStores }),
  }).where(eq(productTagInfo.id, tagId)).returning()

  const fullTag = await db.query.productTagInfo.findFirst({
    where: eq(productTagInfo.id, tagId),
    with: {
      products: {
        with: {
          product: true,
        },
      },
    },
  })

  return {
    ...mapTagInfo(tag),
    products: fullTag?.products.map((assignment) => ({
      productId: assignment.productId,
      tagId: assignment.tagId,
      assignedAt: assignment.assignedAt,
      product: mapProduct(assignment.product),
    })) || [],
  }
}

export async function deleteProductTag(tagId: number): Promise<void> {
  await db.delete(productTagInfo).where(eq(productTagInfo.id, tagId))
}

export async function checkProductTagExistsByName(tagName: string): Promise<boolean> {
  const tag = await db.query.productTagInfo.findFirst({
    where: eq(productTagInfo.tagName, tagName),
  })
  return !!tag
}

// ==========================================================================
// Product Helpers for Admin Controller
// ==========================================================================

export async function checkProductExistsByName(name: string): Promise<boolean> {
  const product = await db.query.productInfo.findFirst({
    where: eq(productInfo.name, name),
    columns: { id: true },
  })

  return !!product
}

export async function checkUnitExists(unitId: number): Promise<boolean> {
  const unit = await db.query.units.findFirst({
    where: eq(units.id, unitId),
    columns: { id: true },
  })

  return !!unit
}

export async function getProductImagesById(productId: number): Promise<string[] | null> {
  const product = await db.query.productInfo.findFirst({
    where: eq(productInfo.id, productId),
    columns: { images: true },
  })

  if (!product) {
    return null
  }

  return getStringArray(product.images) || []
}

export interface CreateSpecialDealInput {
  quantity: number
  price: number
  validTill: string | Date
}

export async function createSpecialDealsForProduct(
  productId: number,
  deals: CreateSpecialDealInput[]
): Promise<AdminSpecialDeal[]> {
  if (deals.length === 0) {
    return []
  }

  const dealInserts = deals.map((deal) => ({
    productId,
    quantity: deal.quantity.toString(),
    price: deal.price.toString(),
    validTill: new Date(deal.validTill),
  }))

  const createdDeals = await db
    .insert(specialDeals)
    .values(dealInserts)
    .returning()

  return createdDeals.map(mapSpecialDeal)
}

export async function updateProductDeals(
  productId: number,
  deals: CreateSpecialDealInput[]
): Promise<void> {
  if (deals.length === 0) {
    await db.delete(specialDeals).where(eq(specialDeals.productId, productId))
    return
  }

  const existingDeals = await db.query.specialDeals.findMany({
    where: eq(specialDeals.productId, productId),
  })

  const existingDealsMap = new Map(
    existingDeals.map((deal) => [`${deal.quantity}-${deal.price}`, deal])
  )
  const newDealsMap = new Map(
    deals.map((deal) => [`${deal.quantity}-${deal.price}`, deal])
  )

  const dealsToAdd = deals.filter((deal) => {
    const key = `${deal.quantity}-${deal.price}`
    return !existingDealsMap.has(key)
  })

  const dealsToRemove = existingDeals.filter((deal) => {
    const key = `${deal.quantity}-${deal.price}`
    return !newDealsMap.has(key)
  })

  const dealsToUpdate = deals.filter((deal) => {
    const key = `${deal.quantity}-${deal.price}`
    const existing = existingDealsMap.get(key)
    const nextValidTill = deal.validTill instanceof Date
      ? deal.validTill.toISOString().split('T')[0]
      : String(deal.validTill)
    return existing && existing.validTill.toISOString().split('T')[0] !== nextValidTill
  })

  if (dealsToRemove.length > 0) {
    await db.delete(specialDeals).where(
      inArray(specialDeals.id, dealsToRemove.map((deal) => deal.id))
    )
  }

  if (dealsToAdd.length > 0) {
    const dealInserts = dealsToAdd.map((deal) => ({
      productId,
      quantity: deal.quantity.toString(),
      price: deal.price.toString(),
      validTill: new Date(deal.validTill),
    }))
    await db.insert(specialDeals).values(dealInserts)
  }

  for (const deal of dealsToUpdate) {
    const key = `${deal.quantity}-${deal.price}`
    const existingDeal = existingDealsMap.get(key)
    if (existingDeal) {
      await db.update(specialDeals)
        .set({ validTill: new Date(deal.validTill) })
        .where(eq(specialDeals.id, existingDeal.id))
    }
  }
}

export async function replaceProductTags(productId: number, tagIds: number[]): Promise<void> {
  await db.delete(productTags).where(eq(productTags.productId, productId))

  if (tagIds.length === 0) {
    return
  }

  const tagAssociations = tagIds.map((tagId) => ({
    productId,
    tagId,
  }))

  await db.insert(productTags).values(tagAssociations)
}
