import { BaseService } from '../../../core/interfaces/IService';
import { eventBus } from '../../../core/events';
import { IWalletService } from './interfaces/IWalletService';
import {
  ErrorHandler,
  NotFoundError,
  ValidationError,
  InsufficientBalanceError,
  WalletFrozenError,
  BusinessLogicError
} from '../../../core/errors';
import {
  UserWallet,
  WalletTransaction,
  WalletBalanceSummary,
  WalletStatistics,
  CreateWalletInput,
  CreateTransactionInput,
  TransactionFilter,
  TransactionType,
  TransactionStatus,
  TransactionSource
} from '../types/wallet.types';

/**
 * Wallet service implementation
 */
export class WalletService extends BaseService implements IWalletService {
  readonly id = 'wallet-service';
  readonly name = 'Digital Wallet Service';
  
  private wallets: Map<string, UserWallet> = new Map();
  private transactions: Map<string, WalletTransaction> = new Map();
  private userWalletIndex: Map<string, string> = new Map(); // userId -> walletId
  private errorHandler: ErrorHandler;

  constructor() {
    super();
    this.errorHandler = ErrorHandler.getInstance();
  }

  async initialize(): Promise<void> {
    await super.initialize();
    await this.loadWalletsFromStorage();
    await this.loadTransactionsFromStorage();
    console.log('WalletService initialized');
  }

  async createWallet(input: CreateWalletInput): Promise<UserWallet> {
    // Check if user already has a wallet
    const existingWallet = await this.getWalletByUserId(input.userId);
    if (existingWallet) {
      throw new Error(`User ${input.userId} already has a wallet`);
    }

    const walletId = this.generateId('wallet');
    const now = new Date();

    const wallet: UserWallet = {
      id: walletId,
      userId: input.userId,
      balance: input.initialBalance || 0,
      currency: input.currency || 'IDR',
      isActive: true,
      createdAt: now,
      updatedAt: now
    };

    this.wallets.set(walletId, wallet);
    this.userWalletIndex.set(input.userId, walletId);

    // Create initial transaction if there's an initial balance
    if (input.initialBalance && input.initialBalance > 0) {
      await this.createTransaction({
        walletId,
        type: TransactionType.CREDIT,
        amount: input.initialBalance,
        source: TransactionSource.MANUAL_ADJUSTMENT,
        description: 'Initial wallet balance'
      });
    }

    // Emit event
    await eventBus.emit('wallet.created', {
      walletId,
      userId: input.userId,
      initialBalance: input.initialBalance || 0,
      createdAt: now
    });

    return wallet;
  }

  async getWalletByUserId(userId: string): Promise<UserWallet | null> {
    const walletId = this.userWalletIndex.get(userId);
    if (!walletId) return null;
    return this.wallets.get(walletId) || null;
  }

  async getWalletById(walletId: string): Promise<UserWallet | null> {
    return this.wallets.get(walletId) || null;
  }

  async getWalletBalance(userId: string): Promise<WalletBalanceSummary> {
    const wallet = await this.getWalletByUserId(userId);
    if (!wallet) {
      throw new Error(`Wallet not found for user: ${userId}`);
    }

    const transactions = await this.getTransactionsByWalletId(wallet.id, {
      status: TransactionStatus.PENDING
    });

    const pendingCredits = transactions
      .filter(t => t.type === TransactionType.CREDIT)
      .reduce((sum, t) => sum + t.amount, 0);

    const pendingDebits = transactions
      .filter(t => t.type === TransactionType.DEBIT)
      .reduce((sum, t) => sum + t.amount, 0);

    const availableBalance = wallet.balance - pendingDebits;

    return {
      totalBalance: wallet.balance,
      pendingCredits,
      pendingDebits,
      availableBalance,
      currency: wallet.currency,
      lastUpdated: wallet.updatedAt
    };
  }

  async createTransaction(input: CreateTransactionInput): Promise<WalletTransaction> {
    const wallet = await this.getWalletById(input.walletId);
    if (!wallet) {
      throw new Error(`Wallet not found: ${input.walletId}`);
    }

    if (!wallet.isActive) {
      throw new Error(`Wallet is not active: ${input.walletId}`);
    }

    // Validate transaction
    const validation = await this.validateTransaction(input);
    if (!validation.valid) {
      throw new Error(`Transaction validation failed: ${validation.errors.join(', ')}`);
    }

    const transactionId = this.generateId('txn');
    const now = new Date();

    const transaction: WalletTransaction = {
      id: transactionId,
      walletId: input.walletId,
      userId: wallet.userId,
      type: input.type,
      amount: input.amount,
      currency: wallet.currency,
      status: TransactionStatus.PENDING,
      source: input.source,
      description: input.description,
      referenceId: input.referenceId,
      balanceBefore: wallet.balance,
      balanceAfter: wallet.balance, // Will be updated when processed
      metadata: input.metadata,
      createdAt: now,
      updatedAt: now
    };

    this.transactions.set(transactionId, transaction);

    // Process transaction immediately for now
    await this.processTransaction(transactionId);

    // Emit event
    await eventBus.emit('wallet.transaction.created', {
      transactionId,
      walletId: input.walletId,
      userId: wallet.userId,
      type: input.type,
      amount: input.amount,
      createdAt: now
    });

    return this.transactions.get(transactionId)!;
  }

  async getTransactionById(transactionId: string): Promise<WalletTransaction | null> {
    return this.transactions.get(transactionId) || null;
  }

  async getTransactionsByWalletId(walletId: string, filter?: TransactionFilter): Promise<WalletTransaction[]> {
    let transactions = Array.from(this.transactions.values()).filter(t => t.walletId === walletId);

    if (filter) {
      transactions = this.applyTransactionFilter(transactions, filter);
    }

    // Sort by creation date (newest first)
    transactions.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());

    return transactions;
  }

  async getTransactionsByUserId(userId: string, filter?: TransactionFilter): Promise<WalletTransaction[]> {
    let transactions = Array.from(this.transactions.values()).filter(t => t.userId === userId);

    if (filter) {
      transactions = this.applyTransactionFilter(transactions, filter);
    }

    // Sort by creation date (newest first)
    transactions.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());

    return transactions;
  }

  async updateTransactionStatus(transactionId: string, status: TransactionStatus): Promise<WalletTransaction> {
    const transaction = this.transactions.get(transactionId);
    if (!transaction) {
      throw new Error(`Transaction not found: ${transactionId}`);
    }

    const updatedTransaction = {
      ...transaction,
      status,
      updatedAt: new Date(),
      ...(status === TransactionStatus.COMPLETED ? { processedAt: new Date() } : {})
    };

    this.transactions.set(transactionId, updatedTransaction);

    // Emit event
    await eventBus.emit('wallet.transaction.status.updated', {
      transactionId,
      walletId: transaction.walletId,
      userId: transaction.userId,
      oldStatus: transaction.status,
      newStatus: status,
      updatedAt: updatedTransaction.updatedAt
    });

    return updatedTransaction;
  }

  async processPendingTransactions(walletId: string): Promise<WalletTransaction[]> {
    const pendingTransactions = await this.getTransactionsByWalletId(walletId, {
      status: TransactionStatus.PENDING
    });

    const processedTransactions: WalletTransaction[] = [];

    for (const transaction of pendingTransactions) {
      try {
        await this.processTransaction(transaction.id);
        const processed = await this.getTransactionById(transaction.id);
        if (processed) {
          processedTransactions.push(processed);
        }
      } catch (error) {
        console.error(`Failed to process transaction ${transaction.id}:`, error);
        await this.updateTransactionStatus(transaction.id, TransactionStatus.FAILED);
      }
    }

    return processedTransactions;
  }

  async getUserWallet(userId: string): Promise<UserWallet | null> {
    try {
      if (!userId?.trim()) {
        throw new ValidationError('User ID is required', 'userId', userId);
      }

      const walletId = this.userWalletIndex.get(userId);
      if (!walletId) {
        return null;
      }

      const wallet = this.wallets.get(walletId);
      return wallet || null;
    } catch (error) {
      this.errorHandler.handleError(error as Error, { 
        userId,
        metadata: { operation: 'getUserWallet', service: 'WalletService' }
      });
      throw error;
    }
  }

  async creditWallet(userId: string, amount: number, description: string, referenceId?: string): Promise<WalletTransaction> {
    try {
      this.validateAmount(amount);
      
      let wallet = await this.getUserWallet(userId);
      if (!wallet) {
        wallet = await this.createWallet({ userId });
      }

      if (!wallet.isActive) {
        throw new WalletFrozenError(wallet.id, { userId });
      }

      const transaction = await this.createTransaction({
        walletId: wallet.id,
        type: TransactionType.CREDIT,
        amount,
        source: TransactionSource.REWARD_PAYMENT,
        description,
        metadata: referenceId ? { referenceId } : {}
      });

      return transaction;
    } catch (error) {
      this.errorHandler.handleError(error as Error, { 
        userId, 
        metadata: { 
          amount,
          operation: 'creditWallet',
          service: 'WalletService'
        }
      });
      throw error;
    }
  }

  private validateAmount(amount: number): void {
    if (typeof amount !== 'number' || amount <= 0) {
      throw new ValidationError('Amount must be a positive number', 'amount', amount);
    }

    if (amount > 1000000) { // Business rule: max transaction amount
      throw new BusinessLogicError(
        `Transaction amount ${amount} exceeds maximum allowed limit`,
        'max_transaction_amount_exceeded'
      );
    }
  }

  private validateTransactionRequest(request: CreateTransactionInput): void {
    if (!request.walletId?.trim()) {
      throw new ValidationError('Wallet ID is required', 'walletId', request.walletId);
    }

    if (!request.type || !Object.values(TransactionType).includes(request.type)) {
      throw new ValidationError('Valid transaction type is required', 'type', request.type);
    }

    this.validateAmount(request.amount);

    if (!request.description?.trim()) {
      throw new ValidationError('Description is required', 'description', request.description);
    }
  }

  async debitWallet(userId: string, amount: number, description: string, referenceId?: string): Promise<WalletTransaction> {
    try {
      this.validateAmount(amount);
      
      const wallet = await this.getUserWallet(userId);
      if (!wallet) {
        throw new NotFoundError(`Wallet not found for user: ${userId}`);
      }

      if (!wallet.isActive) {
        throw new WalletFrozenError(wallet.id, { userId });
      }

      if (wallet.balance < amount) {
        throw new InsufficientBalanceError(amount, wallet.balance);
      }

      return this.createTransaction({
        walletId: wallet.id,
        type: TransactionType.DEBIT,
        amount,
        source: TransactionSource.MANUAL_ADJUSTMENT,
        description,
        referenceId
      });
    } catch (error) {
      this.errorHandler.handleError(error as Error, { 
        userId, 
        metadata: { 
          amount,
          operation: 'debitWallet',
          service: 'WalletService'
        }
      });
      throw error;
    }
  }

  async transferBetweenWallets(fromUserId: string, toUserId: string, amount: number, description: string) {
    const fromWallet = await this.getWalletByUserId(fromUserId);
    const toWallet = await this.getWalletByUserId(toUserId);

    if (!fromWallet) {
      throw new Error(`Source wallet not found for user: ${fromUserId}`);
    }
    if (!toWallet) {
      throw new Error(`Destination wallet not found for user: ${toUserId}`);
    }

    if (fromWallet.balance < amount) {
      throw new Error('Insufficient balance for transfer');
    }

    const transferId = this.generateId('transfer');

    const debitTransaction = await this.createTransaction({
      walletId: fromWallet.id,
      type: TransactionType.DEBIT,
      amount,
      source: TransactionSource.MANUAL_ADJUSTMENT,
      description: `Transfer to ${toUserId}: ${description}`,
      referenceId: transferId
    });

    const creditTransaction = await this.createTransaction({
      walletId: toWallet.id,
      type: TransactionType.CREDIT,
      amount,
      source: TransactionSource.MANUAL_ADJUSTMENT,
      description: `Transfer from ${fromUserId}: ${description}`,
      referenceId: transferId
    });

    return {
      debitTransaction,
      creditTransaction
    };
  }

  async getWalletStatistics(userId: string, dateFrom?: Date, dateTo?: Date): Promise<WalletStatistics> {
    const transactions = await this.getTransactionsByUserId(userId, {
      status: TransactionStatus.COMPLETED,
      dateFrom,
      dateTo
    });

    const totalEarned = transactions
      .filter(t => t.type === TransactionType.CREDIT)
      .reduce((sum, t) => sum + t.amount, 0);

    const totalSpent = transactions
      .filter(t => t.type === TransactionType.DEBIT)
      .reduce((sum, t) => sum + t.amount, 0);

    const transactionCount = transactions.length;
    const averageTransaction = transactionCount > 0 ? (totalEarned + totalSpent) / transactionCount : 0;
    const largestTransaction = Math.max(...transactions.map(t => t.amount), 0);

    // Monthly earnings (last 12 months)
    const monthlyEarnings = this.calculateMonthlyEarnings(transactions);

    // Reward breakdown by source
    const rewardBreakdown = this.calculateRewardBreakdown(transactions);

    return {
      totalEarned,
      totalSpent,
      transactionCount,
      averageTransaction,
      largestTransaction,
      monthlyEarnings,
      rewardBreakdown
    };
  }

  async freezeWallet(walletId: string, reason: string): Promise<UserWallet> {
    const wallet = this.wallets.get(walletId);
    if (!wallet) {
      throw new Error(`Wallet not found: ${walletId}`);
    }

    const updatedWallet = {
      ...wallet,
      isActive: false,
      updatedAt: new Date()
    };

    this.wallets.set(walletId, updatedWallet);

    // Emit event
    await eventBus.emit('wallet.frozen', {
      walletId,
      userId: wallet.userId,
      reason,
      frozenAt: updatedWallet.updatedAt
    });

    return updatedWallet;
  }

  async unfreezeWallet(walletId: string): Promise<UserWallet> {
    const wallet = this.wallets.get(walletId);
    if (!wallet) {
      throw new Error(`Wallet not found: ${walletId}`);
    }

    const updatedWallet = {
      ...wallet,
      isActive: true,
      updatedAt: new Date()
    };

    this.wallets.set(walletId, updatedWallet);

    // Emit event
    await eventBus.emit('wallet.unfrozen', {
      walletId,
      userId: wallet.userId,
      unfrozenAt: updatedWallet.updatedAt
    });

    return updatedWallet;
  }

  async getTransactionHistory(userId: string, page: number, limit: number, filter?: TransactionFilter) {
    const offset = (page - 1) * limit;
    const allTransactions = await this.getTransactionsByUserId(userId, filter);
    
    const total = allTransactions.length;
    const transactions = allTransactions.slice(offset, offset + limit);
    const totalPages = Math.ceil(total / limit);

    return {
      transactions,
      total,
      page,
      limit,
      totalPages
    };
  }

  async calculateWalletBalance(walletId: string): Promise<number> {
    const transactions = await this.getTransactionsByWalletId(walletId, {
      status: TransactionStatus.COMPLETED
    });

    return transactions.reduce((balance, transaction) => {
      return transaction.type === TransactionType.CREDIT
        ? balance + transaction.amount
        : balance - transaction.amount;
    }, 0);
  }

  async reconcileWalletBalance(walletId: string) {
    const wallet = this.wallets.get(walletId);
    if (!wallet) {
      throw new Error(`Wallet not found: ${walletId}`);
    }

    const calculatedBalance = await this.calculateWalletBalance(walletId);
    const previousBalance = wallet.balance;
    const difference = calculatedBalance - previousBalance;

    if (difference !== 0) {
      // Update wallet balance
      const updatedWallet = {
        ...wallet,
        balance: calculatedBalance,
        updatedAt: new Date()
      };
      this.wallets.set(walletId, updatedWallet);

      // Create reconciliation transaction
      await this.createTransaction({
        walletId,
        type: difference > 0 ? TransactionType.CREDIT : TransactionType.DEBIT,
        amount: Math.abs(difference),
        source: TransactionSource.SYSTEM_CORRECTION,
        description: `Balance reconciliation: ${difference > 0 ? 'credit' : 'debit'} adjustment`
      });
    }

    return {
      previousBalance,
      calculatedBalance,
      difference,
      reconciled: difference !== 0
    };
  }

  async getPendingTransactionsCount(walletId?: string): Promise<number> {
    let transactions = Array.from(this.transactions.values()).filter(
      t => t.status === TransactionStatus.PENDING
    );

    if (walletId) {
      transactions = transactions.filter(t => t.walletId === walletId);
    }

    return transactions.length;
  }

  async bulkProcessTransactions(transactionIds: string[]): Promise<WalletTransaction[]> {
    const processedTransactions: WalletTransaction[] = [];

    for (const transactionId of transactionIds) {
      try {
        await this.processTransaction(transactionId);
        const processed = await this.getTransactionById(transactionId);
        if (processed) {
          processedTransactions.push(processed);
        }
      } catch (error) {
        console.error(`Failed to process transaction ${transactionId}:`, error);
      }
    }

    return processedTransactions;
  }

  async getWalletActivitySummary(userId: string, days: number) {
    const dateFrom = new Date();
    dateFrom.setDate(dateFrom.getDate() - days);

    const transactions = await this.getTransactionsByUserId(userId, {
      status: TransactionStatus.COMPLETED,
      dateFrom
    });

    const totalCredits = transactions
      .filter(t => t.type === TransactionType.CREDIT)
      .reduce((sum, t) => sum + t.amount, 0);

    const totalDebits = transactions
      .filter(t => t.type === TransactionType.DEBIT)
      .reduce((sum, t) => sum + t.amount, 0);

    const transactionCount = transactions.length;
    const averageTransaction = transactionCount > 0 ? (totalCredits + totalDebits) / transactionCount : 0;

    // Daily activity breakdown
    const dailyActivity = this.calculateDailyActivity(transactions, days);

    return {
      totalCredits,
      totalDebits,
      transactionCount,
      averageTransaction,
      dailyActivity
    };
  }

  async validateTransaction(input: CreateTransactionInput) {
    const errors: string[] = [];

    if (input.amount <= 0) {
      errors.push('Transaction amount must be positive');
    }

    if (input.type === TransactionType.DEBIT) {
      const wallet = await this.getWalletById(input.walletId);
      if (wallet && wallet.balance < input.amount) {
        errors.push('Insufficient balance for debit transaction');
      }
    }

    return {
      valid: errors.length === 0,
      errors
    };
  }

  async getWalletLimits(userId: string) {
    // In a real implementation, these would be configurable
    const dailyLimit = 10000000; // 10M IDR
    const monthlyLimit = 100000000; // 100M IDR

    const now = new Date();
    const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
    const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);

    const dailyTransactions = await this.getTransactionsByUserId(userId, {
      status: TransactionStatus.COMPLETED,
      dateFrom: startOfDay
    });

    const monthlyTransactions = await this.getTransactionsByUserId(userId, {
      status: TransactionStatus.COMPLETED,
      dateFrom: startOfMonth
    });

    const currentDailyUsage = dailyTransactions
      .filter(t => t.type === TransactionType.DEBIT)
      .reduce((sum, t) => sum + t.amount, 0);

    const currentMonthlyUsage = monthlyTransactions
      .filter(t => t.type === TransactionType.DEBIT)
      .reduce((sum, t) => sum + t.amount, 0);

    const canTransact = currentDailyUsage < dailyLimit && currentMonthlyUsage < monthlyLimit;

    return {
      dailyLimit,
      monthlyLimit,
      currentDailyUsage,
      currentMonthlyUsage,
      canTransact
    };
  }

  private async processTransaction(transactionId: string): Promise<void> {
    const transaction = this.transactions.get(transactionId);
    if (!transaction) {
      throw new Error(`Transaction not found: ${transactionId}`);
    }

    if (transaction.status !== TransactionStatus.PENDING) {
      return; // Already processed
    }

    const wallet = this.wallets.get(transaction.walletId);
    if (!wallet) {
      throw new Error(`Wallet not found: ${transaction.walletId}`);
    }

    let newBalance = wallet.balance;
    if (transaction.type === TransactionType.CREDIT) {
      newBalance += transaction.amount;
    } else {
      newBalance -= transaction.amount;
    }

    if (newBalance < 0) {
      throw new Error('Insufficient balance');
    }

    // Update wallet balance
    const updatedWallet = {
      ...wallet,
      balance: newBalance,
      updatedAt: new Date(),
      lastTransactionAt: new Date()
    };
    this.wallets.set(wallet.id, updatedWallet);

    // Update transaction
    const updatedTransaction = {
      ...transaction,
      status: TransactionStatus.COMPLETED,
      balanceAfter: newBalance,
      processedAt: new Date(),
      updatedAt: new Date()
    };
    this.transactions.set(transactionId, updatedTransaction);
  }

  private applyTransactionFilter(transactions: WalletTransaction[], filter: TransactionFilter): WalletTransaction[] {
    let filtered = transactions;

    if (filter.type) {
      filtered = filtered.filter(t => t.type === filter.type);
    }
    if (filter.status) {
      filtered = filtered.filter(t => t.status === filter.status);
    }
    if (filter.source) {
      filtered = filtered.filter(t => t.source === filter.source);
    }
    if (filter.dateFrom) {
      filtered = filtered.filter(t => t.createdAt >= filter.dateFrom!);
    }
    if (filter.dateTo) {
      filtered = filtered.filter(t => t.createdAt <= filter.dateTo!);
    }
    if (filter.amountMin) {
      filtered = filtered.filter(t => t.amount >= filter.amountMin!);
    }
    if (filter.amountMax) {
      filtered = filtered.filter(t => t.amount <= filter.amountMax!);
    }

    if (filter.offset) {
      filtered = filtered.slice(filter.offset);
    }
    if (filter.limit) {
      filtered = filtered.slice(0, filter.limit);
    }

    return filtered;
  }

  private calculateMonthlyEarnings(transactions: WalletTransaction[]): Array<{ month: string; amount: number }> {
    const monthlyData = new Map<string, number>();
    
    transactions
      .filter(t => t.type === TransactionType.CREDIT)
      .forEach(transaction => {
        const month = transaction.createdAt.toISOString().substring(0, 7); // YYYY-MM
        const current = monthlyData.get(month) || 0;
        monthlyData.set(month, current + transaction.amount);
      });

    return Array.from(monthlyData.entries()).map(([month, amount]) => ({ month, amount }));
  }

  private calculateRewardBreakdown(transactions: WalletTransaction[]): Array<{ type: string; amount: number; count: number }> {
    const breakdown = new Map<string, { amount: number; count: number }>();
    
    transactions
      .filter(t => t.type === TransactionType.CREDIT)
      .forEach(transaction => {
        const type = transaction.source;
        const current = breakdown.get(type) || { amount: 0, count: 0 };
        breakdown.set(type, {
          amount: current.amount + transaction.amount,
          count: current.count + 1
        });
      });

    return Array.from(breakdown.entries()).map(([type, data]) => ({ type, ...data }));
  }

  private calculateDailyActivity(transactions: WalletTransaction[], days: number): Array<{ date: string; credits: number; debits: number; count: number }> {
    const dailyData = new Map<string, { credits: number; debits: number; count: number }>();
    
    // Initialize all days
    for (let i = 0; i < days; i++) {
      const date = new Date();
      date.setDate(date.getDate() - i);
      const dateStr = date.toISOString().substring(0, 10); // YYYY-MM-DD
      dailyData.set(dateStr, { credits: 0, debits: 0, count: 0 });
    }

    transactions.forEach(transaction => {
      const dateStr = transaction.createdAt.toISOString().substring(0, 10);
      const current = dailyData.get(dateStr);
      if (current) {
        if (transaction.type === TransactionType.CREDIT) {
          current.credits += transaction.amount;
        } else {
          current.debits += transaction.amount;
        }
        current.count++;
      }
    });

    return Array.from(dailyData.entries()).map(([date, data]) => ({ date, ...data }));
  }

  private generateId(prefix: string): string {
    return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  private async loadWalletsFromStorage(): Promise<void> {
    // In a real implementation, this would load from a database
  }

  private async loadTransactionsFromStorage(): Promise<void> {
    // In a real implementation, this would load from a database
  }
}