const couchdbService = require("../services/couchdbService");
const { 
  ValidationError,
  NotFoundError, 
  DatabaseError, 
  withErrorHandling,
  createErrorContext
} = require("../utils/modelErrors");

class PointTransaction {
  constructor(transactionData) {
    this.validate(transactionData);
    Object.assign(this, transactionData);
  }

  validate(transactionData) {
    // Validate required fields
    if (!transactionData.user || transactionData.user.trim() === '') {
      throw new ValidationError('User field is required', 'user', transactionData.user);
    }
    if (transactionData.amount === undefined || transactionData.amount === null) {
      throw new ValidationError('Amount field is required', 'amount', transactionData.amount);
    }
    if (!transactionData.transactionType || transactionData.transactionType.trim() === '') {
      throw new ValidationError('Transaction type field is required', 'transactionType', transactionData.transactionType);
    }
    if (!transactionData.description || transactionData.description.trim() === '') {
      throw new ValidationError('Description field is required', 'description', transactionData.description);
    }

    // Validate transaction type
    const validTypes = ['earned', 'spent', 'bonus', 'penalty', 'refund'];
    if (!validTypes.includes(transactionData.transactionType)) {
      throw new ValidationError(`Transaction type must be one of: ${validTypes.join(', ')}`, 'transactionType', transactionData.transactionType);
    }

    // Validate amount based on transaction type
    if (transactionData.transactionType === 'earned' && transactionData.amount <= 0) {
      throw new ValidationError('Earned transactions must have positive amount', 'amount', transactionData.amount);
    }
    if (transactionData.transactionType === 'spent' && transactionData.amount >= 0) {
      throw new ValidationError('Spent transactions must have negative amount', 'amount', transactionData.amount);
    }
    if (transactionData.transactionType === 'bonus' && transactionData.amount <= 0) {
      throw new ValidationError('Bonus transactions must have positive amount', 'amount', transactionData.amount);
    }
    if (transactionData.transactionType === 'penalty' && transactionData.amount >= 0) {
      throw new ValidationError('Penalty transactions must have negative amount', 'amount', transactionData.amount);
    }

    // Validate source type if provided
    if (transactionData.relatedEntity && transactionData.relatedEntity.type) {
      const validSourceTypes = ['task_completion', 'street_adoption', 'event_participation', 'reward_redemption', 'badge_earned', 'manual_adjustment', 'system_bonus'];
      if (!validSourceTypes.includes(transactionData.relatedEntity.type)) {
        throw new ValidationError(`Source type must be one of: ${validSourceTypes.join(', ')}`, 'relatedEntity.type', transactionData.relatedEntity.type);
      }
    }
  }

  static async create(transactionData) {
    const errorContext = createErrorContext('PointTransaction', 'create', { 
      user: transactionData.user,
      amount: transactionData.amount,
      transactionType: transactionData.transactionType
    });
    
    return await withErrorHandling(async () => {
      // Validate using constructor
      new PointTransaction(transactionData);

      const transaction = {
        _id: `point_transaction_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
        type: 'point_transaction',
        user: transactionData.user,
        amount: transactionData.amount,
        transactionType: transactionData.transactionType,
        description: transactionData.description.trim(),
        relatedEntity: transactionData.relatedEntity || null,
        balanceAfter: transactionData.balanceAfter,
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString()
      };

      const result = await couchdbService.createDocument(transaction);
      return { ...transaction, _rev: result._rev || result.rev };
    }, errorContext);
  }

  static async findByUser(userId, limit = 50, skip = 0) {
    const errorContext = createErrorContext('PointTransaction', 'findByUser', { userId, limit, skip });
    
    return await withErrorHandling(async () => {
      const res = await couchdbService.find({
        selector: {
          type: 'point_transaction',
          user: userId
        },
        sort: [{ createdAt: 'desc' }],
        limit: limit,
        skip: skip
      });
      const docs = Array.isArray(res) ? res : (res && res.docs) ? res.docs : [];
      return docs;
    }, errorContext);
  }

  static async findByType(transactionType, limit = 50, skip = 0) {
    const errorContext = createErrorContext('PointTransaction', 'findByType', { transactionType, limit, skip });
    
    return await withErrorHandling(async () => {
      const res = await couchdbService.find({
        selector: {
          type: 'point_transaction',
          transactionType: transactionType
        },
        sort: [{ createdAt: 'desc' }],
        limit: limit,
        skip: skip
      });
      const docs = Array.isArray(res) ? res : (res && res.docs) ? res.docs : [];
      return docs;
    }, errorContext);
  }

  static async findById(id) {
    const errorContext = createErrorContext('PointTransaction', 'findById', { transactionId: id });
    
    return await withErrorHandling(async () => {
      try {
        const transaction = await couchdbService.getDocument(id);
        if (!transaction || transaction.type !== 'point_transaction') {
          return null;
        }
        return transaction;
      } catch (error) {
        if (error.statusCode === 404) {
          return null;
        }
        throw error;
      }
    }, errorContext);
  }

  static async getUserBalance(userId) {
    const errorContext = createErrorContext('PointTransaction', 'getUserBalance', { userId });
    
    return await withErrorHandling(async () => {
      const res = await couchdbService.find({
        selector: {
          type: 'point_transaction',
          user: userId
        },
        sort: [{ createdAt: 'desc' }],
        limit: 1
      });

      const transactions = Array.isArray(res) ? res : (res && res.docs) ? res.docs : [];
      if (transactions.length === 0) {
        return 0;
      }

      return transactions[0].balanceAfter;
    }, errorContext);
  }

  static async getUserTransactionHistory(userId, startDate, endDate) {
    const errorContext = createErrorContext('PointTransaction', 'getUserTransactionHistory', { userId, startDate, endDate });
    
    return await withErrorHandling(async () => {
      const selector = {
        type: 'point_transaction',
        user: userId
      };

      if (startDate || endDate) {
        selector.createdAt = {};
        if (startDate) {
          selector.createdAt.$gte = startDate;
        }
        if (endDate) {
          selector.createdAt.$lte = endDate;
        }
      }

      const res = await couchdbService.find({
        selector: selector,
        sort: [{ createdAt: 'desc' }]
      });

      const transactions = Array.isArray(res) ? res : (res && res.docs) ? res.docs : [];
      return transactions;
    }, errorContext);
  }

  static async getTransactionStats(userId, startDate, endDate) {
    const errorContext = createErrorContext('PointTransaction', 'getTransactionStats', { userId, startDate, endDate });
    
    return await withErrorHandling(async () => {
      const transactions = await this.getUserTransactionHistory(userId, startDate, endDate);
      
      const stats = {
        totalEarned: 0,
        totalSpent: 0,
        transactionCount: transactions.length,
        transactionBreakdown: {}
      };

      transactions.forEach(transaction => {
        if (transaction.amount > 0) {
          stats.totalEarned += transaction.amount;
        } else {
          stats.totalSpent += Math.abs(transaction.amount);
        }

        const type = transaction.transactionType;
        if (!stats.transactionBreakdown[type]) {
          stats.transactionBreakdown[type] = { count: 0, total: 0 };
        }
        stats.transactionBreakdown[type].count++;
        stats.transactionBreakdown[type].total += transaction.amount;
      });

      return stats;
    }, errorContext);
  }
}

module.exports = PointTransaction;
