const axios = require("axios");
const logger = require("../utils/logger");

class CouchDBService {
  constructor() {
    this.baseUrl = null;
    this.dbName = null;
    this.auth = null;
    this.isConnected = false;
    this.isConnecting = false;
  }

  /**
   * Initialize CouchDB connection and database
   */
  async initialize() {
    if (this.isConnected || this.isConnecting) {
      return this.isConnected;
    }

    this.isConnecting = true;

    try {
      // Get configuration from environment variables
      const couchdbUrl = process.env.COUCHDB_URL || "http://localhost:5984";
      this.dbName = process.env.COUCHDB_DB_NAME || "adopt-a-street";
      const couchdbUser = process.env.COUCHDB_USER;
      const couchdbPassword = process.env.COUCHDB_PASSWORD;

      logger.info(`Connecting to CouchDB`, { url: couchdbUrl, database: this.dbName });

      // Set up base URL and authentication
      this.baseUrl = couchdbUrl.replace(/\/$/, ""); // Remove trailing slash
      
      if (couchdbUser && couchdbPassword) {
        const authString = Buffer.from(`${couchdbUser}:${couchdbPassword}`).toString('base64');
        this.auth = `Basic ${authString}`;
      }

      // Test connection
      await this.makeRequest('GET', '/');
      logger.info("CouchDB connection established");

      // Get or create database
      try {
        await this.makeRequest('GET', `/${this.dbName}`);
        logger.info(`Database exists`, { database: this.dbName });
      } catch (error) {
        if (error.response && error.response.status === 404) {
          logger.info(`Creating database`, { database: this.dbName });
          await this.makeRequest('PUT', `/${this.dbName}`);
          logger.info(`Database created successfully`, { database: this.dbName });
        } else {
          throw error;
        }
      }

      // Initialize design documents and indexes
      await this.initializeDesignDocuments();

      this.isConnected = true;
      logger.info("CouchDB service initialized successfully");
      
      return true;
    } catch (error) {
      logger.error("Failed to initialize CouchDB", error);
      this.isConnected = false;
      this.isConnecting = false;
      throw error;
    } finally {
      this.isConnecting = false;
    }
  }

  /**
   * Make HTTP request to CouchDB with proper authentication
   */
  async makeRequest(method, path, data = null, params = {}) {
    const startTime = Date.now();
    const config = {
      method,
      url: `${this.baseUrl}${path}`,
      headers: {
        'Content-Type': 'application/json',
      },
      params
    };

    if (this.auth) {
      config.headers.Authorization = this.auth;
    }

    if (data) {
      config.data = data;
    }

    try {
      const response = await axios(config);
      const duration = Date.now() - startTime;
      
      // Only log in DEBUG mode to reduce noise
      logger.db(method, path, duration);
      
      return response.data;
    } catch (error) {
      const duration = Date.now() - startTime;
      
      logger.error(`CouchDB request failed: ${method} ${path}`, error, {
        statusCode: error.response?.status,
        duration,
      });
      
      if (error.response) {
        const couchError = new Error(error.response.data.reason || error.message);
        couchError.statusCode = error.response.status;
        couchError.response = error.response.data;
        throw couchError;
      }
      throw error;
    }
  }

  /**
   * Initialize design documents with indexes and views
   */
  async initializeDesignDocuments() {
    const designDocs = [
      {
        _id: "_design/users",
        views: {
          "by-email": {
            map: `function(doc) {
              if (doc.type === "user" && doc.email) {
                emit(doc.email, null);
              }
            }`
          },
          "by-points": {
            map: `function(doc) {
              if (doc.type === "user" && doc.points > 0) {
                emit(doc.points, doc);
              }
            }`
          }
        },
        indexes: {
          "user-by-email": {
            index: { fields: ["type", "email"] },
            name: "user-by-email",
            type: "json"
          },
          "users-by-points": {
            index: { fields: ["type", "points"] },
            name: "users-by-points",
            type: "json"
          }
        }
      },
      {
        _id: "_design/streets",
        views: {
          "by-status": {
            map: `function(doc) {
              if (doc.type === "street" && doc.status) {
                emit(doc.status, doc);
              }
            }`
          },
          "by-adopter": {
            map: `function(doc) {
              if (doc.type === "street" && doc.adoptedBy && doc.adoptedBy.userId) {
                emit(doc.adoptedBy.userId, doc);
              }
            }`
          }
        },
        indexes: {
          "streets-by-name": {
            index: { 
              fields: ["type", "name"],
              partial_filter_selector: { "type": "street" }
            },
            name: "streets-by-name",
            type: "json"
          },
          "streets-by-location": {
            index: { 
              fields: ["type", "location"],
              partial_filter_selector: { "type": "street" }
            },
            name: "streets-by-location",
            type: "json"
          },
          "streets-by-status": {
            index: { 
              fields: ["type", "status"],
              partial_filter_selector: { "type": "street" }
            },
            name: "streets-by-status",
            type: "json"
          },
          "streets-geo": {
            index: {
              fields: ["type", "location"],
              partial_filter_selector: { 
                "type": "street",
                "location": { "$exists": true }
              }
            },
            name: "streets-geo",
            type: "json"
          }
        }
      },
      {
        _id: "_design/tasks",
        views: {
          "by-street": {
            map: `function(doc) {
              if (doc.type === "task" && doc.street && doc.street.streetId) {
                emit(doc.street.streetId, doc);
              }
            }`
          },
          "by-completer": {
            map: `function(doc) {
              if (doc.type === "task" && doc.completedBy && doc.completedBy.userId) {
                emit(doc.completedBy.userId, doc);
              }
            }`
          },
          "by-status": {
            map: `function(doc) {
              if (doc.type === "task" && doc.status) {
                emit(doc.status, doc);
              }
            }`
          }
        },
        indexes: {
          "tasks-by-user": {
            index: { fields: ["type", "completedBy.userId"] },
            name: "tasks-by-user",
            type: "json"
          }
        }
      },
      {
        _id: "_design/posts",
        views: {
          "by-user": {
            map: `function(doc) {
              if (doc.type === "post" && doc.user && doc.user.userId) {
                emit(doc.user.userId, doc);
              }
            }`
          },
          "by-date": {
            map: `function(doc) {
              if (doc.type === "post" && doc.createdAt) {
                emit(doc.createdAt, doc);
              }
            }`
          }
        },
        indexes: {
          "posts-by-date": {
            index: { fields: ["type", "createdAt"] },
            name: "posts-by-date",
            type: "json"
          }
        }
      },
      {
        _id: "_design/comments",
        views: {
          "by-post": {
            map: `function(doc) {
              if (doc.type === "comment" && doc.post && doc.post.postId) {
                emit(doc.post.postId, doc);
              }
            }`
          },
          "by-user": {
            map: `function(doc) {
              if (doc.type === "comment" && doc.user && doc.user.userId) {
                emit(doc.user.userId, doc);
              }
            }`
          }
        },
        indexes: {
          "comments-by-post": {
            index: { fields: ["type", "post.postId"] },
            name: "comments-by-post",
            type: "json"
          }
        }
      },
      {
        _id: "_design/events",
        views: {
          "by-date": {
            map: `function(doc) {
              if (doc.type === "event" && doc.date) {
                emit(doc.date, doc);
              }
            }`
          },
          "by-participant": {
            map: `function(doc) {
              if (doc.type === "event" && doc.participants) {
                doc.participants.forEach(function(participant) {
                  emit(participant.userId, doc);
                });
              }
            }`
          }
        },
        indexes: {
          "events-by-date-status": {
            index: { fields: ["type", "date", "status"] },
            name: "events-by-date-status",
            type: "json"
          }
        }
      },
      {
        _id: "_design/reports",
        views: {
          "by-street": {
            map: `function(doc) {
              if (doc.type === "report" && doc.street && doc.street.streetId) {
                emit(doc.street.streetId, doc);
              }
            }`
          },
          "by-status": {
            map: `function(doc) {
              if (doc.type === "report" && doc.status) {
                emit(doc.status, doc);
              }
            }`
          }
        },
        indexes: {
          "reports-by-status": {
            index: { fields: ["type", "status"] },
            name: "reports-by-status",
            type: "json"
          }
        }
      },
      {
        _id: "_design/badges",
        views: {
          "active-badges": {
            map: `function(doc) {
              if (doc.type === "badge" && doc.isActive) {
                emit(doc.order, doc);
              }
            }`
          }
        }
      },
      {
        _id: "_design/transactions",
        views: {
          "by-user": {
            map: `function(doc) {
              if (doc.type === "point_transaction" && doc.user && doc.user.userId) {
                emit(doc.user.userId, doc);
              }
            }`
          },
          "by-date": {
            map: `function(doc) {
              if (doc.type === "point_transaction" && doc.createdAt) {
                emit(doc.createdAt, doc);
              }
            }`
          }
        }
      },
      {
        _id: "_design/rewards",
        views: {
          "by-cost": {
            map: `function(doc) {
              if (doc.type === "reward" && doc.cost) {
                emit(doc.cost, doc);
              }
            }`
          },
          "by-premium": {
            map: `function(doc) {
              if (doc.type === "reward" && doc.isPremium) {
                emit(doc.isPremium, doc);
              }
            }`
          }
        },
        indexes: {
          "rewards-by-cost": {
            index: { fields: ["type", "cost"] },
            name: "rewards-by-cost",
            type: "json"
          },
          "rewards-by-premium": {
            index: { fields: ["type", "isPremium"] },
            name: "rewards-by-premium",
            type: "json"
          }
        }
      },
      {
        _id: "_design/general",
        indexes: {
          "by-type": {
            index: { fields: ["type"] },
            name: "by-type",
            type: "json"
          },
          "by-user": {
            index: { fields: ["type", "user.userId"] },
            name: "by-user",
            type: "json"
          },
          "by-created-date": {
            index: { fields: ["type", "createdAt"] },
            name: "by-created-date",
            type: "json"
          }
        }
      }
    ];

    for (const designDoc of designDocs) {
      try {
        // Check if design document exists
        const existing = await this.getDocument(designDoc._id);
        
        if (existing) {
          // Update with new revision
          designDoc._rev = existing._rev;
          await this.makeRequest('PUT', `/${this.dbName}/${designDoc._id}`, designDoc);
          logger.debug(`Updated design document`, { designDoc: designDoc._id });
        } else {
          // Create new design document
          const designDocToCreate = { ...designDoc };
          delete designDocToCreate._rev;
          await this.makeRequest('PUT', `/${this.dbName}/${designDoc._id}`, designDocToCreate);
          logger.debug(`Created design document`, { designDoc: designDoc._id });
        }
      } catch (error) {
        logger.error(`Error creating design document ${designDoc._id}`, error);
      }
    }
  }

  /**
   * Get database instance (for compatibility)
   */
  getDB() {
    if (!this.isConnected) {
      throw new Error("CouchDB not connected. Call initialize() first.");
    }
    return this; // Return this instance for method chaining
  }

  /**
   * Check connection status
   */
  isReady() {
    return this.isConnected;
  }

  /**
   * Check connection health
   */
  async checkConnection() {
    try {
      if (!this.baseUrl) {
        return false;
      }
      await this.makeRequest('GET', '/');
      return true;
    } catch (error) {
      logger.warn("CouchDB connection check failed", { error: error.message });
      return false;
    }
  }

  // Generic CRUD operations
  async createDocument(doc) {
    if (!this.isConnected) await this.initialize();
    
    try {
      // Remove _rev if undefined or null for new documents
      const docToCreate = { ...doc };
      if (docToCreate._rev === undefined || docToCreate._rev === null) {
        delete docToCreate._rev;
      }
      
      logger.debug("Creating document", { type: docToCreate.type, id: docToCreate._id });
      const response = await this.makeRequest('POST', `/${this.dbName}`, docToCreate);
      return { ...doc, _id: response.id, _rev: response.rev };
    } catch (error) {
      logger.error("Error creating document", error);
      throw error;
    }
  }

  async getDocument(id, options = {}) {
    if (!this.isConnected) await this.initialize();
    
    try {
      const params = new URLSearchParams();
      if (options.rev) params.append('rev', options.rev);
      if (options.revs) params.append('revs', 'true');
      if (options.open_revs) params.append('open_revs', options.open_revs);
      
      const doc = await this.makeRequest('GET', `/${this.dbName}/${id}`, null, params.toString());
      return doc;
    } catch (error) {
      if (error.statusCode === 404) {
        return null;
      }
      logger.error("Error getting document", error);
      throw error;
    }
  }

  async updateDocument(docOrId, maybeDoc) {
    if (!this.isConnected) await this.initialize();
    try {
      let doc;
      if (arguments.length === 2) {
        const id = docOrId;
        const provided = maybeDoc || {};
        if (!provided._id) provided._id = id;
        if (!provided._rev) {
          const existing = await this.getDocument(id);
          if (!existing) throw new Error("Document not found for update");
          provided._rev = existing._rev;
        }
        doc = provided;
      } else {
        doc = docOrId;
      }

      if (!doc || !doc._id || !doc._rev) {
        throw new Error("Document must have _id and _rev for update");
      }

      const response = await this.makeRequest('PUT', `/${this.dbName}/${doc._id}`, doc);
      return { ...doc, _rev: response.rev };
    } catch (error) {
      logger.error("Error updating document", error);
      throw error;
    }
  }

  async deleteDocument(id, rev) {
    if (!this.isConnected) await this.initialize();
    
    try {
      const response = await this.makeRequest('DELETE', `/${this.dbName}/${id}`, null, { rev });
      return response;
    } catch (error) {
      logger.error("Error deleting document", error);
      throw error;
    }
  }

  // Query operations
  async find(queryOrSelector, maybeOptions) {
    if (!this.isConnected) await this.initialize();

    try {
      let query;
      if (queryOrSelector && queryOrSelector.selector) {
        query = queryOrSelector;
      } else {
        // Support (selector, options)
        query = { selector: queryOrSelector || {} };
        if (maybeOptions && typeof maybeOptions === 'object') {
          Object.assign(query, maybeOptions);
        }
      }

      const response = await this.makeRequest('POST', `/${this.dbName}/_find`, query);
      const docs = Array.isArray(response) ? response : response?.docs;
      return Array.isArray(docs) ? docs : [];
    } catch (error) {
      logger.error("Error executing query", error);
      throw error;
    }
  }

  async findOne(selector) {
    const query = {
      selector,
      limit: 1
    };
    const docs = await this.find(query);
    return docs[0] || null;
  }

  async findByType(type, selector = {}, options = {}) {
    const query = {
      selector: { type, ...selector },
      ...options
    };
    return await this.find(query);
  }

  async findDocuments(selector = {}, options = {}) {
    const query = {
      selector,
      ...options
    };
    return await this.find(query);
  }

  async countDocuments(selector = {}) {
    const query = {
      selector,
      limit: 0, // We don't need documents, just count
    };
    
    try {
      const response = await this.makeRequest('POST', `/${this.dbName}/_find`, query);
      return response.total_rows || 0;
    } catch (error) {
      logger.error("Error counting documents", error);
      throw error;
    }
  }

  async findWithPagination(selector = {}, options = {}) {
    const { page = 1, limit = 10, sort = {} } = options;
    const skip = (page - 1) * limit;

    const query = {
      selector,
      limit,
      skip,
      sort: Object.entries(sort).map(([field, order]) => ({
        [field]: order === -1 ? "desc" : "asc"
      }))
    };

    try {
      const response = await this.makeRequest('POST', `/${this.dbName}/_find`, query);
      const totalCount = response.total_rows || 0;
      const totalPages = Math.ceil(totalCount / limit);

      return {
        docs: response.docs,
        totalDocs: totalCount,
        page,
        limit,
        totalPages,
        hasNextPage: page < totalPages,
        hasPrevPage: page > 1,
      };
    } catch (error) {
      logger.error("Error finding documents with pagination", error);
      throw error;
    }
  }

  // View query helper
  async view(designDoc, viewName, params = {}) {
    if (!this.isConnected) await this.initialize();
    
    try {
      const queryParams = new URLSearchParams();
      Object.entries(params).forEach(([key, value]) => {
        if (typeof value === 'object') {
          queryParams.append(key, JSON.stringify(value));
        } else {
          queryParams.append(key, value);
        }
      });
      
      const response = await this.makeRequest('GET', `/${this.dbName}/_design/${designDoc}/_view/${viewName}`, null, queryParams.toString());
      return response.rows.map(row => row.value);
    } catch (error) {
      logger.error("Error querying view", error);
      throw error;
    }
  }

  // Batch operation helper
  async bulkDocs(docsOrPayload) {
    if (!this.isConnected) await this.initialize();

    try {
      const payload = Array.isArray(docsOrPayload)
        ? { docs: docsOrPayload }
        : (docsOrPayload && docsOrPayload.docs ? docsOrPayload : { docs: [] });
      const response = await this.makeRequest('POST', `/${this.dbName}/_bulk_docs`, payload);
      return response;
    } catch (error) {
      logger.error("Error in bulk operation", error);
      throw error;
    }
  }

  // Conflict resolution helper
  async resolveConflict(id, conflictResolver) {
    if (!this.isConnected) await this.initialize();
    
    try {
      const doc = await this.db.get(id, { open_revs: "all" });
      
      if (!Array.isArray(doc) || doc.length === 1) {
        return doc[0] || doc; // No conflict
      }

      // Multiple revisions exist - resolve conflict
      const resolvedDoc = await conflictResolver(doc);
      return await this.updateDocument(resolvedDoc);
    } catch (error) {
      logger.error("Error resolving conflict", error);
      throw error;
    }
  }

  // Migration utilities
  async migrateDocument(mongoDoc, transformFn) {
    try {
      const couchDoc = transformFn(mongoDoc);
      return await this.createDocument(couchDoc);
    } catch (error) {
      logger.error("Error migrating document", error);
      throw error;
    }
  }

  // Validate document structure
  validateDocument(doc, requiredFields = []) {
    const errors = [];
    
    if (!doc.type) {
      errors.push("Document must have a 'type' field");
    }
    
    for (const field of requiredFields) {
      if (!doc[field]) {
        errors.push(`Document must have '${field}' field`);
      }
    }
    
    return errors;
  }

  // Generate document ID with prefix
  generateId(type, originalId) {
    return `${type}_${originalId}`;
  }

  // Extract original ID from prefixed ID
  extractOriginalId(prefixedId) {
    const parts = prefixedId.split("_");
    return parts.length > 1 ? parts.slice(1).join("_") : prefixedId;
  }

  // Graceful shutdown
  async shutdown() {
    try {
      if (this.baseUrl) {
        // Mark as disconnected
        this.isConnected = false;
        logger.info("CouchDB service shut down gracefully");
      }
    } catch (error) {
      logger.error("Error during shutdown", error);
    }
  }

  // Legacy compatibility methods
  async create(document) {
    return await this.createDocument(document);
  }

  async getById(id) {
    return await this.getDocument(id);
  }

  async update(id, document) {
    const existing = await this.getDocument(id);
    if (!existing) {
      throw new Error("Document not found for update");
    }
    const updatedDoc = { ...document, _id: id, _rev: existing._rev };
    return await this.updateDocument(updatedDoc);
  }

  async delete(id) {
    const doc = await this.getDocument(id);
    if (!doc) return false;
    await this.deleteDocument(id, doc._rev);
    return true;
  }

  // User-specific operations
  async findUserByEmail(email) {
    return this.findOne({ type: 'user', email });
  }

  async findUserById(userId) {
    return this.getById(userId);
  }

  async updateUserPoints(userId, pointsChange, description, relatedEntity = null) {
    const user = await this.findUserById(userId);
    if (!user) throw new Error('User not found');

    const newPoints = Math.max(0, user.points + pointsChange);
    
    // Update user points
    const updatedUser = await this.update(userId, { ...user, points: newPoints });

    // Create point transaction
    const transaction = {
      _id: `transaction_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
      type: 'point_transaction',
      user: {
        userId: userId,
        name: user.name
      },
      amount: pointsChange,
      type: this.getTransactionType(description),
      description,
      relatedEntity,
      balanceAfter: newPoints,
      createdAt: new Date().toISOString()
    };

    await this.create(transaction);

    // Check for badge awards
    await this.checkAndAwardBadges(userId, newPoints);

    return updatedUser;
  }

  getTransactionType(description) {
    if (description.includes('task')) return 'task_completion';
    if (description.includes('street')) return 'street_adoption';
    if (description.includes('post')) return 'post_creation';
    if (description.includes('event')) return 'event_participation';
    if (description.includes('reward')) return 'reward_redemption';
    return 'admin_adjustment';
  }

  async checkAndAwardBadges(userId, userPoints) {
    const user = await this.findUserById(userId);
    const badges = await this.findByType('badge', { isActive: true });
    
    for (const badge of badges) {
      // Check if user already has this badge
      const hasBadge = user.earnedBadges.some(earned => earned.badgeId === badge._id);
      if (hasBadge) continue;

      let shouldAward = false;
      let progress = 0;

      switch (badge.criteria.type) {
        case 'points_earned':
          progress = Math.min(100, (userPoints / badge.criteria.threshold) * 100);
          shouldAward = userPoints >= badge.criteria.threshold;
          break;
        case 'street_adoptions':
          progress = Math.min(100, (user.stats.streetsAdopted / badge.criteria.threshold) * 100);
          shouldAward = user.stats.streetsAdopted >= badge.criteria.threshold;
          break;
        case 'task_completions':
          progress = Math.min(100, (user.stats.tasksCompleted / badge.criteria.threshold) * 100);
          shouldAward = user.stats.tasksCompleted >= badge.criteria.threshold;
          break;
        case 'post_creations':
          progress = Math.min(100, (user.stats.postsCreated / badge.criteria.threshold) * 100);
          shouldAward = user.stats.postsCreated >= badge.criteria.threshold;
          break;
        case 'event_participations':
          progress = Math.min(100, (user.stats.eventsParticipated / badge.criteria.threshold) * 100);
          shouldAward = user.stats.eventsParticipated >= badge.criteria.threshold;
          break;
      }

      if (shouldAward) {
        await this.awardBadgeToUser(userId, badge);
      } else if (progress > 0) {
        await this.updateBadgeProgress(userId, badge._id, progress);
      }
    }
  }

  async awardBadgeToUser(userId, badge) {
    const user = await this.findUserById(userId);
    
    const newBadge = {
      badgeId: badge._id,
      name: badge.name,
      description: badge.description,
      icon: badge.icon,
      rarity: badge.rarity,
      earnedAt: new Date().toISOString(),
      progress: 100
    };

    user.earnedBadges.push(newBadge);
    user.stats.badgesEarned = user.earnedBadges.length;

    await this.update(userId, user);

    // Create user badge document for tracking
    const userBadge = {
      _id: `userbadge_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
      type: 'user_badge',
      userId: userId,
      badgeId: badge._id,
      earnedAt: newBadge.earnedAt,
      progress: 100,
      createdAt: newBadge.earnedAt,
      updatedAt: newBadge.earnedAt
    };

    await this.create(userBadge);
  }

  async updateBadgeProgress(userId, badgeId, progress) {
    const existingBadge = await this.findOne({
      type: 'user_badge',
      userId: userId,
      badgeId: badgeId
    });

    if (existingBadge) {
      await this.update(existingBadge._id, {
        ...existingBadge,
        progress: progress,
        updatedAt: new Date().toISOString()
      });
    } else {
      const userBadge = {
        _id: `userbadge_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
        type: 'user_badge',
        userId: userId,
        badgeId: badgeId,
        progress: progress,
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString()
      };
      await this.create(userBadge);
    }
  }

  // Street-specific operations
  async findStreetsByLocation(bounds) {
    try {
      // For CouchDB, we need to handle geospatial queries differently
      // We'll use a bounding box approach with the geo index
      const [sw, ne] = bounds;
      
      const query = {
        selector: {
          type: 'street',
          status: 'available',
          location: {
            $exists: true
          }
        },
        limit: 1000 // Reasonable limit for geographic queries
      };

      const streets = await this.find(query);
      
      // Filter by bounding box manually (since CouchDB doesn't support $geoWithin in Mango)
      return streets.filter(street => {
        if (!street.location || !street.location.coordinates) {
          return false;
        }
        
        const [lng, lat] = street.location.coordinates;
        return lng >= sw[0] && lng <= ne[0] && lat >= sw[1] && lat <= ne[1];
      });
    } catch (error) {
      logger.error("Error finding streets by location", error);
      throw error;
    }
  }

  async adoptStreet(userId, streetId) {
    const user = await this.findUserById(userId);
    const street = await this.getById(streetId);

    if (!user || !street) throw new Error('User or street not found');
    if (street.status !== 'available') throw new Error('Street is not available');
    if (street.adoptedBy) throw new Error('Street already adopted');

    // Update street
    const updatedStreet = await this.update(streetId, {
      ...street,
      adoptedBy: {
        userId: userId,
        name: user.name,
        profilePicture: user.profilePicture || ''
      },
      status: 'adopted'
    });

    // Update user
    user.adoptedStreets.push(streetId);
    user.stats.streetsAdopted = user.adoptedStreets.length;
    await this.update(userId, user);

    // Award points for street adoption
    await this.updateUserPoints(userId, 50, 'Street adoption', {
      entityType: 'Street',
      entityId: streetId,
      entityName: street.name
    });

    return updatedStreet;
  }

  // Task-specific operations
  async completeTask(userId, taskId) {
    const user = await this.findUserById(userId);
    const task = await this.getById(taskId);

    if (!user || !task) throw new Error('User or task not found');
    if (task.status === 'completed') throw new Error('Task already completed');

    // Update task
    const updatedTask = await this.update(taskId, {
      ...task,
      completedBy: {
        userId: userId,
        name: user.name,
        profilePicture: user.profilePicture || ''
      },
      status: 'completed',
      completedAt: new Date().toISOString()
    });

    // Update user
    user.completedTasks.push(taskId);
    user.stats.tasksCompleted = user.completedTasks.length;
    await this.update(userId, user);

    // Update street stats
    if (task.street && task.street.streetId) {
      const street = await this.getById(task.street.streetId);
      if (street) {
        street.stats.completedTasksCount = (street.stats.completedTasksCount || 0) + 1;
        await this.update(task.street.streetId, street);
      }
    }

    // Award points for task completion
    await this.updateUserPoints(userId, task.pointsAwarded || 10, `Completed task: ${task.description}`, {
      entityType: 'Task',
      entityId: taskId,
      entityName: task.description
    });

    return updatedTask;
  }

  // Post-specific operations
  async createPost(userId, postData) {
    const user = await this.findUserById(userId);
    if (!user) throw new Error('User not found');

    const post = {
      _id: `post_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
      type: 'post',
      user: {
        userId: userId,
        name: user.name,
        profilePicture: user.profilePicture || ''
      },
      content: postData.content,
      imageUrl: postData.imageUrl,
      cloudinaryPublicId: postData.cloudinaryPublicId,
      likes: [],
      likesCount: 0,
      commentsCount: 0,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString()
    };

    const createdPost = await this.create(post);

    // Update user
    user.posts.push(createdPost._id);
    user.stats.postsCreated = user.posts.length;
    await this.update(userId, user);

    // Award points for post creation
    await this.updateUserPoints(userId, 5, `Created post: ${postData.content.substring(0, 50)}...`, {
      entityType: 'Post',
      entityId: createdPost._id,
      entityName: postData.content.substring(0, 50)
    });

    return createdPost;
  }

  async togglePostLike(userId, postId) {
    const post = await this.getById(postId);
    if (!post) throw new Error('Post not found');

    const userLikedIndex = post.likes.indexOf(userId);
    
    if (userLikedIndex > -1) {
      // Unlike
      post.likes.splice(userLikedIndex, 1);
      post.likesCount = Math.max(0, post.likesCount - 1);
    } else {
      // Like
      post.likes.push(userId);
      post.likesCount += 1;
    }

    post.updatedAt = new Date().toISOString();
    return await this.update(postId, post);
  }

  async addCommentToPost(userId, postId, commentContent) {
    const user = await this.findUserById(userId);
    const post = await this.getById(postId);

    if (!user || !post) throw new Error('User or post not found');

    const comment = {
      _id: `comment_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
      type: 'comment',
      post: {
        postId: postId,
        content: post.content,
        userId: post.user.userId
      },
      user: {
        userId: userId,
        name: user.name,
        profilePicture: user.profilePicture || ''
      },
      content: commentContent,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString()
    };

    const createdComment = await this.create(comment);

    // Update post comment count
    post.commentsCount += 1;
    post.updatedAt = new Date().toISOString();
    await this.update(postId, post);

    return createdComment;
  }

  // Event-specific operations
  async joinEvent(userId, eventId) {
    const user = await this.findUserById(userId);
    const event = await this.getById(eventId);

    if (!user || !event) throw new Error('User or event not found');
    if (event.status !== 'upcoming') throw new Error('Event is not upcoming');

    // Check if already participating
    const alreadyParticipating = event.participants.some(p => p.userId === userId);
    if (alreadyParticipating) throw new Error('User already participating in event');

    // Add participant
    const newParticipant = {
      userId: userId,
      name: user.name,
      profilePicture: user.profilePicture || '',
      joinedAt: new Date().toISOString()
    };

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

    const updatedEvent = await this.update(eventId, event);

    // Update user
    user.events.push(eventId);
    user.stats.eventsParticipated = user.events.length;
    await this.update(userId, user);

    // Award points for event participation
    await this.updateUserPoints(userId, 15, `Joined event: ${event.title}`, {
      entityType: 'Event',
      entityId: eventId,
      entityName: event.title
    });

    return updatedEvent;
  }

  // Leaderboard operations
  async getLeaderboard(limit = 10) {
    return this.find({
      type: 'user',
      points: { $gt: 0 }
    }, {
      sort: [{ points: 'desc' }],
      limit,
      fields: ['_id', 'name', 'points', 'profilePicture', 'stats']
    });
  }

  // Social feed operations
  async getSocialFeed(limit = 20, skip = 0) {
    return this.find({
      type: 'post'
    }, {
      sort: [{ createdAt: 'desc' }],
      limit,
      skip
    });
  }

  async getPostComments(postId, limit = 50) {
    return this.find({
      type: 'comment',
      'post.postId': postId
    }, {
      sort: [{ createdAt: 'asc' }],
      limit
    });
  }

  // User activity
  async getUserActivity(userId, limit = 50) {
    const posts = await this.find({
      type: 'post',
      'user.userId': userId
    }, { limit });

    const tasks = await this.find({
      type: 'task',
      'completedBy.userId': userId
    }, { limit });

    const events = await this.find({
      type: 'event',
      'participants': { $elemMatch: { userId: userId } }
    }, { limit });

    // Combine and sort by date
    const activity = [
      ...posts.map(p => ({ ...p, activityType: 'post' })),
      ...tasks.map(t => ({ ...t, activityType: 'task' })),
      ...events.map(e => ({ ...e, activityType: 'event' }))
    ];

    return activity.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)).slice(0, limit);
  }

  // Report operations
  async createReport(userId, streetId, reportData) {
    const user = await this.findUserById(userId);
    const street = await this.getById(streetId);

    if (!user || !street) throw new Error('User or street not found');

    const report = {
      _id: `report_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
      type: 'report',
      street: {
        streetId: streetId,
        name: street.name,
        location: street.location
      },
      user: {
        userId: userId,
        name: user.name,
        profilePicture: user.profilePicture || ''
      },
      issue: reportData.issue,
      imageUrl: reportData.imageUrl,
      cloudinaryPublicId: reportData.cloudinaryPublicId,
      status: 'open',
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString()
    };

    const createdReport = await this.create(report);

    // Update street stats
    street.stats.reportsCount = (street.stats.reportsCount || 0) + 1;
    street.stats.openReportsCount = (street.stats.openReportsCount || 0) + 1;
    await this.update(streetId, street);

    return createdReport;
  }

  async resolveReport(reportId) {
    const report = await this.getById(reportId);
    if (!report) throw new Error('Report not found');
    if (report.status === 'resolved') throw new Error('Report already resolved');

    const updatedReport = await this.update(reportId, {
      ...report,
      status: 'resolved',
      updatedAt: new Date().toISOString()
    });

    // Update street stats
    if (report.street && report.street.streetId) {
      const street = await this.getById(report.street.streetId);
      if (street) {
        street.stats.openReportsCount = Math.max(0, (street.stats.openReportsCount || 0) - 1);
        await this.update(report.street.streetId, street);
      }
    }

    return updatedReport;
  }
}

module.exports = new CouchDBService();