/**
 * upsertDealSkus — update variant axes / options / SKUs for an existing deal.
 *
 * Strategy:
 * - Axes matched by (dealId, axisOrder). Existing axis → update nameHe/nameEn/kind.
 *   New axis → insert. Axes present in DB but not in payload → soft-delete (isActive=false).
 * - Options matched by (axisId, valueCode). Existing → update labels/slot/swatch.
 *   New → insert. Missing → soft-delete.
 * - SKUs matched by optionIdsHash. Existing → update price/qty. New → insert.
 *   Missing → soft-delete.
 * - refreshDealCache + validateDealHasSkus at end of TX.
 */

import { and, eq, not, inArray } from 'drizzle-orm';
import { setInventory, toInventoryTx } from '@/server/stock/inventory-platform.js';
import type { DrizzleClient, TxDrizzleClient } from '@/server/db/client';
import { dealSkus, dealVariantAxes, dealVariantOptions, deals } from '@/server/db/schema';
import { hashOptionIds } from '@/server/domain/variants/hash';
import { refreshDealCache } from '@/server/domain/variants/cache';
import { validateDealHasSkus } from './validate-has-skus';
import { replaceQtyTiersForSku } from '@/server/db/queries/sku-qty-tiers.js';
import type { VariantAxisSpec, SkuSpec } from '@/server/schemas/vendor-deal';
import { invalidateCatalog } from '@/server/cache/invalidate.js';

export interface UpsertDealSkusInput {
  dealId: string;
  mode: 'single' | 'variants';
  single?: {
    originalPrice: string;
    discountPercent: number;
    discountedPrice: string;
    quantityTotal: number;
    qtyTiers?: Array<{ minQty: number; discountPercent: number }>;
  };
  variants?: {
    axes: VariantAxisSpec[];
    skus: SkuSpec[];
  };
}

export async function upsertDealSkus(
  db: TxDrizzleClient,
  input: UpsertDealSkusInput,
): Promise<void> {
  await db.transaction(async (tx) => {
    await upsertDealSkusInTransaction(tx, input);
  });
  await invalidateCatalog(db, { scope: 'deal', dealId: input.dealId });
}

async function upsertDealSkusInTransaction(
  tx: DrizzleClient,
  input: UpsertDealSkusInput,
): Promise<void> {
  const { dealId } = input;

  const [dealRow] = await tx.select({ vendorId: deals.vendorId }).from(deals).where(eq(deals.id, dealId)).limit(1);
  const vendorId = dealRow?.vendorId ?? null;

  if (input.mode === 'single') {
    if (!input.single) throw new Error('SINGLE_MODE_REQUIRES_PRICE_BLOCK');
    const { originalPrice, discountPercent, discountedPrice, quantityTotal, qtyTiers } =
      input.single;
    const defaultHash = hashOptionIds([]);

    // Upsert the single default SKU (optionIdsHash = 'default')
    const existing = await tx
      .select({ id: dealSkus.id })
      .from(dealSkus)
      .where(and(eq(dealSkus.dealId, dealId), eq(dealSkus.optionIdsHash, defaultHash)));

    let singleSkuId: string;
    if (existing.length > 0 && existing[0]) {
      singleSkuId = existing[0].id;
      await tx
        .update(dealSkus)
        .set({ originalPrice, discountPercent, discountedPrice, quantityTotal, isActive: true })
        .where(and(eq(dealSkus.dealId, dealId), eq(dealSkus.optionIdsHash, defaultHash)));
      await setInventory(toInventoryTx(tx), {
        skuId: singleSkuId,
        vendorId,
        quantityTotal,
      });
    } else {
      const [inserted] = await tx
        .insert(dealSkus)
        .values({
          dealId,
          optionIds: [],
          optionIdsHash: defaultHash,
          originalPrice,
          discountPercent,
          discountedPrice,
          quantityTotal,
        })
        .returning({ id: dealSkus.id });
      if (!inserted) throw new Error('SINGLE_SKU_INSERT_FAILED');
      singleSkuId = inserted.id;
      await setInventory(toInventoryTx(tx), {
        skuId: singleSkuId,
        vendorId,
        quantityTotal,
      });
    }

    // Persist quantity-discount tiers for the default SKU
    if (qtyTiers !== undefined) {
      await replaceQtyTiersForSku(tx, singleSkuId, qtyTiers);
    }

    // Soft-delete any non-default SKUs
    await tx
      .update(dealSkus)
      .set({ isActive: false })
      .where(and(eq(dealSkus.dealId, dealId), not(eq(dealSkus.optionIdsHash, defaultHash))));

    // Soft-delete all axes (single mode has no axes)
    await tx
      .update(dealVariantAxes)
      .set({ isActive: false })
      .where(eq(dealVariantAxes.dealId, dealId));

    await refreshDealCache(tx, dealId);
    await validateDealHasSkus(tx, dealId);
    return;
  }

  // ── Variants mode ───────────────────────────────────────────────────────────

  if (!input.variants) throw new Error('VARIANTS_MODE_REQUIRES_PAYLOAD');
  const { axes: incomingAxes, skus: incomingSkus } = input.variants;

  const optionIdsByAxisOrderAndCode = new Map<string, string>();
  const keptAxisIds: string[] = [];

  for (const ax of incomingAxes) {
    // Look up existing axis by (dealId, axisOrder)
    const existing = await tx
      .select({ id: dealVariantAxes.id })
      .from(dealVariantAxes)
      .where(and(eq(dealVariantAxes.dealId, dealId), eq(dealVariantAxes.axisOrder, ax.axisOrder)));

    let axisId: string;

    if (existing.length > 0 && existing[0]) {
      axisId = existing[0].id;
      await tx
        .update(dealVariantAxes)
        .set({ kind: ax.kind, nameHe: ax.nameHe, nameEn: ax.nameEn, isActive: true })
        .where(eq(dealVariantAxes.id, axisId));
    } else {
      const [inserted] = await tx
        .insert(dealVariantAxes)
        .values({
          dealId,
          axisOrder: ax.axisOrder,
          kind: ax.kind,
          nameHe: ax.nameHe,
          nameEn: ax.nameEn,
        })
        .returning({ id: dealVariantAxes.id });
      if (!inserted) throw new Error('AXIS_INSERT_FAILED');
      axisId = inserted.id;
    }

    keptAxisIds.push(axisId);

    // Options: upsert by (axisId, valueCode)
    const keptOptionIds: string[] = [];
    for (const opt of ax.options) {
      const existingOpt = await tx
        .select({ id: dealVariantOptions.id })
        .from(dealVariantOptions)
        .where(
          and(
            eq(dealVariantOptions.axisId, axisId),
            eq(dealVariantOptions.valueCode, opt.valueCode),
          ),
        );

      let optionId: string;
      if (existingOpt.length > 0 && existingOpt[0]) {
        optionId = existingOpt[0].id;
        await tx
          .update(dealVariantOptions)
          .set({
            optionOrder: opt.optionOrder,
            labelHe: opt.labelHe,
            labelEn: opt.labelEn,
            slotStart: opt.slotStart ?? null,
            slotEnd: opt.slotEnd ?? null,
            swatchHex: opt.swatchHex ?? null,
            isActive: true,
          })
          .where(eq(dealVariantOptions.id, optionId));
      } else {
        const [inserted] = await tx
          .insert(dealVariantOptions)
          .values({
            axisId,
            optionOrder: opt.optionOrder,
            valueCode: opt.valueCode,
            labelHe: opt.labelHe,
            labelEn: opt.labelEn,
            slotStart: opt.slotStart ?? null,
            slotEnd: opt.slotEnd ?? null,
            swatchHex: opt.swatchHex ?? null,
          })
          .returning({ id: dealVariantOptions.id });
        if (!inserted) throw new Error('OPTION_INSERT_FAILED');
        optionId = inserted.id;
      }

      optionIdsByAxisOrderAndCode.set(`${ax.axisOrder}|${opt.valueCode}`, optionId);
      keptOptionIds.push(optionId);
    }

    // Soft-delete removed options for this axis
    if (keptOptionIds.length > 0) {
      await tx
        .update(dealVariantOptions)
        .set({ isActive: false })
        .where(
          and(
            eq(dealVariantOptions.axisId, axisId),
            not(inArray(dealVariantOptions.id, keptOptionIds)),
          ),
        );
    } else {
      await tx
        .update(dealVariantOptions)
        .set({ isActive: false })
        .where(eq(dealVariantOptions.axisId, axisId));
    }
  }

  // Soft-delete axes not in the incoming list
  if (keptAxisIds.length > 0) {
    await tx
      .update(dealVariantAxes)
      .set({ isActive: false })
      .where(
        and(eq(dealVariantAxes.dealId, dealId), not(inArray(dealVariantAxes.id, keptAxisIds))),
      );
  } else {
    await tx
      .update(dealVariantAxes)
      .set({ isActive: false })
      .where(eq(dealVariantAxes.dealId, dealId));
  }

  // Upsert SKUs by optionIdsHash
  const keptSkuHashes: string[] = [];
  for (const skuSpec of incomingSkus) {
    const optionIds = skuSpec.optionValueCodes.map((code, idx) => {
      const id = optionIdsByAxisOrderAndCode.get(`${idx}|${code}`);
      if (!id) throw new Error(`OPTION_CODE_UNKNOWN:${idx}|${code}`);
      return id;
    });
    const hash = hashOptionIds(optionIds);
    keptSkuHashes.push(hash);

    const existingSku = await tx
      .select({ id: dealSkus.id })
      .from(dealSkus)
      .where(and(eq(dealSkus.dealId, dealId), eq(dealSkus.optionIdsHash, hash)));

    let skuId: string;
    if (existingSku.length > 0 && existingSku[0]) {
      skuId = existingSku[0].id;
      await tx
        .update(dealSkus)
        .set({
          optionIds,
          originalPrice: skuSpec.originalPrice,
          discountPercent: skuSpec.discountPercent,
          discountedPrice: skuSpec.discountedPrice,
          quantityTotal: skuSpec.quantityTotal,
          isActive: true,
        })
        .where(and(eq(dealSkus.dealId, dealId), eq(dealSkus.optionIdsHash, hash)));
    } else {
      const [inserted] = await tx
        .insert(dealSkus)
        .values({
          dealId,
          optionIds,
          optionIdsHash: hash,
          originalPrice: skuSpec.originalPrice,
          discountPercent: skuSpec.discountPercent,
          discountedPrice: skuSpec.discountedPrice,
          quantityTotal: skuSpec.quantityTotal,
        })
        .returning({ id: dealSkus.id });
      if (!inserted) throw new Error('SKU_INSERT_FAILED');
      skuId = inserted.id;
    }

    await setInventory(toInventoryTx(tx), { skuId, vendorId, quantityTotal: skuSpec.quantityTotal });

    // Persist quantity-discount tiers for this SKU
    const tiers = skuSpec.qtyTiers ?? [];
    await replaceQtyTiersForSku(tx, skuId, tiers);
  }

  // Soft-delete SKUs no longer in the payload
  if (keptSkuHashes.length > 0) {
    await tx
      .update(dealSkus)
      .set({ isActive: false })
      .where(and(eq(dealSkus.dealId, dealId), not(inArray(dealSkus.optionIdsHash, keptSkuHashes))));
  } else {
    await tx.update(dealSkus).set({ isActive: false }).where(eq(dealSkus.dealId, dealId));
  }

  await refreshDealCache(tx, dealId);
  await validateDealHasSkus(tx, dealId);
}
