/**
 * Credit Management Service
 *
 * Handles credit allocation, deduction, and balance tracking for user accounts
 */

import { PrismaClient, CreditTransactionType } from '@prisma/client';
import { logger } from '../utils/logger';
import { creditsAllocatedTotal, creditsDeductedTotal } from '../utils/metrics';
import type { CreditTransaction as CreditTransactionRow } from '@prisma/client';
import { CreditTransaction, CreditTransactionType as ApiCreditTransactionType } from '../types';

// Export prisma instance for testing
export const prisma = new PrismaClient();

const TRANSACTION_TYPES: Record<CreditTransactionType, ApiCreditTransactionType> = {
  [CreditTransactionType.allocation]: ApiCreditTransactionType.ALLOCATION,
  [CreditTransactionType.deduction]: ApiCreditTransactionType.DEDUCTION,
  [CreditTransactionType.refund]: ApiCreditTransactionType.REFUND,
};

function toCreditTransaction(row: CreditTransactionRow): CreditTransaction {
  return {
    id: row.id,
    userId: row.user_id,
    type: TRANSACTION_TYPES[row.type],
    amount: row.amount,
    balanceAfter: row.balance_after,
    description: row.description,
    relatedJobId: row.related_job_id ?? undefined,
    relatedPaymentId: row.related_payment_id ?? undefined,
    createdAt: row.created_at,
  };
}

/**
 * Get user's current credit balance
 *
 * Retrieves the most recent balance_after value from credit transactions
 * If no transactions exist, returns 0
 */
export async function getCurrentBalance(userId: string): Promise<number> {
  try {
    const latestTransaction = await prisma.creditTransaction.findFirst({
      where: { user_id: userId },
      orderBy: { created_at: 'desc' },
      select: { balance_after: true },
    });

    return latestTransaction?.balance_after ?? 0;
  } catch (error) {
    logger.error('Failed to get current balance', { userId, error });
    throw error;
  }
}

/**
 * Allocate credits to a user account
 *
 * Used when:
 * - Subscription is activated or renewed
 * - Manual credit grants by admin
 * - Promotional credits
 */
export async function allocateCredits(
  userId: string,
  amount: number,
  description: string,
  relatedPaymentId?: string
): Promise<CreditTransaction> {
  if (amount <= 0) {
    throw new Error('Allocation amount must be positive');
  }

  try {
    // Use transaction to ensure atomicity
    const result = await prisma.$transaction(async (tx) => {
      // Get current balance
      const currentBalance = await getCurrentBalance(userId);
      const newBalance = currentBalance + amount;

      // Create credit transaction record
      const transaction = await tx.creditTransaction.create({
        data: {
          user_id: userId,
          type: CreditTransactionType.allocation,
          amount,
          balance_after: newBalance,
          description,
          related_payment_id: relatedPaymentId,
        },
      });

      logger.info('Credits allocated', {
        userId,
        amount,
        balanceAfter: newBalance,
        description,
        transactionId: transaction.id,
      });

      // Track metrics
      creditsAllocatedTotal.inc({ tier: 'unknown' }, amount);

      return transaction;
    });

    return toCreditTransaction(result);
  } catch (error) {
    logger.error('Failed to allocate credits', {
      userId,
      amount,
      description,
      error,
    });
    throw error;
  }
}

/**
 * Deduct credits from a user account
 *
 * Used when:
 * - Translation job is completed
 * - Any credit-consuming operation
 *
 * Throws error if insufficient credits
 */
export async function deductCredits(
  userId: string,
  amount: number,
  description: string,
  relatedJobId?: string
): Promise<CreditTransaction> {
  if (amount <= 0) {
    throw new Error('Deduction amount must be positive');
  }

  try {
    // Use transaction to ensure atomicity
    const result = await prisma.$transaction(async (tx) => {
      // Get current balance
      const currentBalance = await getCurrentBalance(userId);

      // Check sufficient credits
      if (currentBalance < amount) {
        throw new Error(
          `Insufficient credits. Required: ${amount}, Available: ${currentBalance}`
        );
      }

      const newBalance = currentBalance - amount;

      // Create credit transaction record (negative amount for deduction)
      const transaction = await tx.creditTransaction.create({
        data: {
          user_id: userId,
          type: CreditTransactionType.deduction,
          amount: -amount, // Negative amount for deductions
          balance_after: newBalance,
          description,
          related_job_id: relatedJobId,
        },
      });

      logger.info('Credits deducted', {
        userId,
        amount,
        balanceAfter: newBalance,
        description,
        transactionId: transaction.id,
      });

      // Track metrics
      creditsDeductedTotal.inc({ tier: 'unknown', model: 'unknown' }, amount);

      return transaction;
    });

    return toCreditTransaction(result);
  } catch (error) {
    logger.error('Failed to deduct credits', {
      userId,
      amount,
      description,
      error,
    });
    throw error;
  }
}

/**
 * Refund credits to a user account
 *
 * Used when:
 * - Translation job fails
 * - Job is cancelled
 * - Manual refund by admin
 */
export async function refundCredits(
  userId: string,
  amount: number,
  description: string,
  relatedJobId?: string
): Promise<CreditTransaction> {
  if (amount <= 0) {
    throw new Error('Refund amount must be positive');
  }

  try {
    // Use transaction to ensure atomicity
    const result = await prisma.$transaction(async (tx) => {
      // Get current balance
      const currentBalance = await getCurrentBalance(userId);
      const newBalance = currentBalance + amount;

      // Create credit transaction record
      const transaction = await tx.creditTransaction.create({
        data: {
          user_id: userId,
          type: CreditTransactionType.refund,
          amount,
          balance_after: newBalance,
          description,
          related_job_id: relatedJobId,
        },
      });

      logger.info('Credits refunded', {
        userId,
        amount,
        balanceAfter: newBalance,
        description,
        transactionId: transaction.id,
      });

      return transaction;
    });

    return toCreditTransaction(result);
  } catch (error) {
    logger.error('Failed to refund credits', {
      userId,
      amount,
      description,
      error,
    });
    throw error;
  }
}

/**
 * Check if user has sufficient credits for an operation
 *
 * Returns true if user has enough credits, false otherwise
 */
export async function hasSufficientCredits(
  userId: string,
  requiredAmount: number
): Promise<boolean> {
  try {
    const currentBalance = await getCurrentBalance(userId);
    return currentBalance >= requiredAmount;
  } catch (error) {
    logger.error('Failed to check credit sufficiency', {
      userId,
      requiredAmount,
      error,
    });
    throw error;
  }
}

/**
 * Get credit transaction history for a user
 *
 * Returns paginated list of credit transactions
 */
export async function getCreditHistory(
  userId: string,
  limit: number = 50,
  offset: number = 0
): Promise<CreditTransaction[]> {
  try {
    const transactions = await prisma.creditTransaction.findMany({
      where: { user_id: userId },
      orderBy: { created_at: 'desc' },
      take: limit,
      skip: offset,
    });

    return transactions.map(toCreditTransaction);
  } catch (error) {
    logger.error('Failed to get credit history', { userId, error });
    throw error;
  }
}

/**
 * Calculate credit cost for a translation job
 *
 * Characters are the credit unit (1 character = 1 credit)
 */
export function calculateCreditCost(charactersUsed: number): number {
  return charactersUsed;
}
