/**
 * Purchase Validation and Anti-Cheat
 * Handles rate limiting, idempotency, and IAP validation.
 * Ported from Python monetization/validation.py
 */

import crypto from 'crypto';
import { getConnection } from '../database/pool.js';
import { awardDiamonds, getDiamondBalance } from './diamondEconomy.js';

// ============================================================================
// Rate Limiting
// ============================================================================

interface RateLimitEntry {
  calls: number[];
}

const rateLimitStore: Map<string, RateLimitEntry> = new Map();

export interface RateLimitResult {
  success: boolean;
  message?: string;
  errorCode?: string;
}

/**
 * Rate limiting decorator factory
 * @param maxCalls Maximum calls allowed in time window
 * @param timeWindow Time window in seconds
 */
export function createRateLimiter(maxCalls: number, timeWindow: number) {
  const callTracker: Map<string, number[]> = new Map();

  return function rateLimit<T extends (...args: unknown[]) => unknown>(
    fn: T
  ): (...args: Parameters<T>) => ReturnType<T> | RateLimitResult {
    return function (this: unknown, ...args: Parameters<T>) {
      // Extract player ID from first argument
      const playerId = String(args[0]);
      const now = Date.now() / 1000; // Convert to seconds

      // Clean old entries
      let calls = callTracker.get(playerId) || [];
      calls = calls.filter((t) => now - t < timeWindow);
      callTracker.set(playerId, calls);

      // Check rate limit
      if (calls.length >= maxCalls) {
        console.warn(`Rate limit exceeded for player ${playerId}`);
        return {
          success: false,
          message: 'Too many requests. Please wait and try again.',
          errorCode: 'RATE_LIMIT_EXCEEDED',
        } as RateLimitResult;
      }

      // Add this call
      calls.push(now);
      callTracker.set(playerId, calls);

      return fn.apply(this, args) as ReturnType<T>;
    };
  };
}

/**
 * Check if a player is rate limited for a specific action
 */
export function checkRateLimit(
  playerId: string,
  action: string,
  maxCalls: number,
  timeWindowSeconds: number
): RateLimitResult {
  const key = `${playerId}:${action}`;
  const now = Date.now() / 1000;

  let entry = rateLimitStore.get(key);
  if (!entry) {
    entry = { calls: [] };
    rateLimitStore.set(key, entry);
  }

  // Clean old entries
  entry.calls = entry.calls.filter((t) => now - t < timeWindowSeconds);

  // Check limit
  if (entry.calls.length >= maxCalls) {
    return {
      success: false,
      message: 'Too many requests. Please wait and try again.',
      errorCode: 'RATE_LIMIT_EXCEEDED',
    };
  }

  // Record this call
  entry.calls.push(now);

  return { success: true };
}

// ============================================================================
// Idempotency Manager
// ============================================================================

export interface TransactionData {
  transactionId: string;
  productId: string;
  validationStatus: string;
  [key: string]: unknown;
}

export class IdempotencyManager {
  /**
   * Generate idempotency key from transaction
   */
  static generateKey(playerId: string, transactionId: string): string {
    return crypto
      .createHash('sha256')
      .update(`${playerId}:${transactionId}`)
      .digest('hex');
  }

  /**
   * Check if transaction already processed
   */
  static async checkProcessed(idempotencyKey: string): Promise<boolean> {
    let connection;
    try {
      connection = await getConnection();
      const [rows] = await connection.execute(
        'SELECT COUNT(*) as count FROM processed_transactions WHERE idempotency_key = ?',
        [idempotencyKey]
      );

      const result = rows as Array<{ count: number }>;
      return result[0]?.count > 0;
    } catch (error) {
      console.error('Error checking idempotency key:', error);
      return false;
    } finally {
      if (connection) {
        connection.release();
      }
    }
  }

  /**
   * Mark transaction as processed
   */
  static async markProcessed(
    idempotencyKey: string,
    playerId: string,
    transactionData: TransactionData
  ): Promise<void> {
    let connection;
    try {
      connection = await getConnection();
      await connection.execute(
        `INSERT INTO processed_transactions
         (idempotency_key, player_id, transaction_data)
         VALUES (?, ?, ?)`,
        [idempotencyKey, playerId, JSON.stringify(transactionData)]
      );
    } catch (error) {
      console.error('Error marking transaction as processed:', error);
    } finally {
      if (connection) {
        connection.release();
      }
    }
  }
}

// ============================================================================
// IAP Validation
// ============================================================================

export interface IAPValidationResult {
  success: boolean;
  message: string;
  errorCode?: string;
  diamondsAwarded?: number;
}

// Product ID to diamond mapping
export const DIAMOND_PACKAGES: Record<string, number> = {
  'com.baolife.diamonds.small': 100,
  'com.baolife.diamonds.medium': 300,
  'com.baolife.diamonds.large': 750,
  'com.baolife.diamonds.mega': 2000,
  // iOS product IDs
  diamond1: 100,
  diamond2: 210,
  diamond3: 330,
  diamond4: 460,
  diamond5: 600,
};

/**
 * Validate IAP purchase with Apple
 */
// Apple receipt status codes
const APPLE_STATUS = {
  VALID: 0,
  MALFORMED_RECEIPT: 21002,
  COULD_NOT_AUTHENTICATE: 21003,
  SHARED_SECRET_MISMATCH: 21004,
  RECEIPT_SERVER_UNAVAILABLE: 21005,
  SUBSCRIPTION_EXPIRED: 21006,
  SANDBOX_RECEIPT_ON_PRODUCTION: 21007,
  PRODUCTION_RECEIPT_ON_SANDBOX: 21008,
} as const;

interface AppleReceiptResponse {
  status: number;
  receipt?: {
    bundle_id: string;
    in_app: Array<{
      product_id: string;
      transaction_id: string;
      original_transaction_id: string;
      purchase_date_ms: string;
    }>;
  };
  environment?: 'Production' | 'Sandbox';
}

/**
 * Validate receipt with Apple's servers
 */
async function validateWithApple(
  receiptData: string,
  useSandbox: boolean
): Promise<AppleReceiptResponse> {
  const url = useSandbox
    ? 'https://sandbox.itunes.apple.com/verifyReceipt'
    : 'https://buy.itunes.apple.com/verifyReceipt';

  // App Store Connect shared secret for receipt validation
  const sharedSecret = process.env.APPLE_SHARED_SECRET || '';

  const requestBody = {
    'receipt-data': receiptData,
    password: sharedSecret,
    'exclude-old-transactions': true,
  };

  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(requestBody),
  });

  if (!response.ok) {
    throw new Error(`Apple server returned status ${response.status}`);
  }

  return response.json() as Promise<AppleReceiptResponse>;
}

export async function validateIAPReceipt(
  playerId: string,
  receiptData: string,
  transactionId: string,
  productId: string
): Promise<IAPValidationResult> {
  // Generate idempotency key
  const idemKey = IdempotencyManager.generateKey(playerId, transactionId);

  // Check if already processed
  if (await IdempotencyManager.checkProcessed(idemKey)) {
    console.warn(`Duplicate IAP transaction attempted: ${transactionId}`);
    return {
      success: false,
      message: 'Transaction already processed',
      errorCode: 'DUPLICATE_TRANSACTION',
    };
  }

  try {
    // First try production, then sandbox if needed
    let validationResult = await validateWithApple(receiptData, false);

    // If production returns sandbox receipt error, retry with sandbox
    if (validationResult.status === APPLE_STATUS.SANDBOX_RECEIPT_ON_PRODUCTION) {
      console.log('Receipt is from sandbox, retrying with sandbox server');
      validationResult = await validateWithApple(receiptData, true);
    }

    if (validationResult.status === APPLE_STATUS.VALID) {
      // Verify the transaction exists in the receipt
      const receipt = validationResult.receipt;
      if (!receipt || !receipt.in_app) {
        return {
          success: false,
          message: 'Invalid receipt format',
          errorCode: 'INVALID_RECEIPT',
        };
      }

      // Find the matching transaction
      const transaction = receipt.in_app.find(
        (t) => t.transaction_id === transactionId || t.original_transaction_id === transactionId
      );

      if (!transaction) {
        return {
          success: false,
          message: 'Transaction not found in receipt',
          errorCode: 'TRANSACTION_NOT_FOUND',
        };
      }

      // Verify product ID matches
      if (transaction.product_id !== productId) {
        console.warn(
          `Product ID mismatch: expected ${productId}, got ${transaction.product_id}`
        );
        return {
          success: false,
          message: 'Product ID mismatch',
          errorCode: 'PRODUCT_MISMATCH',
        };
      }

      // Mark as processed
      await IdempotencyManager.markProcessed(idemKey, playerId, {
        transactionId,
        productId,
        validationStatus: 'success',
        environment: validationResult.environment,
      });

      // Award diamonds based on product ID
      const diamondAmount = DIAMOND_PACKAGES[productId] || 0;

      if (diamondAmount > 0) {
        awardDiamonds(playerId, `iap_purchase_${productId}`, diamondAmount);

        console.log(
          `IAP validated for player ${playerId}: ${diamondAmount} diamonds (${validationResult.environment})`
        );

        return {
          success: true,
          diamondsAwarded: diamondAmount,
          message: `Successfully purchased ${diamondAmount} diamonds`,
        };
      } else {
        return {
          success: false,
          message: 'Invalid product ID',
          errorCode: 'INVALID_PRODUCT',
        };
      }
    } else {
      // Handle specific error codes
      let errorMessage = 'Purchase validation failed';
      let errorCode = 'VALIDATION_FAILED';

      switch (validationResult.status) {
        case APPLE_STATUS.MALFORMED_RECEIPT:
          errorMessage = 'Receipt data is malformed';
          errorCode = 'MALFORMED_RECEIPT';
          break;
        case APPLE_STATUS.COULD_NOT_AUTHENTICATE:
          errorMessage = 'Receipt could not be authenticated';
          errorCode = 'AUTH_FAILED';
          break;
        case APPLE_STATUS.RECEIPT_SERVER_UNAVAILABLE:
          errorMessage = 'Apple receipt server is temporarily unavailable';
          errorCode = 'SERVER_UNAVAILABLE';
          break;
        case APPLE_STATUS.SUBSCRIPTION_EXPIRED:
          errorMessage = 'Subscription has expired';
          errorCode = 'SUBSCRIPTION_EXPIRED';
          break;
      }

      console.error(
        `IAP validation failed for player ${playerId}: status ${validationResult.status} - ${errorMessage}`
      );
      return {
        success: false,
        message: errorMessage,
        errorCode,
      };
    }
  } catch (error) {
    console.error(`Error validating IAP for player ${playerId}:`, error);
    return {
      success: false,
      message: 'Server error during validation',
      errorCode: 'SERVER_ERROR',
    };
  }
}

// ============================================================================
// WebSocket Handler
// ============================================================================

export interface ValidatePurchaseMessage {
  receiptData?: string;
  receipt_data?: string;
  transactionId?: string;
  transaction_id?: string;
  productId?: string;
  product_id?: string;
}

export type SendToClientFn = (
  playerId: string,
  message: Record<string, unknown>
) => void;

/**
 * WebSocket handler for IAP validation
 */
export async function handleValidatePurchase(
  playerId: string,
  messageData: ValidatePurchaseMessage,
  sendToClient: SendToClientFn
): Promise<void> {
  const receiptData = messageData.receiptData || messageData.receipt_data;
  const transactionId = messageData.transactionId || messageData.transaction_id;
  const productId = messageData.productId || messageData.product_id;

  if (!receiptData || !transactionId || !productId) {
    sendToClient(playerId, {
      type: 'error',
      errorCode: 'INVALID_REQUEST',
      message: 'Missing required fields for IAP validation',
    });
    return;
  }

  const result = await validateIAPReceipt(
    playerId,
    receiptData,
    transactionId,
    productId
  );

  if (result.success) {
    sendToClient(playerId, {
      type: 'purchaseValidated',
      success: true,
      diamondsAwarded: result.diamondsAwarded,
      message: result.message,
    });

    // Send diamond balance update
    const newBalance = getDiamondBalance(playerId);

    sendToClient(playerId, {
      type: 'diamondUpdate',
      balance: newBalance,
    });
  } else {
    sendToClient(playerId, {
      type: 'error',
      errorCode: result.errorCode || 'PURCHASE_FAILED',
      message: result.message,
    });
  }
}

// ============================================================================
// Export
// ============================================================================

export const validationManager = {
  createRateLimiter,
  checkRateLimit,
  IdempotencyManager,
  validateIAPReceipt,
  handleValidatePurchase,
  DIAMOND_PACKAGES,
};
