/**
 * Transactions Service
 *
 * Service for processing and managing payment transactions
 */

import { Repository, DataSource, Between, MoreThanOrEqual, LessThanOrEqual } from 'typeorm';
import {
  PaymentTransaction,
  PaymentStatus,
  PaymentMethod,
} from '../../mobile/entities/payment-transaction.entity';
import { BranchPaymentTerminal } from '../../branches/entities/branch-payment-terminal.entity';
import {
  ProcessPaymentDto,
  PaymentResultDto,
  ProcessRefundDto,
  RefundResultDto,
  SendReceiptDto,
  TransactionFilterDto,
  TransactionStatsDto,
} from '../dto';
import { CircuitBreaker } from '../../../shared/utils/circuit-breaker';

export class TransactionsService {
  private transactionRepository: Repository<PaymentTransaction>;
  private terminalRepository: Repository<BranchPaymentTerminal>;
  private circuitBreakers: Map<string, CircuitBreaker> = new Map();

  constructor(private dataSource: DataSource) {
    this.transactionRepository = dataSource.getRepository(PaymentTransaction);
    this.terminalRepository = dataSource.getRepository(BranchPaymentTerminal);
  }

  /**
   * Process a payment
   */
  async processPayment(
    tenantId: string,
    userId: string,
    dto: ProcessPaymentDto
  ): Promise<PaymentResultDto> {
    // Get terminal
    const terminal = await this.terminalRepository.findOne({
      where: { id: dto.terminalId, isActive: true },
    });

    if (!terminal) {
      return this.errorResult(dto.amount, dto.tipAmount || 0, 'Terminal not found', 'TERMINAL_NOT_FOUND');
    }

    // Check transaction limit
    if (terminal.transactionLimit && dto.amount > Number(terminal.transactionLimit)) {
      return this.errorResult(
        dto.amount,
        dto.tipAmount || 0,
        `Amount exceeds transaction limit of ${terminal.transactionLimit}`,
        'LIMIT_EXCEEDED'
      );
    }

    // Get or create circuit breaker for this terminal
    const circuitBreaker = this.getCircuitBreaker(terminal.id);

    // Create transaction record
    const transaction = this.transactionRepository.create({
      tenantId,
      branchId: terminal.branchId,
      userId,
      sourceType: dto.sourceType,
      sourceId: dto.sourceId,
      terminalProvider: terminal.terminalProvider,
      terminalId: terminal.terminalId,
      amount: dto.amount,
      currency: dto.currency || 'MXN',
      tipAmount: dto.tipAmount || 0,
      totalAmount: dto.amount + (dto.tipAmount || 0),
      paymentMethod: 'card', // Default, will be updated from provider response
      status: 'pending',
      initiatedAt: new Date(),
    });

    await this.transactionRepository.save(transaction);

    try {
      // Process through circuit breaker
      const providerResult = await circuitBreaker.execute(async () => {
        return this.processWithProvider(terminal, transaction, dto);
      });

      // Update transaction with result
      transaction.status = providerResult.status;
      transaction.externalTransactionId = providerResult.externalTransactionId || '';
      transaction.paymentMethod = providerResult.paymentMethod || transaction.paymentMethod;
      transaction.cardBrand = providerResult.cardBrand || '';
      transaction.cardLastFour = providerResult.cardLastFour || '';
      transaction.receiptUrl = providerResult.receiptUrl || '';
      transaction.providerResponse = providerResult.rawResponse || {};

      if (providerResult.status === 'completed') {
        transaction.completedAt = new Date();
        // Update terminal last transaction
        await this.terminalRepository.update(terminal.id, {
          lastTransactionAt: new Date(),
          healthStatus: 'healthy',
        });
      } else if (providerResult.status === 'failed') {
        transaction.failureReason = providerResult.error || '';
      }

      await this.transactionRepository.save(transaction);

      return {
        success: providerResult.status === 'completed',
        transactionId: transaction.id,
        externalTransactionId: providerResult.externalTransactionId,
        amount: dto.amount,
        totalAmount: transaction.totalAmount,
        tipAmount: transaction.tipAmount,
        currency: transaction.currency,
        status: transaction.status,
        paymentMethod: transaction.paymentMethod,
        cardBrand: transaction.cardBrand,
        cardLastFour: transaction.cardLastFour,
        receiptUrl: transaction.receiptUrl,
        error: providerResult.error,
      };
    } catch (error: any) {
      // Circuit breaker opened or other error
      transaction.status = 'failed';
      transaction.failureReason = error.message;
      await this.transactionRepository.save(transaction);

      // Update terminal health
      await this.terminalRepository.update(terminal.id, {
        healthStatus: 'offline',
        lastHealthCheckAt: new Date(),
      });

      return this.errorResult(
        dto.amount,
        dto.tipAmount || 0,
        error.message,
        'PROVIDER_ERROR',
        transaction.id
      );
    }
  }

  /**
   * Process refund
   */
  async processRefund(
    tenantId: string,
    userId: string,
    dto: ProcessRefundDto
  ): Promise<RefundResultDto> {
    const transaction = await this.transactionRepository.findOne({
      where: { id: dto.transactionId, tenantId },
    });

    if (!transaction) {
      return { success: false, amount: 0, status: 'failed', error: 'Transaction not found' };
    }

    if (transaction.status !== 'completed') {
      return {
        success: false,
        amount: 0,
        status: 'failed',
        error: 'Only completed transactions can be refunded',
      };
    }

    const refundAmount = dto.amount || Number(transaction.totalAmount);

    if (refundAmount > Number(transaction.totalAmount)) {
      return {
        success: false,
        amount: 0,
        status: 'failed',
        error: 'Refund amount cannot exceed transaction amount',
      };
    }

    try {
      // Get terminal for provider info
      const terminal = await this.terminalRepository.findOne({
        where: { terminalProvider: transaction.terminalProvider as any },
      });

      // Process refund with provider
      // In production, this would call the actual provider API
      const refundResult = await this.processRefundWithProvider(transaction, refundAmount, dto.reason);

      if (refundResult.success) {
        transaction.status = 'refunded';
        await this.transactionRepository.save(transaction);
      }

      return {
        success: refundResult.success,
        refundId: refundResult.refundId,
        amount: refundAmount,
        status: refundResult.success ? 'completed' : 'failed',
        error: refundResult.error,
      };
    } catch (error: any) {
      return {
        success: false,
        amount: refundAmount,
        status: 'failed',
        error: error.message,
      };
    }
  }

  /**
   * Get transaction by ID
   */
  async findById(id: string, tenantId: string): Promise<PaymentTransaction | null> {
    return this.transactionRepository.findOne({
      where: { id, tenantId },
    });
  }

  /**
   * Get transactions with filters
   */
  async findAll(
    tenantId: string,
    filter: TransactionFilterDto
  ): Promise<{ data: PaymentTransaction[]; total: number }> {
    const query = this.transactionRepository
      .createQueryBuilder('tx')
      .where('tx.tenantId = :tenantId', { tenantId });

    if (filter.branchId) {
      query.andWhere('tx.branchId = :branchId', { branchId: filter.branchId });
    }

    if (filter.userId) {
      query.andWhere('tx.userId = :userId', { userId: filter.userId });
    }

    if (filter.status) {
      query.andWhere('tx.status = :status', { status: filter.status });
    }

    if (filter.sourceType) {
      query.andWhere('tx.sourceType = :sourceType', { sourceType: filter.sourceType });
    }

    if (filter.terminalProvider) {
      query.andWhere('tx.terminalProvider = :provider', { provider: filter.terminalProvider });
    }

    if (filter.startDate) {
      query.andWhere('tx.createdAt >= :startDate', { startDate: filter.startDate });
    }

    if (filter.endDate) {
      query.andWhere('tx.createdAt <= :endDate', { endDate: filter.endDate });
    }

    const total = await query.getCount();

    query.orderBy('tx.createdAt', 'DESC');

    if (filter.limit) {
      query.take(filter.limit);
    }

    if (filter.offset) {
      query.skip(filter.offset);
    }

    const data = await query.getMany();

    return { data, total };
  }

  /**
   * Send receipt
   */
  async sendReceipt(
    transactionId: string,
    tenantId: string,
    dto: SendReceiptDto
  ): Promise<{ success: boolean; error?: string }> {
    const transaction = await this.findById(transactionId, tenantId);
    if (!transaction) {
      return { success: false, error: 'Transaction not found' };
    }

    if (!dto.email && !dto.phone) {
      return { success: false, error: 'Email or phone is required' };
    }

    try {
      // Send receipt via email or SMS
      // In production, this would integrate with email/SMS service

      transaction.receiptSent = true;
      transaction.receiptSentTo = dto.email || dto.phone || '';
      await this.transactionRepository.save(transaction);

      return { success: true };
    } catch (error: any) {
      return { success: false, error: error.message };
    }
  }

  /**
   * Get transaction statistics
   */
  async getStats(tenantId: string, filter?: TransactionFilterDto): Promise<TransactionStatsDto> {
    const query = this.transactionRepository
      .createQueryBuilder('tx')
      .where('tx.tenantId = :tenantId', { tenantId });

    if (filter?.branchId) {
      query.andWhere('tx.branchId = :branchId', { branchId: filter.branchId });
    }

    if (filter?.startDate) {
      query.andWhere('tx.createdAt >= :startDate', { startDate: filter.startDate });
    }

    if (filter?.endDate) {
      query.andWhere('tx.createdAt <= :endDate', { endDate: filter.endDate });
    }

    const transactions = await query.getMany();

    const byStatus: Record<PaymentStatus, number> = {
      pending: 0,
      processing: 0,
      completed: 0,
      failed: 0,
      refunded: 0,
      cancelled: 0,
    };

    const byProvider: Record<string, { count: number; amount: number }> = {};
    const byPaymentMethod: Record<PaymentMethod, number> = {
      card: 0,
      contactless: 0,
      qr: 0,
      link: 0,
    };

    let totalAmount = 0;
    let completedCount = 0;

    for (const tx of transactions) {
      byStatus[tx.status]++;

      if (!byProvider[tx.terminalProvider]) {
        byProvider[tx.terminalProvider] = { count: 0, amount: 0 };
      }
      byProvider[tx.terminalProvider].count++;

      if (tx.status === 'completed') {
        totalAmount += Number(tx.totalAmount);
        completedCount++;
        byProvider[tx.terminalProvider].amount += Number(tx.totalAmount);
        byPaymentMethod[tx.paymentMethod]++;
      }
    }

    const total = transactions.length;
    const failedCount = byStatus.failed;

    return {
      total,
      totalAmount,
      byStatus,
      byProvider,
      byPaymentMethod,
      averageAmount: completedCount > 0 ? totalAmount / completedCount : 0,
      successRate: total > 0 ? ((total - failedCount) / total) * 100 : 0,
    };
  }

  /**
   * Get or create circuit breaker for terminal
   */
  private getCircuitBreaker(terminalId: string): CircuitBreaker {
    if (!this.circuitBreakers.has(terminalId)) {
      this.circuitBreakers.set(
        terminalId,
        new CircuitBreaker(`terminal-${terminalId}`, {
          failureThreshold: 3,
          halfOpenRequests: 2,
          resetTimeout: 30000, // 30 seconds
        })
      );
    }
    return this.circuitBreakers.get(terminalId)!;
  }

  /**
   * Process payment with provider (simulated)
   */
  private async processWithProvider(
    terminal: BranchPaymentTerminal,
    transaction: PaymentTransaction,
    dto: ProcessPaymentDto
  ): Promise<{
    status: PaymentStatus;
    externalTransactionId?: string;
    paymentMethod?: PaymentMethod;
    cardBrand?: string;
    cardLastFour?: string;
    receiptUrl?: string;
    rawResponse?: Record<string, any>;
    error?: string;
  }> {
    // In production, this would call the actual provider API
    // For now, simulate a successful transaction

    // Simulate processing time
    await new Promise((resolve) => setTimeout(resolve, 500));

    // Simulate success rate (95%)
    const success = Math.random() > 0.05;

    if (success) {
      return {
        status: 'completed',
        externalTransactionId: `${terminal.terminalProvider}-${Date.now()}`,
        paymentMethod: 'card',
        cardBrand: 'visa',
        cardLastFour: '4242',
        receiptUrl: `https://receipts.example.com/${transaction.id}`,
        rawResponse: {
          provider: terminal.terminalProvider,
          approved: true,
          timestamp: new Date().toISOString(),
        },
      };
    } else {
      return {
        status: 'failed',
        error: 'Payment declined by issuer',
        rawResponse: {
          provider: terminal.terminalProvider,
          approved: false,
          declineReason: 'insufficient_funds',
        },
      };
    }
  }

  /**
   * Process refund with provider (simulated)
   */
  private async processRefundWithProvider(
    transaction: PaymentTransaction,
    amount: number,
    reason?: string
  ): Promise<{ success: boolean; refundId?: string; error?: string }> {
    // In production, this would call the actual provider API

    // Simulate processing
    await new Promise((resolve) => setTimeout(resolve, 300));

    return {
      success: true,
      refundId: `ref-${Date.now()}`,
    };
  }

  /**
   * Create error result
   */
  private errorResult(
    amount: number,
    tipAmount: number,
    error: string,
    errorCode: string,
    transactionId?: string
  ): PaymentResultDto {
    return {
      success: false,
      transactionId,
      amount,
      totalAmount: amount + tipAmount,
      tipAmount,
      currency: 'MXN',
      status: 'failed',
      error,
      errorCode,
    };
  }
}
