import { ActionRowBuilder, ButtonBuilder, ButtonStyle, type APIEmbed } from 'discord.js';
import { formatCurrency, randomInt } from '@nexumi/shared';
import type { BotContext } from '../../types.js';
import type { Locale } from '@nexumi/shared';
import { tf } from '@nexumi/shared';
import {
  addBalance,
  assertEconomyEnabled,
  deductBalance,
  EconomyError,
  getEconomyConfig
} from './service.js';

const BLACKJACK_TTL_SECONDS = 300;
const BLACKJACK_KEY_PREFIX = 'eco:bj:';
export const BLACKJACK_BUTTON_PREFIX = 'eco:bj:';

const RANKS = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'] as const;
const SUITS = ['H', 'D', 'C', 'S'] as const;

type Card = `${(typeof RANKS)[number]}${(typeof SUITS)[number]}`;

export type BlackjackState = {
  userId: string;
  guildId: string;
  bet: number;
  deck: Card[];
  playerHand: Card[];
  dealerHand: Card[];
  finished: boolean;
};

const SLOT_SYMBOLS = ['🍒', '🍋', '🔔', '⭐', '💎', '7️⃣'] as const;

function buildDeck(): Card[] {
  const deck: Card[] = [];
  for (const suit of SUITS) {
    for (const rank of RANKS) {
      deck.push(`${rank}${suit}`);
    }
  }
  for (let i = deck.length - 1; i > 0; i -= 1) {
    const j = randomInt(0, i);
    [deck[i], deck[j]] = [deck[j]!, deck[i]!];
  }
  return deck;
}

function cardValue(card: Card): number {
  const rank = card.slice(0, card.length - 1);
  if (rank === 'A') {
    return 11;
  }
  if (rank === 'K' || rank === 'Q' || rank === 'J') {
    return 10;
  }
  return Number(rank);
}

export function handValue(hand: Card[]): number {
  let total = hand.reduce((sum, card) => sum + cardValue(card), 0);
  let aces = hand.filter((card) => card.startsWith('A')).length;
  while (total > 21 && aces > 0) {
    total -= 10;
    aces -= 1;
  }
  return total;
}

function isBlackjack(hand: Card[]): boolean {
  return hand.length === 2 && handValue(hand) === 21;
}

function formatHand(hand: Card[], hideFirst = false): string {
  if (hideFirst && hand.length > 0) {
    return `🂠 ${hand.slice(1).join(' ')}`;
  }
  return hand.join(' ');
}

function blackjackKey(guildId: string, userId: string): string {
  return `${BLACKJACK_KEY_PREFIX}${guildId}:${userId}`;
}

async function saveBlackjackState(context: BotContext, state: BlackjackState): Promise<void> {
  await context.redis.set(
    blackjackKey(state.guildId, state.userId),
    JSON.stringify(state),
    'EX',
    BLACKJACK_TTL_SECONDS
  );
}

async function loadBlackjackState(
  context: BotContext,
  guildId: string,
  userId: string
): Promise<BlackjackState | null> {
  const raw = await context.redis.get(blackjackKey(guildId, userId));
  if (!raw) {
    return null;
  }
  return JSON.parse(raw) as BlackjackState;
}

async function deleteBlackjackState(context: BotContext, guildId: string, userId: string): Promise<void> {
  await context.redis.del(blackjackKey(guildId, userId));
}

function drawCard(state: BlackjackState): Card {
  const card = state.deck.pop();
  if (!card) {
    throw new Error('Blackjack deck empty');
  }
  return card;
}

export function buildBlackjackButtons(locale: Locale, guildId: string, userId: string): ActionRowBuilder<ButtonBuilder> {
  return new ActionRowBuilder<ButtonBuilder>().addComponents(
    new ButtonBuilder()
      .setCustomId(`${BLACKJACK_BUTTON_PREFIX}hit:${guildId}:${userId}`)
      .setLabel(tf(locale, 'economy.blackjack.button.hit', {}))
      .setStyle(ButtonStyle.Primary),
    new ButtonBuilder()
      .setCustomId(`${BLACKJACK_BUTTON_PREFIX}stand:${guildId}:${userId}`)
      .setLabel(tf(locale, 'economy.blackjack.button.stand', {}))
      .setStyle(ButtonStyle.Secondary)
  );
}

export function buildBlackjackEmbed(
  locale: Locale,
  state: BlackjackState,
  config: { currencySymbol: string; currencyName: string },
  revealDealer: boolean,
  outcome?: 'win' | 'lose' | 'push' | 'blackjack'
): APIEmbed {
  const playerTotal = handValue(state.playerHand);
  const dealerTotal = revealDealer ? handValue(state.dealerHand) : handValue(state.dealerHand.slice(1));

  let description = tf(locale, 'economy.blackjack.hand', {
    playerHand: formatHand(state.playerHand),
    playerTotal: String(playerTotal),
    dealerHand: formatHand(state.dealerHand, !revealDealer),
    dealerTotal: revealDealer ? String(dealerTotal) : '?',
    bet: formatCurrency(state.bet, config.currencySymbol, config.currencyName)
  });

  if (outcome) {
    const outcomeKey = `economy.blackjack.outcome.${outcome}` as const;
    description += `\n\n${tf(locale, outcomeKey, {
      bet: formatCurrency(state.bet, config.currencySymbol, config.currencyName)
    })}`;
  }

  return {
    title: tf(locale, 'economy.blackjack.title', {}),
    description,
    color: outcome === 'win' || outcome === 'blackjack' ? 0x22c55e : outcome === 'push' ? 0xeab308 : 0x6366f1
  };
}

async function finishBlackjack(
  context: BotContext,
  state: BlackjackState
): Promise<{ state: BlackjackState; outcome: 'win' | 'lose' | 'push' | 'blackjack'; payout: number }> {
  const playerTotal = handValue(state.playerHand);
  const dealerTotal = handValue(state.dealerHand);

  let outcome: 'win' | 'lose' | 'push' | 'blackjack';
  let payout = 0;

  if (playerTotal > 21) {
    outcome = 'lose';
  } else if (isBlackjack(state.playerHand) && !isBlackjack(state.dealerHand)) {
    outcome = 'blackjack';
    payout = Math.floor(state.bet * 2.5);
  } else if (dealerTotal > 21 || playerTotal > dealerTotal) {
    outcome = 'win';
    payout = state.bet * 2;
  } else if (playerTotal === dealerTotal) {
    outcome = 'push';
    payout = state.bet;
  } else {
    outcome = 'lose';
  }

  if (payout > 0) {
    await addBalance(context.prisma, state.guildId, state.userId, payout);
  }

  state.finished = true;
  await deleteBlackjackState(context, state.guildId, state.userId);

  return { state, outcome, payout };
}

export async function startBlackjack(
  context: BotContext,
  guildId: string,
  userId: string,
  bet: number,
  locale: Locale
): Promise<{ embed: APIEmbed; components: ActionRowBuilder<ButtonBuilder>[] }> {
  await assertEconomyEnabled(context.prisma, guildId);
  if (!Number.isInteger(bet) || bet <= 0) {
    throw new EconomyError('invalid_amount');
  }

  const existing = await loadBlackjackState(context, guildId, userId);
  if (existing && !existing.finished) {
    throw new EconomyError('game_in_progress');
  }

  await deductBalance(context.prisma, guildId, userId, bet);

  const state: BlackjackState = {
    userId,
    guildId,
    bet,
    deck: buildDeck(),
    playerHand: [],
    dealerHand: [],
    finished: false
  };

  state.playerHand.push(drawCard(state), drawCard(state));
  state.dealerHand.push(drawCard(state), drawCard(state));

  const config = await getEconomyConfig(context.prisma, guildId);

  if (isBlackjack(state.playerHand) || isBlackjack(state.dealerHand)) {
    const { state: finished, outcome } = await finishBlackjack(context, state);
    return {
      embed: buildBlackjackEmbed(locale, finished, config, true, outcome),
      components: []
    };
  }

  await saveBlackjackState(context, state);
  return {
    embed: buildBlackjackEmbed(locale, state, config, false),
    components: [buildBlackjackButtons(locale, guildId, userId)]
  };
}

export async function hitBlackjack(
  context: BotContext,
  guildId: string,
  userId: string,
  locale: Locale
): Promise<{ embed: APIEmbed; components: ActionRowBuilder<ButtonBuilder>[]; finished: boolean }> {
  const state = await loadBlackjackState(context, guildId, userId);
  if (!state || state.finished) {
    throw new EconomyError('not_found');
  }
  if (state.userId !== userId) {
    throw new EconomyError('not_found');
  }

  state.playerHand.push(drawCard(state));
  const config = await getEconomyConfig(context.prisma, guildId);

  if (handValue(state.playerHand) > 21) {
    const { state: finished, outcome } = await finishBlackjack(context, state);
    return {
      embed: buildBlackjackEmbed(locale, finished, config, true, outcome),
      components: [],
      finished: true
    };
  }

  await saveBlackjackState(context, state);
  return {
    embed: buildBlackjackEmbed(locale, state, config, false),
    components: [buildBlackjackButtons(locale, guildId, userId)],
    finished: false
  };
}

export async function standBlackjack(
  context: BotContext,
  guildId: string,
  userId: string,
  locale: Locale
): Promise<{ embed: APIEmbed; finished: true }> {
  const state = await loadBlackjackState(context, guildId, userId);
  if (!state || state.finished) {
    throw new EconomyError('not_found');
  }
  if (state.userId !== userId) {
    throw new EconomyError('not_found');
  }

  while (handValue(state.dealerHand) < 17) {
    state.dealerHand.push(drawCard(state));
  }

  const config = await getEconomyConfig(context.prisma, guildId);
  const { state: finished, outcome } = await finishBlackjack(context, state);
  return {
    embed: buildBlackjackEmbed(locale, finished, config, true, outcome),
    finished: true
  };
}

export async function playGamble(
  context: BotContext,
  guildId: string,
  userId: string,
  amount: number
): Promise<{ won: boolean; balance: number }> {
  await assertEconomyEnabled(context.prisma, guildId);
  if (!Number.isInteger(amount) || amount <= 0) {
    throw new EconomyError('invalid_amount');
  }

  await deductBalance(context.prisma, guildId, userId, amount);
  const won = Math.random() < 0.5;
  if (won) {
    const updated = await addBalance(context.prisma, guildId, userId, amount * 2);
    return { won: true, balance: updated.balance };
  }
  const member = await context.prisma.memberEconomy.findUniqueOrThrow({
    where: { guildId_userId: { guildId, userId } }
  });
  return { won: false, balance: member.balance };
}

export async function playSlots(
  context: BotContext,
  guildId: string,
  userId: string,
  amount: number
): Promise<{ reels: string[]; multiplier: number; payout: number; balance: number }> {
  await assertEconomyEnabled(context.prisma, guildId);
  if (!Number.isInteger(amount) || amount <= 0) {
    throw new EconomyError('invalid_amount');
  }

  await deductBalance(context.prisma, guildId, userId, amount);

  const reels = [
    SLOT_SYMBOLS[randomInt(0, SLOT_SYMBOLS.length - 1)]!,
    SLOT_SYMBOLS[randomInt(0, SLOT_SYMBOLS.length - 1)]!,
    SLOT_SYMBOLS[randomInt(0, SLOT_SYMBOLS.length - 1)]!
  ];

  let multiplier = 0;
  if (reels[0] === reels[1] && reels[1] === reels[2]) {
    multiplier = reels[0] === '7️⃣' ? 10 : reels[0] === '💎' ? 5 : 3;
  } else if (reels[0] === reels[1] || reels[1] === reels[2] || reels[0] === reels[2]) {
    multiplier = 1.5;
  }

  const payout = Math.floor(amount * multiplier);
  let balance: number;
  if (payout > 0) {
    const updated = await addBalance(context.prisma, guildId, userId, payout);
    balance = updated.balance;
  } else {
    const member = await context.prisma.memberEconomy.findUniqueOrThrow({
      where: { guildId_userId: { guildId, userId } }
    });
    balance = member.balance;
  }

  return { reels, multiplier, payout, balance };
}

export type CoinflipChoice = 'heads' | 'tails';

export async function playCoinflipBet(
  context: BotContext,
  guildId: string,
  userId: string,
  amount: number,
  choice: CoinflipChoice
): Promise<{ result: CoinflipChoice; won: boolean; balance: number }> {
  await assertEconomyEnabled(context.prisma, guildId);
  if (!Number.isInteger(amount) || amount <= 0) {
    throw new EconomyError('invalid_amount');
  }

  await deductBalance(context.prisma, guildId, userId, amount);
  const result: CoinflipChoice = Math.random() < 0.5 ? 'heads' : 'tails';
  const won = result === choice;

  if (won) {
    const updated = await addBalance(context.prisma, guildId, userId, amount * 2);
    return { result, won: true, balance: updated.balance };
  }

  const member = await context.prisma.memberEconomy.findUniqueOrThrow({
    where: { guildId_userId: { guildId, userId } }
  });
  return { result, won: false, balance: member.balance };
}

export function parseBlackjackButtonCustomId(customId: string): {
  action: 'hit' | 'stand';
  guildId: string;
  userId: string;
} | null {
  if (!customId.startsWith(BLACKJACK_BUTTON_PREFIX)) {
    return null;
  }
  const parts = customId.slice(BLACKJACK_BUTTON_PREFIX.length).split(':');
  if (parts.length !== 3) {
    return null;
  }
  const [action, guildId, userId] = parts;
  if (action !== 'hit' && action !== 'stand') {
    return null;
  }
  if (!guildId || !userId) {
    return null;
  }
  return { action, guildId, userId };
}
