import { prisma } from "../../core/database/prisma";
import type {
  ItemProps,
  InventoryState,
  Price,
  OpenChestResult,
} from "./types";
import type { Prisma } from "@prisma/client";
import { ensureUserAndGuildExist } from "../core/userService";
import coreUtils from "../core/utils";

// Reusar utilidades centrales desde game core
const {
  now,
  isWithin,
  ensureArray,
  parseItemProps,
  parseState,
  updateInventoryEntryState,
} = coreUtils as any;

// Resuelve un EconomyItem por key con alcance de guild o global
export async function findItemByKey(guildId: string, key: string) {
  // buscamos ítem por guildId específico primero, si no, por global (guildId null)
  const item = await prisma.economyItem.findFirst({
    where: {
      key,
      OR: [{ guildId }, { guildId: null }],
    },
    orderBy: [
      // preferir coincidencia del servidor
      { guildId: "desc" },
    ],
  });
  return item;
}

export async function getOrCreateWallet(userId: string, guildId: string) {
  // Asegurar que User y Guild existan antes de crear/buscar wallet
  await ensureUserAndGuildExist(userId, guildId);

  return prisma.economyWallet.upsert({
    where: { userId_guildId: { userId, guildId } },
    update: {},
    create: { userId, guildId, coins: 25 },
  });
}

export async function adjustCoins(
  userId: string,
  guildId: string,
  delta: number
) {
  const wallet = await getOrCreateWallet(userId, guildId);
  const next = Math.max(0, wallet.coins + delta);
  return prisma.economyWallet.update({
    where: { userId_guildId: { userId, guildId } },
    data: { coins: next },
  });
}

export type EnsureInventoryOptions = { createIfMissing?: boolean };

export async function getInventoryEntryByItemId(
  userId: string,
  guildId: string,
  itemId: string,
  opts?: EnsureInventoryOptions
) {
  const existing = await prisma.inventoryEntry.findUnique({
    where: { userId_guildId_itemId: { userId, guildId, itemId } },
  });
  if (existing) return existing;
  if (!opts?.createIfMissing) return null;
  // Asegurar que User y Guild existan antes de crear inventoryEntry para evitar
  // errores de constraint por foreign keys inexistentes.
  await ensureUserAndGuildExist(userId, guildId);
  return prisma.inventoryEntry.create({
    data: { userId, guildId, itemId, quantity: 0 },
  });
}

export async function getInventoryEntry(
  userId: string,
  guildId: string,
  itemKey: string,
  opts?: EnsureInventoryOptions
) {
  const item = await findItemByKey(guildId, itemKey);
  if (!item) throw new Error(`Item key not found: ${itemKey}`);
  const entry = await getInventoryEntryByItemId(userId, guildId, item.id, opts);
  return { item, entry } as const;
}

// utilidades parseItemProps, parseState, ensureArray y updateInventoryEntryState
// provistas por coreUtils importado arriba

function checkUsableWindow(item: {
  usableFrom: Date | null;
  usableTo: Date | null;
  props: any;
}) {
  const props = parseItemProps(item.props);
  const from = props.usableFrom ? new Date(props.usableFrom) : item.usableFrom;
  const to = props.usableTo ? new Date(props.usableTo) : item.usableTo;
  if (!isWithin(now(), from ?? null, to ?? null)) {
    throw new Error("Item no usable por ventana de tiempo");
  }
}

function checkAvailableWindow(item: {
  availableFrom: Date | null;
  availableTo: Date | null;
  props: any;
}) {
  const props = parseItemProps(item.props);
  const from = props.availableFrom
    ? new Date(props.availableFrom)
    : item.availableFrom;
  const to = props.availableTo ? new Date(props.availableTo) : item.availableTo;
  if (!isWithin(now(), from ?? null, to ?? null)) {
    throw new Error("Item no disponible para adquirir");
  }
}

// Agrega cantidad respetando maxPerInventory y stackable
export async function addItemByKey(
  userId: string,
  guildId: string,
  itemKey: string,
  qty: number
) {
  if (qty <= 0) return { added: 0 } as const;
  const found = await getInventoryEntry(userId, guildId, itemKey, {
    createIfMissing: true,
  });
  const item = found.item;
  const entry = found.entry;
  if (!entry) throw new Error("No se pudo crear/obtener inventario");
  checkAvailableWindow(item);

  const max = item.maxPerInventory ?? Number.MAX_SAFE_INTEGER;
  if (item.stackable) {
    const currentQty = entry.quantity ?? 0;
    const added = Math.max(0, Math.min(qty, Math.max(0, max - currentQty)));
    if (added === 0) return { added: 0 } as const;
    const updated = await prisma.inventoryEntry.update({
      where: { userId_guildId_itemId: { userId, guildId, itemId: item.id } },
      data: { quantity: { increment: added } },
    });
    return { added, entry: updated } as const;
  } else {
    // No apilable: usar state.instances
    const state = parseState(entry.state);
    state.instances = ensureArray(state.instances);
    const canAdd = Math.max(
      0,
      Math.min(qty, Math.max(0, max - state.instances.length))
    );
    // Inicializar durabilidad si corresponde
    const props = parseItemProps(item.props);
    const breakable = props.breakable;
    const maxDurability =
      breakable?.enabled !== false ? breakable?.maxDurability : undefined;
    for (let i = 0; i < canAdd; i++) {
      if (maxDurability && maxDurability > 0) {
        state.instances.push({ durability: maxDurability });
      } else {
        state.instances.push({});
      }
    }
    const updated = await updateInventoryEntryState(
      userId,
      guildId,
      item.id,
      state
    );
    return { added: canAdd, entry: updated } as const;
  }
}

export async function consumeItemByKey(
  userId: string,
  guildId: string,
  itemKey: string,
  qty: number
) {
  if (qty <= 0) return { consumed: 0 } as const;
  const { item, entry } = await getInventoryEntry(userId, guildId, itemKey);
  if (!entry || (entry.quantity ?? 0) <= 0) return { consumed: 0 } as const;

  if (item.stackable) {
    const consumed = Math.min(qty, entry.quantity);
    const updated = await prisma.inventoryEntry.update({
      where: { userId_guildId_itemId: { userId, guildId, itemId: item.id } },
      data: { quantity: { decrement: consumed } },
    });
    return { consumed, entry: updated } as const;
  } else {
    const state = parseState(entry.state);
    state.instances = ensureArray(state.instances);
    const consumed = Math.min(qty, state.instances.length);
    if (consumed === 0) return { consumed: 0 } as const;
    state.instances.splice(0, consumed);
    const updated = await updateInventoryEntryState(
      userId,
      guildId,
      item.id,
      state
    );
    return { consumed, entry: updated } as const;
  }
}

export async function openChestByKey(
  userId: string,
  guildId: string,
  itemKey: string
): Promise<OpenChestResult> {
  const { item, entry } = await getInventoryEntry(userId, guildId, itemKey);
  if (!entry || (entry.quantity ?? 0) <= 0)
    throw new Error("No tienes este cofre");
  checkUsableWindow(item);

  const props = parseItemProps(item.props);
  const chest = props.chest ?? {};
  if (!chest.enabled) throw new Error("Este ítem no se puede abrir");
  const rewards: any[] = Array.isArray(chest.rewards) ? chest.rewards : [];
  const mode = chest.randomMode || "all";
  const result: OpenChestResult = {
    coinsDelta: 0,
    itemsToAdd: [],
    rolesToGrant: [],
    consumed: false,
  };

  function pickOneWeighted<T extends { probability?: number }>(
    arr: T[]
  ): T | null {
    const prepared = arr.map((a) => ({
      ...a,
      _w: a.probability != null ? Math.max(0, a.probability) : 1,
    }));
    const total = prepared.reduce((s, a) => s + a._w, 0);
    if (total <= 0) return null;
    let r = Math.random() * total;
    for (const a of prepared) {
      r -= a._w;
      if (r <= 0) return a;
    }
    return prepared[prepared.length - 1] ?? null;
  }

  if (mode === "single") {
    const one = pickOneWeighted(rewards);
    if (one) {
      if (one.type === "coins") result.coinsDelta += Math.max(0, one.amount);
      else if (one.type === "item")
        result.itemsToAdd.push({
          itemKey: one.itemKey,
          itemId: one.itemId,
          qty: one.qty,
        });
      else if (one.type === "role") result.rolesToGrant.push(one.roleId);
    }
  } else {
    // 'all' y 'roll-each': procesar cada reward con probabilidad (default 100%)
    for (const r of rewards) {
      const p = r.probability != null ? Math.max(0, r.probability) : 1; // p en [0,1] recomendado; si usan valores >1 se interpretan como peso
      // Si p > 1 asumimos error o peso -> para modo 'all' lo tratamos como 1 (100%)
      const chance = p > 1 ? 1 : p; // normalizado
      if (Math.random() <= chance) {
        if (r.type === "coins") result.coinsDelta += Math.max(0, r.amount);
        else if (r.type === "item")
          result.itemsToAdd.push({
            itemKey: r.itemKey,
            itemId: r.itemId,
            qty: r.qty,
          });
        else if (r.type === "role") result.rolesToGrant.push(r.roleId);
      }
    }
  }

  // Roles fijos adicionales en chest.roles
  if (Array.isArray(chest.roles) && chest.roles.length) {
    for (const roleId of chest.roles) {
      if (typeof roleId === "string" && roleId.length > 0)
        result.rolesToGrant.push(roleId);
    }
  }

  if (result.coinsDelta) await adjustCoins(userId, guildId, result.coinsDelta);
  for (const it of result.itemsToAdd) {
    if (it.itemKey) await addItemByKey(userId, guildId, it.itemKey, it.qty);
    else if (it.itemId) {
      const item = await prisma.economyItem.findUnique({
        where: { id: it.itemId },
      });
      if (item) await addItemByKey(userId, guildId, item.key, it.qty);
    }
  }

  if (chest.consumeOnOpen) {
    const c = await consumeItemByKey(userId, guildId, itemKey, 1);
    result.consumed = c.consumed > 0;
  }

  return result;
}

export async function craftByProductKey(
  userId: string,
  guildId: string,
  productKey: string
) {
  const product = await findItemByKey(guildId, productKey);
  if (!product) throw new Error(`Producto no encontrado: ${productKey}`);
  const recipe = await prisma.itemRecipe.findUnique({
    where: { productItemId: product.id },
    include: { ingredients: true },
  });
  if (!recipe) throw new Error("No existe receta para este ítem");

  // Verificar ingredientes suficientes
  const shortages: string[] = [];
  for (const ing of recipe.ingredients) {
    const inv = await prisma.inventoryEntry.findUnique({
      where: { userId_guildId_itemId: { userId, guildId, itemId: ing.itemId } },
    });
    const have = inv?.quantity ?? 0;
    if (have < ing.quantity) shortages.push(ing.itemId);
  }
  if (shortages.length) throw new Error("Ingredientes insuficientes");

  // Consumir ingredientes
  for (const ing of recipe.ingredients) {
    await prisma.inventoryEntry.update({
      where: { userId_guildId_itemId: { userId, guildId, itemId: ing.itemId } },
      data: { quantity: { decrement: ing.quantity } },
    });
  }

  // Agregar producto
  const add = await addItemByKey(
    userId,
    guildId,
    product.key,
    recipe.productQuantity
  );
  return { added: add.added, product } as const;
}

export async function buyFromOffer(
  userId: string,
  guildId: string,
  offerId: string,
  qty = 1
) {
  if (qty <= 0) throw new Error("Cantidad inválida");
  const offer = await prisma.shopOffer.findUnique({ where: { id: offerId } });
  if (!offer || offer.guildId !== guildId)
    throw new Error("Oferta no encontrada");
  if (!offer.enabled) throw new Error("Oferta deshabilitada");
  const nowD = now();
  if (!isWithin(nowD, offer.startAt ?? null, offer.endAt ?? null))
    throw new Error("Oferta fuera de fecha");

  const price = (offer.price as unknown as Price) ?? {};
  // Limites
  if (offer.perUserLimit != null) {
    const count = await prisma.shopPurchase.aggregate({
      where: { offerId: offer.id, userId, guildId },
      _sum: { qty: true },
    });
    const already = count._sum.qty ?? 0;
    if (already + qty > offer.perUserLimit)
      throw new Error("Excede el límite por usuario");
  }

  if (offer.stock != null) {
    if (offer.stock < qty) throw new Error("Stock insuficiente");
  }

  // Cobro: coins
  if (price.coins && price.coins > 0) {
    const wallet = await getOrCreateWallet(userId, guildId);
    const total = price.coins * qty;
    if (wallet.coins < total) throw new Error("Monedas insuficientes");
    await prisma.economyWallet.update({
      where: { userId_guildId: { userId, guildId } },
      data: { coins: wallet.coins - total },
    });
  }
  // Cobro: items
  if (price.items && price.items.length) {
    for (const comp of price.items) {
      const key = comp.itemKey;
      const compQty = comp.qty * qty;
      let itemId: string | null = null;
      if (key) {
        const it = await findItemByKey(guildId, key);
        if (!it) throw new Error(`Item de precio no encontrado: ${key}`);
        itemId = it.id;
      } else if (comp.itemId) {
        itemId = comp.itemId;
      }
      if (!itemId) throw new Error("Item de precio inválido");
      const inv = await prisma.inventoryEntry.findUnique({
        where: { userId_guildId_itemId: { userId, guildId, itemId } },
      });
      if ((inv?.quantity ?? 0) < compQty)
        throw new Error("No tienes suficientes items para pagar");
    }
    // si todo está ok, descontar
    for (const comp of price.items) {
      const key = comp.itemKey;
      const compQty = comp.qty * qty;
      let itemId: string | null = null;
      if (key) {
        const it = await findItemByKey(guildId, key);
        itemId = it?.id ?? null;
      } else if (comp.itemId) {
        itemId = comp.itemId;
      }
      if (!itemId) continue;
      await prisma.inventoryEntry.update({
        where: { userId_guildId_itemId: { userId, guildId, itemId } },
        data: { quantity: { decrement: compQty } },
      });
    }
  }

  // Entregar producto
  const item = await prisma.economyItem.findUnique({
    where: { id: offer.itemId },
  });
  if (!item) throw new Error("Ítem de oferta no existente");
  await addItemByKey(userId, guildId, item.key, qty);

  // Registrar compra
  await prisma.shopPurchase.create({
    data: { offerId: offer.id, userId, guildId, qty },
  });

  // Reducir stock global
  if (offer.stock != null) {
    await prisma.shopOffer.update({
      where: { id: offer.id },
      data: { stock: offer.stock - qty },
    });
  }

  return { ok: true, item, qty } as const;
}

// ---------------------------
// Mutaciones
// ---------------------------
export async function findMutationByKey(guildId: string, key: string) {
  return prisma.itemMutation.findFirst({
    where: { key, OR: [{ guildId }, { guildId: null }] },
    orderBy: [{ guildId: "desc" }],
  });
}

export async function applyMutationToInventory(
  userId: string,
  guildId: string,
  itemKey: string,
  mutationKey: string
) {
  const { item, entry } = await getInventoryEntry(userId, guildId, itemKey, {
    createIfMissing: true,
  });
  if (!entry) throw new Error("Inventario inexistente");

  // Política de mutaciones
  const props = parseItemProps(item.props);
  const policy = props.mutationPolicy;
  if (policy?.deniedKeys?.includes(mutationKey))
    throw new Error("Mutación denegada");
  if (policy?.allowedKeys && !policy.allowedKeys.includes(mutationKey))
    throw new Error("Mutación no permitida");

  const mutation = await findMutationByKey(guildId, mutationKey);
  if (!mutation) throw new Error("Mutación no encontrada");

  // Registrar vínculo
  await prisma.inventoryItemMutation.create({
    data: { inventoryId: entry.id, mutationId: mutation.id },
  });
  return { ok: true } as const;
}
