/**
 * Investment Transaction Service
 * Manages deposits, withdrawals, and distributions
 */

import { v4 as uuidv4 } from 'uuid';
import { accountService } from './account.service';

// ============================================================================
// Types
// ============================================================================

export type TransactionType = 'deposit' | 'withdrawal' | 'earning' | 'fee' | 'distribution';
export type TransactionStatus = 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled';
export type WithdrawalStatus = 'pending' | 'processing' | 'completed' | 'rejected';

export interface Transaction {
  id: string;
  accountId: string;
  userId: string;
  type: TransactionType;
  status: TransactionStatus;
  amount: number;
  balanceBefore: number;
  balanceAfter: number;
  stripePaymentId: string | null;
  description: string;
  processedAt: Date | null;
  createdAt: Date;
}

export interface WithdrawalRequest {
  id: string;
  accountId: string;
  userId: string;
  amount: number;
  status: WithdrawalStatus;
  bankInfo: {
    bankName: string;
    accountNumber: string;
    routingNumber: string;
    accountHolderName: string;
  } | null;
  cryptoInfo: {
    network: string;
    address: string;
  } | null;
  rejectionReason: string | null;
  requestedAt: Date;
  processedAt: Date | null;
  completedAt: Date | null;
}

export interface Distribution {
  id: string;
  accountId: string;
  userId: string;
  periodStart: Date;
  periodEnd: Date;
  grossEarnings: number;
  performanceFee: number;
  netEarnings: number;
  status: 'pending' | 'distributed';
  distributedAt: Date | null;
  createdAt: Date;
}

export interface CreateDepositInput {
  accountId: string;
  amount: number;
  stripePaymentId?: string;
}

export interface CreateWithdrawalInput {
  accountId: string;
  amount: number;
  bankInfo?: {
    bankName: string;
    accountNumber: string;
    routingNumber: string;
    accountHolderName: string;
  };
  cryptoInfo?: {
    network: string;
    address: string;
  };
}

// ============================================================================
// In-Memory Storage
// ============================================================================

const transactions: Map<string, Transaction> = new Map();
const withdrawalRequests: Map<string, WithdrawalRequest> = new Map();
const distributions: Map<string, Distribution> = new Map();

// ============================================================================
// Transaction Service
// ============================================================================

class TransactionService {
  // ==========================================================================
  // Transactions
  // ==========================================================================

  /**
   * Get transactions for an account
   */
  async getAccountTransactions(
    accountId: string,
    options: {
      type?: TransactionType;
      status?: TransactionStatus;
      limit?: number;
      offset?: number;
    } = {}
  ): Promise<{ transactions: Transaction[]; total: number }> {
    let accountTransactions = Array.from(transactions.values())
      .filter((t) => t.accountId === accountId)
      .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());

    if (options.type) {
      accountTransactions = accountTransactions.filter((t) => t.type === options.type);
    }

    if (options.status) {
      accountTransactions = accountTransactions.filter((t) => t.status === options.status);
    }

    const total = accountTransactions.length;

    if (options.offset) {
      accountTransactions = accountTransactions.slice(options.offset);
    }

    if (options.limit) {
      accountTransactions = accountTransactions.slice(0, options.limit);
    }

    return { transactions: accountTransactions, total };
  }

  /**
   * Get transaction by ID
   */
  async getTransactionById(transactionId: string): Promise<Transaction | null> {
    return transactions.get(transactionId) || null;
  }

  /**
   * Create a deposit transaction
   */
  async createDeposit(input: CreateDepositInput): Promise<Transaction> {
    const account = await accountService.getAccountById(input.accountId);
    if (!account) {
      throw new Error(`Account not found: ${input.accountId}`);
    }

    if (input.amount <= 0) {
      throw new Error('Deposit amount must be positive');
    }

    const balanceBefore = account.balance;

    // Process deposit
    await accountService.deposit(input.accountId, input.amount);

    const transaction: Transaction = {
      id: uuidv4(),
      accountId: input.accountId,
      userId: account.userId,
      type: 'deposit',
      status: 'completed',
      amount: input.amount,
      balanceBefore,
      balanceAfter: balanceBefore + input.amount,
      stripePaymentId: input.stripePaymentId || null,
      description: `Deposit of $${input.amount.toFixed(2)}`,
      processedAt: new Date(),
      createdAt: new Date(),
    };

    transactions.set(transaction.id, transaction);
    return transaction;
  }

  /**
   * Create a pending deposit (for Stripe webhooks)
   */
  async createPendingDeposit(
    accountId: string,
    amount: number,
    stripePaymentId: string
  ): Promise<Transaction> {
    const account = await accountService.getAccountById(accountId);
    if (!account) {
      throw new Error(`Account not found: ${accountId}`);
    }

    const transaction: Transaction = {
      id: uuidv4(),
      accountId,
      userId: account.userId,
      type: 'deposit',
      status: 'pending',
      amount,
      balanceBefore: account.balance,
      balanceAfter: account.balance,
      stripePaymentId,
      description: `Pending deposit of $${amount.toFixed(2)}`,
      processedAt: null,
      createdAt: new Date(),
    };

    transactions.set(transaction.id, transaction);
    return transaction;
  }

  /**
   * Complete a pending deposit
   */
  async completeDeposit(transactionId: string): Promise<Transaction> {
    const transaction = transactions.get(transactionId);
    if (!transaction) {
      throw new Error(`Transaction not found: ${transactionId}`);
    }

    if (transaction.status !== 'pending') {
      throw new Error('Transaction is not pending');
    }

    // Process deposit
    await accountService.deposit(transaction.accountId, transaction.amount);

    // Get updated account
    const account = await accountService.getAccountById(transaction.accountId);

    transaction.status = 'completed';
    transaction.balanceAfter = account!.balance;
    transaction.processedAt = new Date();
    transaction.description = `Deposit of $${transaction.amount.toFixed(2)}`;

    return transaction;
  }

  // ==========================================================================
  // Withdrawals
  // ==========================================================================

  /**
   * Get withdrawal requests for a user
   */
  async getUserWithdrawals(
    userId: string,
    status?: WithdrawalStatus
  ): Promise<WithdrawalRequest[]> {
    let userWithdrawals = Array.from(withdrawalRequests.values())
      .filter((w) => w.userId === userId)
      .sort((a, b) => b.requestedAt.getTime() - a.requestedAt.getTime());

    if (status) {
      userWithdrawals = userWithdrawals.filter((w) => w.status === status);
    }

    return userWithdrawals;
  }

  /**
   * Get withdrawal request by ID
   */
  async getWithdrawalById(withdrawalId: string): Promise<WithdrawalRequest | null> {
    return withdrawalRequests.get(withdrawalId) || null;
  }

  /**
   * Create a withdrawal request
   */
  async createWithdrawal(
    userId: string,
    input: CreateWithdrawalInput
  ): Promise<WithdrawalRequest> {
    const account = await accountService.getAccountById(input.accountId);
    if (!account) {
      throw new Error(`Account not found: ${input.accountId}`);
    }

    if (account.userId !== userId) {
      throw new Error('Unauthorized');
    }

    if (account.status !== 'active') {
      throw new Error('Cannot withdraw from inactive account');
    }

    if (input.amount <= 0) {
      throw new Error('Withdrawal amount must be positive');
    }

    if (input.amount > account.balance) {
      throw new Error('Insufficient balance');
    }

    if (!input.bankInfo && !input.cryptoInfo) {
      throw new Error('Bank or crypto information is required');
    }

    // Check daily withdrawal limit
    const dailyLimit = 10000;
    const todayWithdrawals = Array.from(withdrawalRequests.values())
      .filter((w) => {
        const today = new Date();
        today.setHours(0, 0, 0, 0);
        return (
          w.userId === userId &&
          w.status !== 'rejected' &&
          w.requestedAt >= today
        );
      })
      .reduce((sum, w) => sum + w.amount, 0);

    if (todayWithdrawals + input.amount > dailyLimit) {
      throw new Error(`Daily withdrawal limit of $${dailyLimit} exceeded`);
    }

    const withdrawal: WithdrawalRequest = {
      id: uuidv4(),
      accountId: input.accountId,
      userId,
      amount: input.amount,
      status: 'pending',
      bankInfo: input.bankInfo || null,
      cryptoInfo: input.cryptoInfo || null,
      rejectionReason: null,
      requestedAt: new Date(),
      processedAt: null,
      completedAt: null,
    };

    withdrawalRequests.set(withdrawal.id, withdrawal);

    // Create pending transaction
    const transaction: Transaction = {
      id: uuidv4(),
      accountId: input.accountId,
      userId,
      type: 'withdrawal',
      status: 'pending',
      amount: -input.amount,
      balanceBefore: account.balance,
      balanceAfter: account.balance,
      stripePaymentId: null,
      description: `Pending withdrawal of $${input.amount.toFixed(2)}`,
      processedAt: null,
      createdAt: new Date(),
    };

    transactions.set(transaction.id, transaction);

    return withdrawal;
  }

  /**
   * Process a withdrawal (admin)
   */
  async processWithdrawal(withdrawalId: string): Promise<WithdrawalRequest> {
    const withdrawal = withdrawalRequests.get(withdrawalId);
    if (!withdrawal) {
      throw new Error(`Withdrawal not found: ${withdrawalId}`);
    }

    if (withdrawal.status !== 'pending') {
      throw new Error('Withdrawal is not pending');
    }

    withdrawal.status = 'processing';
    withdrawal.processedAt = new Date();

    return withdrawal;
  }

  /**
   * Complete a withdrawal (admin)
   */
  async completeWithdrawal(withdrawalId: string): Promise<WithdrawalRequest> {
    const withdrawal = withdrawalRequests.get(withdrawalId);
    if (!withdrawal) {
      throw new Error(`Withdrawal not found: ${withdrawalId}`);
    }

    if (withdrawal.status !== 'processing') {
      throw new Error('Withdrawal is not being processed');
    }

    const account = await accountService.getAccountById(withdrawal.accountId);
    if (!account) {
      throw new Error('Account not found');
    }

    // Deduct from account
    account.balance -= withdrawal.amount;
    account.totalWithdrawn += withdrawal.amount;
    account.updatedAt = new Date();

    // Update withdrawal
    withdrawal.status = 'completed';
    withdrawal.completedAt = new Date();

    // Update transaction
    const pendingTx = Array.from(transactions.values()).find(
      (t) =>
        t.accountId === withdrawal.accountId &&
        t.type === 'withdrawal' &&
        t.status === 'pending' &&
        t.amount === -withdrawal.amount
    );

    if (pendingTx) {
      pendingTx.status = 'completed';
      pendingTx.balanceAfter = account.balance;
      pendingTx.processedAt = new Date();
      pendingTx.description = `Withdrawal of $${withdrawal.amount.toFixed(2)}`;
    }

    return withdrawal;
  }

  /**
   * Reject a withdrawal (admin)
   */
  async rejectWithdrawal(
    withdrawalId: string,
    reason: string
  ): Promise<WithdrawalRequest> {
    const withdrawal = withdrawalRequests.get(withdrawalId);
    if (!withdrawal) {
      throw new Error(`Withdrawal not found: ${withdrawalId}`);
    }

    if (withdrawal.status === 'completed') {
      throw new Error('Cannot reject completed withdrawal');
    }

    withdrawal.status = 'rejected';
    withdrawal.rejectionReason = reason;
    withdrawal.processedAt = new Date();

    // Cancel transaction
    const pendingTx = Array.from(transactions.values()).find(
      (t) =>
        t.accountId === withdrawal.accountId &&
        t.type === 'withdrawal' &&
        t.status === 'pending' &&
        t.amount === -withdrawal.amount
    );

    if (pendingTx) {
      pendingTx.status = 'cancelled';
      pendingTx.description = `Withdrawal rejected: ${reason}`;
    }

    return withdrawal;
  }

  // ==========================================================================
  // Distributions
  // ==========================================================================

  /**
   * Get distributions for an account
   */
  async getAccountDistributions(accountId: string): Promise<Distribution[]> {
    return Array.from(distributions.values())
      .filter((d) => d.accountId === accountId)
      .sort((a, b) => b.periodEnd.getTime() - a.periodEnd.getTime());
  }

  /**
   * Create a distribution
   */
  async createDistribution(
    accountId: string,
    periodStart: Date,
    periodEnd: Date,
    grossEarnings: number,
    performanceFeePercent: number
  ): Promise<Distribution> {
    const account = await accountService.getAccountById(accountId);
    if (!account) {
      throw new Error(`Account not found: ${accountId}`);
    }

    const performanceFee = grossEarnings * (performanceFeePercent / 100);
    const netEarnings = grossEarnings - performanceFee;

    const distribution: Distribution = {
      id: uuidv4(),
      accountId,
      userId: account.userId,
      periodStart,
      periodEnd,
      grossEarnings,
      performanceFee,
      netEarnings,
      status: 'pending',
      distributedAt: null,
      createdAt: new Date(),
    };

    distributions.set(distribution.id, distribution);
    return distribution;
  }

  /**
   * Distribute earnings
   */
  async distributeEarnings(distributionId: string): Promise<Distribution> {
    const distribution = distributions.get(distributionId);
    if (!distribution) {
      throw new Error(`Distribution not found: ${distributionId}`);
    }

    if (distribution.status === 'distributed') {
      throw new Error('Distribution already completed');
    }

    // Record earnings in account
    await accountService.recordEarnings(
      distribution.accountId,
      distribution.grossEarnings,
      distribution.performanceFee
    );

    // Create transactions
    const account = await accountService.getAccountById(distribution.accountId);

    if (distribution.netEarnings !== 0) {
      const earningTx: Transaction = {
        id: uuidv4(),
        accountId: distribution.accountId,
        userId: distribution.userId,
        type: 'earning',
        status: 'completed',
        amount: distribution.netEarnings,
        balanceBefore: account!.balance - distribution.netEarnings,
        balanceAfter: account!.balance,
        stripePaymentId: null,
        description: `Earnings for ${distribution.periodStart.toLocaleDateString()} - ${distribution.periodEnd.toLocaleDateString()}`,
        processedAt: new Date(),
        createdAt: new Date(),
      };
      transactions.set(earningTx.id, earningTx);
    }

    if (distribution.performanceFee > 0) {
      const feeTx: Transaction = {
        id: uuidv4(),
        accountId: distribution.accountId,
        userId: distribution.userId,
        type: 'fee',
        status: 'completed',
        amount: -distribution.performanceFee,
        balanceBefore: account!.balance,
        balanceAfter: account!.balance,
        stripePaymentId: null,
        description: `Performance fee (${((distribution.performanceFee / distribution.grossEarnings) * 100).toFixed(0)}%)`,
        processedAt: new Date(),
        createdAt: new Date(),
      };
      transactions.set(feeTx.id, feeTx);
    }

    distribution.status = 'distributed';
    distribution.distributedAt = new Date();

    return distribution;
  }

  /**
   * Get pending distributions
   */
  async getPendingDistributions(): Promise<Distribution[]> {
    return Array.from(distributions.values())
      .filter((d) => d.status === 'pending')
      .sort((a, b) => a.periodEnd.getTime() - b.periodEnd.getTime());
  }
}

// Export singleton instance
export const transactionService = new TransactionService();
