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

class Event {
  constructor(data) {
    // Handle both new documents and database documents
    const isNew = !data._id;
    
    // For new documents, validate required fields
    if (isNew) {
      if (!data.title || data.title.trim() === '') {
        throw new ValidationError('Title is required', 'title', data.title);
      }
      if (!data.description || data.description.trim() === '') {
        throw new ValidationError('Description is required', 'description', data.description);
      }
      if (!data.date) {
        throw new ValidationError('Date is required', 'date', data.date);
      }
      if (!data.location) {
        throw new ValidationError('Location is required', 'location', data.location);
      }

      // Validate status
      const validStatuses = ["upcoming", "ongoing", "completed", "cancelled"];
      if (data.status && !validStatuses.includes(data.status)) {
        throw new ValidationError('Invalid status', 'status', data.status);
      }
    }

    this._id = data._id || null;
    this._rev = data._rev || null;
    this.type = data.type || "event";
    this.title = data.title;
    this.description = data.description;
    this.date = data.date;
    this.location = data.location;
    this.organizer = data.organizer || null;
    this.participants = data.participants || [];
    this.participantsCount = data.participantsCount || 0;
    this.status = data.status || "upcoming";
    this.createdAt = data.createdAt || new Date().toISOString();
    this.updatedAt = data.updatedAt || new Date().toISOString();
  }

  static async create(eventData) {
    const errorContext = createErrorContext('Event', 'create', { 
      title: eventData?.title,
      date: eventData?.date,
      location: eventData?.location 
    });
    
    return await withErrorHandling(async () => {
      const event = new Event(eventData);
      event._id = `event_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
      
      const createdEvent = await couchdbService.createDocument(event.toJSON());
      return new Event(createdEvent);
    }, errorContext);
  }

  static async findById(eventId) {
    const errorContext = createErrorContext('Event', 'findById', { eventId });
    
    return await withErrorHandling(async () => {
      const doc = await couchdbService.getById(eventId);
      if (doc && doc.type === "event") {
        return new Event(doc);
      }
      return null;
    }, errorContext);
  }

  static async find(query = {}, options = {}) {
    const errorContext = createErrorContext('Event', 'find', { query, options });
    
    return await withErrorHandling(async () => {
      const defaultQuery = {
        type: "event",
        ...query
      };

      return await couchdbService.find({
        selector: defaultQuery,
        ...options
      });
    }, errorContext);
  }

  static async findOne(query) {
    const errorContext = createErrorContext('Event', 'findOne', { query });
    
    return await withErrorHandling(async () => {
      const events = await this.find(query, { limit: 1 });
      return events[0] || null;
    }, errorContext);
  }

  static async update(eventId, updateData) {
    const errorContext = createErrorContext('Event', 'update', { eventId, updateData });
    
    return await withErrorHandling(async () => {
      const event = await this.findById(eventId);
      if (!event) {
        throw new NotFoundError('Event', eventId);
      }

      const updatedEvent = {
        ...event.toJSON(),
        ...updateData,
        updatedAt: new Date().toISOString()
      };

      const result = await couchdbService.update(eventId, updatedEvent);
      return new Event(result);
    }, errorContext);
  }

  static async delete(eventId) {
    const errorContext = createErrorContext('Event', 'delete', { eventId });
    
    return await withErrorHandling(async () => {
      const event = await this.findById(eventId);
      if (!event) {
        throw new NotFoundError('Event', eventId);
      }
      
      return await couchdbService.delete(eventId);
    }, errorContext);
  }

  static async addParticipant(eventId, userId, userName, userProfilePicture) {
    const errorContext = createErrorContext('Event', 'addParticipant', { eventId, userId });
    
    return await withErrorHandling(async () => {
      const event = await this.findById(eventId);
      if (!event) {
        throw new NotFoundError('Event', eventId);
      }

      // Check if user is already a participant
      const existingParticipant = event.participants.find(p => p.userId === userId);
      if (existingParticipant) {
        throw new ValidationError('User already participating in this event', 'userId', userId);
      }

      // Add participant with embedded user data
      const newParticipant = {
        userId: userId,
        name: userName,
        profilePicture: userProfilePicture || "",
        joinedAt: new Date().toISOString()
      };

      event.participants.push(newParticipant);
      event.participantsCount = event.participants.length;
      event.updatedAt = new Date().toISOString();

      const result = await couchdbService.update(eventId, event.toJSON());
      return new Event(result);
    }, errorContext);
  }

  static async removeParticipant(eventId, userId) {
    const errorContext = createErrorContext('Event', 'removeParticipant', { eventId, userId });
    
    return await withErrorHandling(async () => {
      const event = await this.findById(eventId);
      if (!event) {
        throw new NotFoundError('Event', eventId);
      }

      // Remove participant
      event.participants = event.participants.filter(p => p.userId !== userId);
      event.participantsCount = event.participants.length;
      event.updatedAt = new Date().toISOString();

      const result = await couchdbService.update(eventId, event.toJSON());
      return new Event(result);
    }, errorContext);
  }

  static async updateStatus(eventId, newStatus) {
    const errorContext = createErrorContext('Event', 'updateStatus', { eventId, newStatus });
    
    return await withErrorHandling(async () => {
      const validStatuses = ["upcoming", "ongoing", "completed", "cancelled"];
      if (!validStatuses.includes(newStatus)) {
        throw new ValidationError('Invalid status', 'status', newStatus);
      }

      return await this.update(eventId, { status: newStatus });
    }, errorContext);
  }

  static async findByStatus(status) {
    const errorContext = createErrorContext('Event', 'findByStatus', { status });
    
    return await withErrorHandling(async () => {
      return await this.find({ status });
    }, errorContext);
  }

  static async findByDateRange(startDate, endDate) {
    const errorContext = createErrorContext('Event', 'findByDateRange', { startDate, endDate });
    
    return await withErrorHandling(async () => {
      return await couchdbService.find({
        selector: {
          type: "event",
          date: {
            $gte: startDate.toISOString(),
            $lte: endDate.toISOString()
          }
        },
        sort: [{ date: "asc" }]
      });
    }, errorContext);
  }

  static async findByParticipant(userId) {
    const errorContext = createErrorContext('Event', 'findByParticipant', { userId });
    
    return await withErrorHandling(async () => {
      return await couchdbService.view("events", "by-participant", {
        key: userId,
        include_docs: true
      });
    }, errorContext);
  }

  static async getUpcomingEvents(limit = 10) {
    const errorContext = createErrorContext('Event', 'getUpcomingEvents', { limit });
    
    return await withErrorHandling(async () => {
      const now = new Date().toISOString();
      return await couchdbService.find({
        selector: {
          type: "event",
          status: "upcoming",
          date: { $gte: now }
        },
        sort: [{ date: "asc" }],
        limit
      });
    }, errorContext);
  }

  static async getAllPaginated(page = 1, limit = 10) {
    const errorContext = createErrorContext('Event', 'getAllPaginated', { page, limit });
    
    return await withErrorHandling(async () => {
      const skip = (page - 1) * limit;
      
      const events = await couchdbService.find({
        selector: { type: "event" },
        sort: [{ date: "desc" }],
        skip,
        limit
      });

      // Get total count
      const totalCount = await couchdbService.find({
        selector: { type: "event" },
        fields: ["_id"]
      });

      return {
        events,
        pagination: {
          page,
          limit,
          totalCount: totalCount.length,
          totalPages: Math.ceil(totalCount.length / limit)
        }
      };
    }, errorContext);
  }

  static async getEventsByUser(userId) {
    const errorContext = createErrorContext('Event', 'getEventsByUser', { userId });
    
    return await withErrorHandling(async () => {
      return await this.find({
        "participants": { $elemMatch: { userId: userId } }
      });
    }, errorContext);
  }

  // Convert to CouchDB document format
  toJSON() {
    return {
      _id: this._id,
      _rev: this._rev,
      type: this.type,
      title: this.title,
      description: this.description,
      date: this.date,
      location: this.location,
      organizer: this.organizer,
      participants: this.participants,
      participantsCount: this.participantsCount,
      status: this.status,
      createdAt: this.createdAt,
      updatedAt: this.updatedAt
    };
  }

  // Instance save method
  async save() {
    const errorContext = createErrorContext('Event', 'save', { 
      id: this._id,
      isNew: !this._id 
    });
    
    return await withErrorHandling(async () => {
      if (!this._id) {
        // New document
        this._id = `event_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
        const created = await couchdbService.createDocument(this.toJSON());
        this._rev = created._rev;
        return this;
      } else {
        // Update existing document
        this.updatedAt = new Date().toISOString();
        const updated = await couchdbService.updateDocument(this.toJSON());
        this._rev = updated._rev;
        return this;
      }
    }, errorContext);
  }

  // Migration helper
  static async migrateFromMongo(mongoEvent) {
    const errorContext = createErrorContext('Event', 'migrateFromMongo', { 
      mongoEventId: mongoEvent._id 
    });
    
    return await withErrorHandling(async () => {
      const eventData = {
        title: mongoEvent.title,
        description: mongoEvent.description,
        date: mongoEvent.date,
        location: mongoEvent.location,
        status: mongoEvent.status || "upcoming"
      };

      // Create event without participants first
      const event = await this.create(eventData);

      // If there are participants, add them with embedded user data
      if (mongoEvent.participants && mongoEvent.participants.length > 0) {
        for (const participantId of mongoEvent.participants) {
          try {
            // Get user data to embed
            const user = await couchdbService.findUserById(participantId.toString());
            if (user) {
              await this.addParticipant(event._id, participantId.toString(), user.name, user.profilePicture);
            }
          } catch (error) {
            console.error(`Error migrating participant ${participantId}:`, error.message);
          }
        }
      }

      return event;
    }, errorContext);
  }
}

module.exports = Event;