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

class UserBadge {
  constructor(userBadgeData) {
    UserBadge.validate(userBadgeData);
    Object.assign(this, userBadgeData);
  }

  static validate(userBadgeData, requireEarnedAt = false) {
    if (!userBadgeData.user || userBadgeData.user.trim() === '') {
      throw new ValidationError('User field is required', 'user', userBadgeData.user);
    }
    if (!userBadgeData.badge || userBadgeData.badge.trim() === '') {
      throw new ValidationError('Badge field is required', 'badge', userBadgeData.badge);
    }
    if (requireEarnedAt && !userBadgeData.earnedAt) {
      throw new ValidationError('EarnedAt field is required', 'earnedAt', userBadgeData.earnedAt);
    }
  }

  static async create(userBadgeData) {
    const errorContext = createErrorContext('UserBadge', 'create', { 
      user: userBadgeData.user,
      badge: userBadgeData.badge
    });
    
    return await withErrorHandling(async () => {
      new UserBadge(userBadgeData);

      const doc = {
        _id: `user_badge_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
        type: "user_badge",
        user: userBadgeData.user,
        badge: userBadgeData.badge,
        earnedAt: userBadgeData.earnedAt || new Date().toISOString(),
        progress: userBadgeData.progress || 0,
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString(),
      };

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

  static async findById(id) {
    const errorContext = createErrorContext('UserBadge', 'findById', { userBadgeId: id });
    
    return await withErrorHandling(async () => {
      try {
        const doc = await couchdbService.getDocument(id);
        if (doc && doc.type === "user_badge") {
          return doc;
        }
        return null;
      } catch (error) {
        if (error.statusCode === 404) {
          return null;
        }
        throw error;
      }
    }, errorContext);
  }

  static async find(filter = {}) {
    const errorContext = createErrorContext('UserBadge', 'find', { filter });
    
    return await withErrorHandling(async () => {
      const selector = { type: "user_badge", ...filter };
      const res = await couchdbService.find({ selector });
      const docs = Array.isArray(res) ? res : (res && Array.isArray(res.docs) ? res.docs : []);
      return docs;
    }, errorContext);
  }

  static async findByUser(userId) {
    const errorContext = createErrorContext('UserBadge', 'findByUser', { userId });
    
    return await withErrorHandling(async () => {
      const selector = { type: "user_badge", user: userId };
      const res = await couchdbService.find({ selector });
      const userBadges = Array.isArray(res) ? res : (res && Array.isArray(res.docs) ? res.docs : []);

      const populatedBadges = await Promise.all(
        userBadges.map(async (userBadge) => {
          if (userBadge.badge) {
            const badge = await couchdbService.getDocument(userBadge.badge);
            return { ...userBadge, badge };
          }
          return userBadge;
        })
      );

      return populatedBadges;
    }, errorContext);
  }

  static async findByBadge(badgeId) {
    const errorContext = createErrorContext('UserBadge', 'findByBadge', { badgeId });
    
    return await withErrorHandling(async () => {
      const selector = { type: "user_badge", badge: badgeId };
      const res = await couchdbService.find({ selector });
      const docs = Array.isArray(res) ? res : (res && Array.isArray(res.docs) ? res.docs : []);
      return docs;
    }, errorContext);
  }

  static async update(id, updateData) {
    const errorContext = createErrorContext('UserBadge', 'update', { userBadgeId: id, updateData });
    
    return await withErrorHandling(async () => {
      const doc = await couchdbService.getDocument(id);
      if (!doc || doc.type !== "user_badge") {
        throw new NotFoundError('UserBadge', id);
      }

      const updatedDoc = { ...doc, ...updateData, updatedAt: new Date().toISOString() };

      let result;
      if (typeof couchdbService.updateDocument === 'function') {
        result = await couchdbService.updateDocument(updatedDoc);
      } else if (typeof couchdbService.update === 'function') {
        result = await couchdbService.update(updatedDoc._id, updatedDoc);
      } else {
        throw new DatabaseError('Update method not available on couchdbService');
      }

      const _rev = result && (result._rev || result.rev);
      return _rev ? { ...updatedDoc, _rev } : updatedDoc;
    }, errorContext);
  }

  static async findByUserAndBadge(userId, badgeId) {
    const errorContext = createErrorContext('UserBadge', 'findByUserAndBadge', { userId, badgeId });
    
    return await withErrorHandling(async () => {
      const selector = { type: "user_badge", user: userId, badge: badgeId };
      const res = await couchdbService.find({ selector });
      const docs = Array.isArray(res) ? res : (res && Array.isArray(res.docs) ? res.docs : []);
      return docs[0] || null;
    }, errorContext);
  }

  static async delete(id) {
    const errorContext = createErrorContext('UserBadge', 'delete', { userBadgeId: id });
    
    return await withErrorHandling(async () => {
      const doc = await couchdbService.getDocument(id);
      if (!doc || doc.type !== "user_badge") {
        throw new NotFoundError('UserBadge', id);
      }

      await couchdbService.deleteDocument(id, doc._rev);
      return true;
    }, errorContext);
  }

  static async updateProgress(userId, badgeId, progress) {
    const errorContext = createErrorContext('UserBadge', 'updateProgress', { userId, badgeId, progress });
    
    return await withErrorHandling(async () => {
      const userBadge = await this.findByUserAndBadge(userId, badgeId);
      if (userBadge) {
        return await this.update(userBadge._id, { progress });
      } else {
        return await this.create({ user: userId, badge: badgeId, progress });
      }
    }, errorContext);
  }

  static async userHasBadge(userId, badgeId) {
    const errorContext = createErrorContext('UserBadge', 'userHasBadge', { userId, badgeId });
    
    return await withErrorHandling(async () => {
      const userBadge = await this.findByUserAndBadge(userId, badgeId);
      return !!userBadge;
    }, errorContext);
  }

  static validateWithEarnedAt(userBadgeData) {
    return this.validate(userBadgeData, true);
  }
}

module.exports = UserBadge;
