/**
 * Purchase Handlers
 * Handles store purchases, in-app purchases, energy refills, and time skips
 */

import type { PlayerSession } from '../game/PlayerSession.js';
import {
  handlePurchaseEnergyRefill as processEnergyRefill,
  handlePurchaseTimeSkip as processTimeSkip,
  getRefillTiers,
  getSkipTiers,
  validateIAPReceipt,
} from '../monetization/index.js';
import { purchaseItem, purchaseInAppItem } from '../services/shop/index.js';
import { config } from '../config.js';
import {
  checkAchievementsAsync,
  updateQuestProgress,
  trackMoneySpent,
  sendAchievementsUnlocked,
  sendQuestProgress,
} from '../services/retention/index.js';
import { resolvePayloadId } from './payloadHelpers.js';

interface InAppPurchasePayload {
  productId: string;
  receiptData?: string;
  transactionId?: string;
}

interface EnergyRefillPayload {
  refillType?: string;
  currentEnergy?: number;
  maxEnergy?: number;
}

interface TimeSkipPayload {
  skipType?: string;
}

export async function handlePurchaseItem(
  payload: unknown,
  session: PlayerSession
): Promise<void> {
  const itemId = resolvePayloadId(payload, 'itemId');
  const player = session.player;

  if (!itemId) {
    session.send({
      type: 'error',
      message: 'Failed to purchase item',
    });
    return;
  }

  const result = purchaseItem(player, itemId);

  if (result.success) {
    await session.savePlayer();
    session.sendPlayerObject();
    session.send({
      type: 'purchaseComplete',
      success: true,
      itemId,
      message: result.message,
    });

    // Track spending and update achievements/quests
    if (result.item) {
      trackMoneySpent(player.userId, result.item.price);
    }

    // Collection achievements key off the player's actual inventory size.
    // shop_manager.purchaseItem() has already pushed permanent items into
    // player.c.items by this point (and intentionally does NOT add energy-boost
    // consumables), so the post-purchase length is the correct count. Adding +1
    // here double-counted permanent items and broke the `first_purchase`
    // achievement (which requires itemCount === 1 on the very first buy).
    const itemCount = player.c.items?.length ?? 0;

    // Check purchase achievements
    const unlocked = await checkAchievementsAsync(
      player.userId,
      'purchase_item',
      {},
      { itemCount },
      {},
      player
    );
    sendAchievementsUnlocked(session, unlocked);

    // Update quest progress for buying items
    const quest = await updateQuestProgress(player.userId, 'buy_item', 1, player);
    if (quest) {
      sendQuestProgress(session, quest);
    }

    // Achievement/quest unlocks award diamonds onto the player (via
    // awardDiamonds). The earlier savePlayer() happened BEFORE those awards, so
    // persist + resync again to keep any granted diamonds / mutated state
    // (they would otherwise be lost on reconnect and never reach the client).
    await session.savePlayer();
    session.sendPlayerObject();
  } else {
    session.send({
      type: 'error',
      message: result.message || 'Failed to purchase item',
    });
  }
}

export async function handlePurchaseInAppItem(
  payload: unknown,
  session: PlayerSession
): Promise<void> {
  const productId = resolvePayloadId(payload, 'productId');
  const player = session.player;

  if (!productId) {
    session.send({
      type: 'error',
      message: 'Failed to process in-app purchase',
    });
    return;
  }

  // When server-side receipt validation is enabled (production, after the iOS
  // client ships receipt sending and APPLE_SHARED_SECRET is provisioned), the
  // receipt MUST be validated with Apple before any diamonds are granted.
  // Defaults OFF so dev/test/current-client behavior is unchanged.
  if (config.IAP_VALIDATION_ENABLED) {
    await handleValidatedInAppPurchase(payload, productId, session);
    return;
  }

  // Note: In production, receiptData should be validated with Apple/Google
  const success = purchaseInAppItem(player, productId);

  if (success) {
    await session.savePlayer();
    session.sendPlayerObject();
    session.send({
      type: 'inAppPurchaseComplete',
      success: true,
      productId,
      message: 'Purchase completed successfully',
    });
  } else {
    session.send({
      type: 'error',
      message: 'Failed to process in-app purchase',
    });
  }
}

/**
 * Receipt-validated in-app purchase path (used only when
 * config.IAP_VALIDATION_ENABLED is true).
 *
 * Fails closed: if the client did not include a receipt + transaction id, no
 * diamonds are granted. On a valid receipt, validateIAPReceipt awards diamonds
 * itself (and enforces idempotency), so we do NOT also call purchaseInAppItem
 * here -- doing so would double-grant.
 */
async function handleValidatedInAppPurchase(
  payload: unknown,
  productId: string,
  session: PlayerSession
): Promise<void> {
  const player = session.player;
  const data = (payload && typeof payload === 'object' ? payload : {}) as InAppPurchasePayload;
  const receiptData = typeof data.receiptData === 'string' ? data.receiptData : '';
  const transactionId = typeof data.transactionId === 'string' ? data.transactionId : '';

  if (!receiptData || !transactionId) {
    session.send({
      type: 'error',
      message: 'Purchase receipt is required for validation',
      errorCode: 'MISSING_RECEIPT',
    });
    return;
  }

  const result = await validateIAPReceipt(player.userId, receiptData, transactionId, productId);

  if (result.success) {
    await session.savePlayer();
    session.sendPlayerObject();
    session.send({
      type: 'inAppPurchaseComplete',
      success: true,
      productId,
      diamondsAwarded: result.diamondsAwarded ?? 0,
      message: result.message || 'Purchase completed successfully',
    });
  } else {
    session.send({
      type: 'error',
      message: result.message || 'Failed to process in-app purchase',
      errorCode: result.errorCode,
    });
  }
}

export async function handlePurchaseEnergyRefill(
  payload: unknown,
  session: PlayerSession
): Promise<void> {
  const player = session.player;

  // Pass the authoritative player so the refill reads/writes REAL energy +
  // diamonds (not the client-supplied values, which made it a no-op).
  processEnergyRefill(
    player,
    payload as EnergyRefillPayload,
    (_playerId, message) => {
      session.send(message);
    }
  );

  session.sendPlayerObject();
}

export async function handlePurchaseTimeSkip(
  payload: unknown,
  session: PlayerSession
): Promise<void> {
  const player = session.player;

  // Pass the authoritative player so the skip deducts REAL diamonds and applies
  // the simulated outcome + advances the clock (it previously always failed for
  // want of a client playerState and wrote nothing).
  processTimeSkip(
    player,
    payload as TimeSkipPayload,
    (_playerId, message) => {
      session.send(message);
    }
  );

  session.sendPlayerObject();
}

export async function handleGetEnergyTiers(
  _payload: unknown,
  session: PlayerSession
): Promise<void> {
  const tiers = getRefillTiers();

  // Python: {'type': 'energyRefillTiers', 'tiers': tiers}
  session.send({
    type: 'energyRefillTiers',
    tiers: tiers,
  });
}

export async function handleGetTimeSkipTiers(
  _payload: unknown,
  session: PlayerSession
): Promise<void> {
  const tiers = getSkipTiers();

  // Python: {'type': 'timeSkipTiers', 'tiers': tiers}
  session.send({
    type: 'timeSkipTiers',
    tiers: tiers,
  });
}
