import { BonusPromotion, BonusStatus, CryptoDepositStatus, LedgerDirection, LedgerEntryType, Prisma, UserBonusStatus, WalletAccountType } from "@prisma/client";
import { prisma } from "@/server/db";
import { postLedgerEntry } from "@/server/wallet/ledger";

type Db = typeof prisma | Prisma.TransactionClient;

export const DEFAULT_WELCOME_PROMO_CODE = "WELCOME_SPORT";
export const WELCOME_PROMO_TYPE = "welcome_deposit";

export type WelcomePromotionView = {
  id: string;
  code: string;
  name: string;
  status: string;
  currency: string;
  matchPercent: number;
  maxBonusAmount: string;
  wageringMultiplier: string;
  terms: string;
  startsAt: string;
  endsAt: string | null;
};

type WelcomeRules = {
  welcome?: {
    matchPercent?: number;
    firstDepositOnly?: boolean;
    promoCode?: boolean;
  };
  matchPercent?: number;
};

export async function getDefaultWelcomePromotion(db: Db = prisma) {
  return getWelcomePromotionByCode(DEFAULT_WELCOME_PROMO_CODE, db);
}

export async function getWelcomePromotionByCode(code: string, db: Db = prisma) {
  const normalizedCode = normalizePromotionCode(code);
  if (!normalizedCode) return null;

  const promotion = await db.bonusPromotion.findFirst({
    where: {
      code: normalizedCode,
      type: WELCOME_PROMO_TYPE,
      status: BonusStatus.ACTIVE,
      OR: [{ endsAt: null }, { endsAt: { gt: new Date() } }]
    }
  });

  return promotion;
}

export function serializeWelcomePromotion(promotion: BonusPromotion): WelcomePromotionView {
  return {
    id: promotion.id,
    code: promotion.code,
    name: promotion.name,
    status: promotion.status,
    currency: promotion.currency,
    matchPercent: getMatchPercent(promotion),
    maxBonusAmount: promotion.maxBonusAmount?.toString() ?? "0",
    wageringMultiplier: promotion.wageringMultiplier.toString(),
    terms: promotion.terms,
    startsAt: promotion.startsAt.toISOString(),
    endsAt: promotion.endsAt?.toISOString() ?? null
  };
}

export function promotionLabel(promotion: BonusPromotion) {
  return `${getMatchPercent(promotion)}% jusqu'à ${formatEuro(promotion.maxBonusAmount?.toString() ?? "0")}`;
}

export async function createPendingWelcomeBonusInTx(tx: Prisma.TransactionClient, input: { userId: string; promotion: BonusPromotion }) {
  return tx.userBonus.create({
    data: {
      userId: input.userId,
      promotionId: input.promotion.id,
      status: UserBonusStatus.OFFERED,
      bonusAmount: "0",
      wagerRequired: "0",
      remainingAmount: "0",
      expiresAt: input.promotion.endsAt
    }
  });
}

export async function applyFirstDepositWelcomeBonusInTx(
  tx: Prisma.TransactionClient,
  input: {
    userId: string;
    walletId: string;
    depositId: string;
    depositAmount: Prisma.Decimal;
    currency: string;
  }
) {
  const previousCreditedDeposits = await tx.cryptoDeposit.count({
    where: {
      userId: input.userId,
      status: CryptoDepositStatus.CREDITED,
      id: { not: input.depositId }
    }
  });

  if (previousCreditedDeposits > 0) {
    return null;
  }

  const pendingBonus = await tx.userBonus.findFirst({
    where: {
      userId: input.userId,
      status: UserBonusStatus.OFFERED,
      bonusAmount: { lte: new Prisma.Decimal(0) },
      promotion: {
        type: WELCOME_PROMO_TYPE,
        status: BonusStatus.ACTIVE,
        currency: input.currency
      }
    },
    include: { promotion: true },
    orderBy: { createdAt: "asc" }
  });

  if (!pendingBonus) {
    return null;
  }

  const bonusAmount = calculateWelcomeBonus(pendingBonus.promotion, input.depositAmount);
  if (bonusAmount.lte(0)) {
    return null;
  }

  const wagerRequired = bonusAmount.mul(pendingBonus.promotion.wageringMultiplier);

  await postLedgerEntry(tx, {
    walletId: input.walletId,
    accountType: WalletAccountType.BONUS,
    direction: LedgerDirection.CREDIT,
    type: LedgerEntryType.BONUS_CREDIT,
    amount: bonusAmount,
    currency: input.currency,
    referenceType: "welcome_bonus",
    referenceId: pendingBonus.id,
    idempotencyKey: `welcome-bonus:${pendingBonus.id}:${input.depositId}`,
    reason: `Bonus premier dépôt ${pendingBonus.promotion.code}`,
    metadata: {
      promotionId: pendingBonus.promotionId,
      promotionCode: pendingBonus.promotion.code,
      depositId: input.depositId,
      depositAmount: input.depositAmount.toString(),
      matchPercent: getMatchPercent(pendingBonus.promotion),
      maxBonusAmount: pendingBonus.promotion.maxBonusAmount?.toString() ?? "0",
      wageringMultiplier: pendingBonus.promotion.wageringMultiplier.toString()
    }
  });

  const updatedBonus = await tx.userBonus.update({
    where: { id: pendingBonus.id },
    data: {
      status: UserBonusStatus.ACTIVE,
      bonusAmount,
      wagerRequired,
      remainingAmount: bonusAmount
    },
    include: { promotion: true }
  });

  return { bonus: updatedBonus, amount: bonusAmount, wagerRequired };
}

export function calculateWelcomeBonus(promotion: BonusPromotion, depositAmount: Prisma.Decimal) {
  const matchPercent = new Prisma.Decimal(getMatchPercent(promotion)).div(100);
  const rawBonus = depositAmount.mul(matchPercent);
  const maxBonus = new Prisma.Decimal(promotion.maxBonusAmount ?? 0);
  if (maxBonus.lte(0)) return rawBonus;
  return rawBonus.lt(maxBonus) ? rawBonus : maxBonus;
}

export function getMatchPercent(promotion: Pick<BonusPromotion, "contributionRules">) {
  const rules = promotion.contributionRules as WelcomeRules;
  return Number(rules?.welcome?.matchPercent ?? rules?.matchPercent ?? 0);
}

export function normalizePromotionCode(code: string) {
  return code.trim().toUpperCase().replace(/[^A-Z0-9_-]/g, "");
}

function formatEuro(value: string) {
  return new Intl.NumberFormat("fr-FR", {
    style: "currency",
    currency: "EUR",
    maximumFractionDigits: 0
  }).format(Number(value));
}
