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

class Task {
  constructor(data) {
    // Handle both new documents and database documents
    const isNew = !data._id;
    
    // For new documents, validate required fields
    if (isNew) {
      if (!data.street || (!data.street.streetId && !data.street._id)) {
        throw new ValidationError('Street reference is required', 'street', data.street);
      }
      if (!data.description || data.description.trim() === '') {
        throw new ValidationError('Task description is required', 'description', data.description);
      }
      if (data.status && !['pending', 'in_progress', 'completed', 'cancelled'].includes(data.status)) {
        throw new ValidationError('Status must be one of: pending, in_progress, completed, cancelled', 'status', data.status);
      }
      if (data.pointsAwarded !== undefined && (typeof data.pointsAwarded !== 'number' || data.pointsAwarded < 0)) {
        throw new ValidationError('Points awarded must be a non-negative number', 'pointsAwarded', data.pointsAwarded);
      }
    }

    // Assign properties
    this._id = data._id || null;
    this._rev = data._rev || null;
    this.type = data.type || "task";
    this.street = data.street || null;
    this.description = data.description ? data.description.trim() : '';
    this.completedBy = data.completedBy || null;
    this.status = data.status || "pending";
    this.pointsAwarded = data.pointsAwarded || 10;
    this.createdAt = data.createdAt || new Date().toISOString();
    this.updatedAt = data.updatedAt || new Date().toISOString();
    this.completedAt = data.completedAt || null;
  }

  // Static methods for MongoDB-like interface
  static async find(filter = {}) {
    const errorContext = createErrorContext('Task', 'find', { filter });
    
    return await withErrorHandling(async () => {
      await couchdbService.initialize();
      
      // Convert MongoDB filter to CouchDB selector
      const selector = { type: "task", ...filter };
      
      // Handle special cases
      if (filter._id) {
        selector._id = filter._id;
      }
      
      if (filter.status) {
        selector.status = filter.status;
      }
      
      if (filter.street) {
        selector["street.streetId"] = filter.street;
      }
      
      if (filter.completedBy) {
        selector["completedBy.userId"] = filter.completedBy;
      }

      const query = {
        selector,
        sort: filter.sort || [{ createdAt: "desc" }]
      };

      // Add pagination if specified
      if (filter.skip) query.skip = filter.skip;
      if (filter.limit) query.limit = filter.limit;

      const docs = await couchdbService.find(query);
      
      // Convert to Task instances
      return docs.map(doc => new Task(doc));
    }, errorContext);
  }

  static async findById(id) {
    const errorContext = createErrorContext('Task', 'findById', { taskId: id });
    
    return await withErrorHandling(async () => {
      await couchdbService.initialize();
      const doc = await couchdbService.getDocument(id);
      
      if (!doc || doc.type !== "task") {
        return null;
      }
      
      return new Task(doc);
    }, errorContext);
  }

  static async findOne(filter = {}) {
    const errorContext = createErrorContext('Task', 'findOne', { filter });
    
    return await withErrorHandling(async () => {
      const tasks = await Task.find(filter);
      return tasks.length > 0 ? tasks[0] : null;
    }, errorContext);
  }

  static async countDocuments(filter = {}) {
    const errorContext = createErrorContext('Task', 'countDocuments', { filter });
    
    return await withErrorHandling(async () => {
      await couchdbService.initialize();
      
      const selector = { type: "task", ...filter };
      
      // Use Mango query with count
      const query = {
        selector,
        fields: ["_id"]
      };

      const docs = await couchdbService.find(query);
      return docs.length;
    }, errorContext);
  }

  static async create(data) {
    const errorContext = createErrorContext('Task', 'create', { 
      description: data.description,
      streetId: data.street?.streetId || data.street?._id 
    });
    
    return await withErrorHandling(async () => {
      await couchdbService.initialize();
      
      const task = new Task(data);
      const doc = await couchdbService.createDocument(task.toJSON());
      
      return new Task(doc);
    }, errorContext);
  }

  static async deleteMany(filter = {}) {
    const errorContext = createErrorContext('Task', 'deleteMany', { filter });
    
    return await withErrorHandling(async () => {
      await couchdbService.initialize();
      
      const tasks = await Task.find(filter);
      const deletePromises = tasks.map(task => task.delete());
      
      await Promise.all(deletePromises);
      return { deletedCount: tasks.length };
    }, errorContext);
  }

  // Instance methods
  async save() {
    const errorContext = createErrorContext('Task', 'save', { 
      taskId: this._id,
      description: this.description 
    });
    
    return await withErrorHandling(async () => {
      await couchdbService.initialize();
      
      this.updatedAt = new Date().toISOString();
      
      if (this._id && this._rev) {
        // Update existing document
        const doc = await couchdbService.updateDocument(this.toJSON());
        this._rev = doc._rev;
      } else {
        // Create new document
        const doc = await couchdbService.createDocument(this.toJSON());
        this._id = doc._id;
        this._rev = doc._rev;
      }
      
      // Handle post-save operations
      await this._handlePostSave();
      
      return this;
    }, errorContext);
  }

  async delete() {
    const errorContext = createErrorContext('Task', 'delete', { 
      taskId: this._id,
      description: this.description 
    });
    
    return await withErrorHandling(async () => {
      await couchdbService.initialize();
      
      if (!this._id || !this._rev) {
        throw new ValidationError("Task must have _id and _rev to delete", '_id', this._id);
      }

      // Handle cascade operations
      await this._handleCascadeDelete();

      await couchdbService.deleteDocument(this._id, this._rev);
      return this;
    }, errorContext);
  }

  async _handlePostSave() {
    const errorContext = createErrorContext('Task', '_handlePostSave', { 
      taskId: this._id,
      status: this.status,
      completedBy: this.completedBy 
    });
    
    return await withErrorHandling(async () => {
      // Update user relationship when task is completed
      if (this.completedBy && this.completedBy.userId && this.status === "completed") {
        const User = require("./User");
        const user = await User.findById(this.completedBy.userId);
        
        if (user && !user.completedTasks.includes(this._id)) {
          user.completedTasks.push(this._id);
          user.stats.tasksCompleted = user.completedTasks.length;
          await user.save();
        }

        // Update street stats
        if (this.street && this.street.streetId) {
          const Street = require("./Street");
          const street = await Street.findById(this.street.streetId);
          
          if (street) {
            street.stats.completedTasksCount = (street.stats.completedTasksCount || 0) + 1;
            await street.save();
          }
        }
      }
    }, errorContext);
  }

  async _handleCascadeDelete() {
    const errorContext = createErrorContext('Task', '_handleCascadeDelete', { 
      taskId: this._id,
      completedBy: this.completedBy 
    });
    
    return await withErrorHandling(async () => {
      // Remove task from user's completedTasks
      if (this.completedBy && this.completedBy.userId) {
        const User = require("./User");
        const user = await User.findById(this.completedBy.userId);
        
        if (user) {
          user.completedTasks = user.completedTasks.filter(id => id !== this._id);
          user.stats.tasksCompleted = user.completedTasks.length;
          await user.save();
        }
      }
    }, errorContext);
  }

  // Populate method for compatibility
  async populate(paths) {
    const errorContext = createErrorContext('Task', 'populate', { 
      taskId: this._id,
      paths 
    });
    
    return await withErrorHandling(async () => {
      if (Array.isArray(paths)) {
        for (const path of paths) {
          await this._populatePath(path);
        }
      } else {
        await this._populatePath(paths);
      }
      
      return this;
    }, errorContext);
  }

  async _populatePath(path) {
    const errorContext = createErrorContext('Task', '_populatePath', { 
      taskId: this._id,
      path 
    });
    
    return await withErrorHandling(async () => {
      if (path === "street" && this.street && this.street.streetId) {
        const Street = require("./Street");
        const street = await Street.findById(this.street.streetId);
        
        if (street) {
          this.street = {
            streetId: street._id,
            name: street.name,
            location: street.location
          };
        }
      }
      
      if (path === "completedBy" && this.completedBy && this.completedBy.userId) {
        const User = require("./User");
        const user = await User.findById(this.completedBy.userId);
        
        if (user) {
          this.completedBy = {
            userId: user._id,
            name: user.name,
            profilePicture: user.profilePicture
          };
        }
      }
    }, errorContext);
  }

  // Convert to plain object
  toJSON() {
    return {
      _id: this._id,
      _rev: this._rev,
      type: this.type,
      street: this.street,
      description: this.description,
      completedBy: this.completedBy,
      status: this.status,
      pointsAwarded: this.pointsAwarded,
      createdAt: this.createdAt,
      updatedAt: this.updatedAt,
      completedAt: this.completedAt
    };
  }

  // Convert to MongoDB-like format for API responses
  toObject() {
    const obj = this.toJSON();
    
    // Remove CouchDB-specific fields for API compatibility
    delete obj._rev;
    delete obj.type;
    
    // Add _id field for compatibility
    if (obj._id) {
      obj.id = obj._id;
    }
    
    return obj;
  }
}

// Export both the class and static methods for compatibility
module.exports = Task;
