/**
 * Zod schemas for vendor-side deal API routes.
 */

import { z } from 'zod';
import { MAX_PER_DEAL_CAP_HARD, MAX_PER_SKU_CAP_HARD } from '@/server/constants/image-limits';
import { dealTypeSchema } from '@/lib/deal-types/schema';
import { dealTypeCaps } from '@/lib/deal-types';
import { shippingMode } from '@/lib/enums/shipping-mode';
import { carrier, MANUAL_CARRIER } from '@/lib/enums/carrier';

// ─── Image-set schemas ────────────────────────────────────────────────────────

export const dealImageEntry = z.object({
  r2Key: z.string().min(1).max(512),
  isPrimary: z.boolean(),
  sortOrder: z.number().int().min(0),
});

export const skuImageSet = z.object({
  optionValueCodes: z.array(z.string().min(1).max(64)).min(1).max(8),
  images: z.array(dealImageEntry).max(MAX_PER_SKU_CAP_HARD),
});

export const imageSetSchema = z
  .object({
    deal: z.array(dealImageEntry).min(1).max(MAX_PER_DEAL_CAP_HARD),
    skus: z.array(skuImageSet).optional(),
    visualAxisOrder: z.number().int().min(0).max(2).nullable().optional(),
  })
  .superRefine((val, ctx) => {
    const primaries = val.deal.filter((i) => i.isPrimary).length;
    if (primaries !== 1) {
      ctx.addIssue({
        code: 'custom',
        path: ['deal'],
        message: 'deal image set must have exactly one primary',
      });
    }
    for (const set of val.skus ?? []) {
      const skuPrimaries = set.images.filter((i) => i.isPrimary).length;
      if (set.images.length > 0 && skuPrimaries !== 1) {
        ctx.addIssue({
          code: 'custom',
          path: ['skus'],
          message: `sku ${set.optionValueCodes.join('|')} image set must have exactly one primary`,
        });
      }
    }
  });

export type DealImageEntry = z.infer<typeof dealImageEntry>;
export type SkuImageSet = z.infer<typeof skuImageSet>;
export type ImageSet = z.infer<typeof imageSetSchema>;

// ─── Shared primitives ────────────────────────────────────────────────────────

/** Positive ILS price string, e.g. "49.90" */
const priceSchema = z
  .string()
  .trim()
  .regex(/^\d+(\.\d{1,2})?$/, 'Price must be a positive decimal with up to 2 decimal places')
  .refine((v) => Number(v) > 0, 'Price must be greater than 0');

/** Discount percent: 0-99 inclusive */
const discountPercentSchema = z.number().int().min(0).max(99);

/** Time string in HH:MM format */
const timeStringSchema = z
  .string()
  .trim()
  .regex(/^\d{2}:\d{2}$/, 'Time must be in HH:MM format')
  .optional();

// ─── Create deal ─────────────────────────────────────────────────────────────

// Raw shape — no refinements. Safe to .extend() / .partial() downstream.
export const createDealBodyShape = z.object({
  dealType: dealTypeSchema,
  title: z.string().trim().min(1).max(120),
  description: z.string().trim().max(2000).optional(),
  categoryId: z.string().optional(),
  tagIds: z.array(z.string()).max(20).optional().default([]),
  isVoucher: z.boolean().optional().default(false),
  originalPrice: priceSchema,
  discountPercent: discountPercentSchema,
  discountedPrice: priceSchema,
  quantityTotal: z.number().int().min(1).max(10000),
  windowStart: z.iso.datetime({ offset: true }).optional(),
  windowEnd: z.iso.datetime({ offset: true }).optional(),
  pickupStart: timeStringSchema,
  pickupEnd: timeStringSchema,
  pickupAddress: z.string().trim().max(300).optional(),
  // TODO: ADD-DELIVERY-MODE-COLUMN — delivery_mode column not yet in deals table; field removed to prevent silent data drop
  specialInstructions: z.string().trim().max(1000).optional(),
  imageSet: imageSetSchema,
  /** Maximum units a single user may purchase. Null = no per-user cap. */
  maxPerUser: z.number().int().min(1).max(1000).optional().nullable(),
});

// Refined schema — used by API handlers / workflows for full validation.
// Pickup hours required for COUPON when delivery method includes pickup
// (PICKUP_ONLY or BOTH). DELIVERY-only coupons skip pickup. GROUP deals
// don't render pickup-time inputs.
export const createDealBodySchema = createDealBodyShape.superRefine((val, ctx) => {
  if (dealTypeCaps(val.dealType).requiresPickupHours) {
    if (!val.pickupStart) {
      ctx.addIssue({
        path: ['pickupStart'],
        code: 'custom',
        message: 'Required for COUPON',
      });
    }
    if (!val.pickupEnd) {
      ctx.addIssue({
        path: ['pickupEnd'],
        code: 'custom',
        message: 'Required for COUPON',
      });
    }
  }
  const discounted =
    Math.round(Number(val.originalPrice) * (1 - val.discountPercent / 100) * 100) / 100;
  if (discounted < 3) {
    ctx.addIssue({
      code: 'custom',
      message: 'Deal price after discount must be at least ₪3',
      path: ['discountPercent'],
    });
  }
  // Reject windowStart in the past (5-minute clock-skew tolerance).
  if (val.windowStart) {
    const skewMs = 5 * 60 * 1000;
    if (new Date(val.windowStart).getTime() < Date.now() - skewMs) {
      ctx.addIssue({
        path: ['windowStart'],
        code: 'custom',
        message: 'windowStart must not be in the past',
      });
    }
  }
});

export type CreateDealBody = z.infer<typeof createDealBodySchema>;

// ─── Update deal ─────────────────────────────────────────────────────────────

/**
 * All fields optional for partial PATCH.
 * Locked-field enforcement happens in the workflow, not here.
 */
export const updateDealBodySchema = z
  .object({
    title: z.string().trim().min(1).max(120).optional(),
    description: z.string().trim().max(2000).optional(),
    categoryId: z.string().optional(),
    tagIds: z.array(z.string()).max(20).optional(),
    isVoucher: z.boolean().optional(),
    originalPrice: priceSchema.optional(),
    discountPercent: discountPercentSchema.optional(),
    discountedPrice: priceSchema.optional(),
    quantityTotal: z.number().int().min(1).max(10000).optional(),
    windowStart: z.iso.datetime({ offset: true }).optional(),
    windowEnd: z.iso.datetime({ offset: true }).optional(),
    pickupStart: timeStringSchema,
    pickupEnd: timeStringSchema,
    pickupAddress: z.string().trim().max(300).optional(),
    // TODO: ADD-DELIVERY-MODE-COLUMN — delivery_mode column not yet in deals table; field removed to prevent silent data drop
    specialInstructions: z.string().trim().max(1000).optional(),
    imageSet: imageSetSchema.optional(),
    /** Maximum units a single user may purchase. Null = no per-user cap. */
    maxPerUser: z.number().int().min(1).max(1000).optional().nullable(),
  })
  .superRefine((val, ctx) => {
    if (val.originalPrice !== undefined && val.discountPercent !== undefined) {
      const discounted =
        Math.round(Number(val.originalPrice) * (1 - val.discountPercent / 100) * 100) / 100;
      if (discounted < 3) {
        ctx.addIssue({
          code: 'custom',
          message: 'Deal price after discount must be at least ₪3',
          path: ['discountPercent'],
        });
      }
    }
  });

export type UpdateDealBody = z.infer<typeof updateDealBodySchema>;

// ─── Add quantity ─────────────────────────────────────────────────────────────

export const addQuantityBodySchema = z.object({
  delta: z.number().int().min(1).max(10000),
});

export type AddQuantityBody = z.infer<typeof addQuantityBodySchema>;

// ─── Patch vendor profile ─────────────────────────────────────────────────────

export const patchVendorBodySchema = z.object({
  businessName: z.string().trim().min(1).max(120).optional(),
  description: z.string().trim().max(2000).optional(),
  phone: z
    .string()
    .trim()
    .regex(/^\+?[0-9]{9,15}$/, 'Invalid phone number format')
    .optional(),
  email: z.email('Invalid email address').trim().max(254).optional(),
  logoUrl: z
    .string()
    .max(1024)
    .refine(
      (v) => {
        // reject protocol-relative ("//evil.com") and "/\" variants first
        if (v.startsWith('//') || v.startsWith('/\\')) return false;
        if (v.startsWith('/')) return true;
        if (v.startsWith('https://') || v.startsWith('http://')) return true;
        // bare R2 key e.g. "originals/<sha>.jpg" — no colon, safe internal ref
        if (!v.includes(':')) return true;
        return false;
      },
      { message: 'Invalid URL' },
    )
    .optional()
    .nullable(),
  heroImageUrl: z
    .string()
    .max(1024)
    .refine(
      (v) => {
        if (v.startsWith('//') || v.startsWith('/\\')) return false;
        if (v.startsWith('/')) return true;
        if (v.startsWith('https://') || v.startsWith('http://')) return true;
        if (!v.includes(':')) return true;
        return false;
      },
      { message: 'Invalid URL' },
    )
    .optional()
    .nullable(),
  heroImageR2Key: z.string().optional().nullable(),
  heroFocalX: z.number().min(0).max(1).optional(),
  heroFocalY: z.number().min(0).max(1).optional(),
  website: z.url('Invalid URL').or(z.literal('')).optional().nullable(),
  /** UUIDs of `business_types` rows (1..5) classifying the vendor. */
  businessTypeIds: z.array(z.uuid()).max(5).optional(),
  /** Whether the vendor offers self-pickup at their location. */
  selfPickup: z.boolean().optional(),
  /** Free-text pickup address shown to buyers. Null clears it. */
  pickupAddress: z.string().trim().max(300).nullable().optional(),
  /** Structured return address. Null explicitly clears it. */
  returnAddress: z
    .object({
      line1: z.string().trim().min(1).max(120),
      line2: z.string().trim().max(120).optional(),
      city: z.string().trim().min(1).max(80),
      postal: z.string().trim().min(1).max(20),
      country: z.string().trim().length(2).default('IL'),
    })
    .optional()
    .nullable(),
  /** Restocking fee as a fraction (0..0.05 = 0%..5%). */
  restockingFeePct: z.number().min(0).max(0.05).optional(),
});

export type PatchVendorBody = z.infer<typeof patchVendorBodySchema>;

// ─── Shipping config ──────────────────────────────────────────────────────────

export const shippingConfigSchema = z.object({
  preferredCarrier: z
    // Preferred-carrier subset: excludes pickup only (no-carrier path). wolt_drive included (auto-dispatched via Wolt Drive).
    .enum(carrier.schema.extract([...MANUAL_CARRIER, 'wolt_drive']).options)
    .nullable(),
  // pickup handled via deliveryMode; pickup_only is pricing-path only
  shippingMode: shippingMode.schema.extract(['free', 'flat', 'free_over_threshold']),
  shippingFlatAgorot: z.number().int().min(0).nullable(),
  shippingFreeThresholdAgorot: z.number().int().min(0).nullable(),
  pickupEnabled: z.boolean().default(false),
  pickupAddressId: z.uuid().nullable().optional(),
});

export type ShippingConfig = z.infer<typeof shippingConfigSchema>;
