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

class Street {
  constructor(data) {
    // Handle both new documents and database documents
    const isNew = !data._id;
    
    // For new documents, validate required fields
    if (isNew) {
      if (!data.name || data.name.trim() === '') {
        throw new ValidationError('Street name is required', 'name', data.name);
      }
      
      // Handle GeoJSON format: { type: 'Point', coordinates: [lng, lat] }
      let coordinates;
      if (data.location) {
        if (data.location.type === 'Point' && Array.isArray(data.location.coordinates)) {
          coordinates = data.location.coordinates;
        } else if (Array.isArray(data.location)) {
          coordinates = data.location;
        }
      }
      
      if (!coordinates || !Array.isArray(coordinates) || coordinates.length !== 2) {
        throw new ValidationError('Valid location coordinates [longitude, latitude] are required', 'location', data.location);
      }
      if (typeof coordinates[0] !== 'number' || typeof coordinates[1] !== 'number') {
        throw new ValidationError('Location coordinates must be numbers', 'location', coordinates);
      }
      if (coordinates[0] < -180 || coordinates[0] > 180) {
        throw new ValidationError('Longitude must be between -180 and 180', 'location[0]', coordinates[0]);
      }
      if (coordinates[1] < -90 || coordinates[1] > 90) {
        throw new ValidationError('Latitude must be between -90 and 90', 'location[1]', coordinates[1]);
      }
      if (data.status && !['available', 'adopted', 'maintenance'].includes(data.status)) {
        throw new ValidationError('Status must be one of: available, adopted, maintenance', 'status', data.status);
      }
    }

    // Assign properties
    this._id = data._id || null;
    this._rev = data._rev || null;
    this.type = data.type || "street";
    this.name = data.name ? data.name.trim() : '';
    
    // Handle location - ensure GeoJSON format
    if (data.location) {
      if (data.location.type === 'Point' && Array.isArray(data.location.coordinates)) {
        this.location = data.location;
      } else if (Array.isArray(data.location)) {
        this.location = {
          type: 'Point',
          coordinates: data.location
        };
      } else {
        this.location = {
          type: 'Point',
          coordinates: [0, 0]
        };
      }
    } else {
      this.location = {
        type: 'Point',
        coordinates: [0, 0]
      };
    }
    
    this.adoptedBy = data.adoptedBy || null;
    this.status = data.status || "available";
    this.createdAt = data.createdAt || new Date().toISOString();
    this.updatedAt = data.updatedAt || new Date().toISOString();
    this.stats = data.stats || {
      completedTasksCount: 0,
      reportsCount: 0,
      openReportsCount: 0
    };
  }

  static async find(filter = {}) {
    const errorContext = createErrorContext('Street', 'find', { filter });
    
    return await withErrorHandling(async () => {
      await couchdbService.initialize();
      
      // Extract pagination and sorting options from filter
      const { sort, skip, limit, ...filterOptions } = filter;
      
      // Convert MongoDB filter to CouchDB selector
      const selector = { type: "street" };
      
      // Handle special cases
      if (filterOptions._id) {
        selector._id = filterOptions._id;
      }
      
      if (filterOptions.status) {
        selector.status = filterOptions.status;
      }
      
      if (filterOptions.adoptedBy) {
        selector["adoptedBy.userId"] = filterOptions.adoptedBy;
      }

      if (filterOptions["adoptedBy.userId"]) {
        selector["adoptedBy.userId"] = filterOptions["adoptedBy.userId"];
      }

      // Handle name search with regex (case-insensitive)
      if (filterOptions.name && filterOptions.name.$regex) {
        // Extract search term from regex pattern
        const searchTerm = filterOptions.name.$regex.replace('(?i)', '').toLowerCase();
        
        // Get all streets and filter in-memory for case-insensitive search
        // This is a workaround since CouchDB doesn't support regex in Mango queries
        const allQuery = {
          selector: { ...selector },
          sort: sort || [{ name: "asc" }]
        };
        
        const allDocs = await couchdbService.find(allQuery);
        const filtered = allDocs.filter(doc => 
          doc.name.toLowerCase().includes(searchTerm)
        );
        
        // Apply pagination to filtered results
        const start = skip || 0;
        const end = start + (limit || filtered.length);
        const paginatedDocs = filtered.slice(start, end);
        
        return paginatedDocs.map(doc => new Street(doc));
      }

      const query = {
        selector,
        sort: sort || [{ name: "asc" }]
      };

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

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

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

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

  static async countDocuments(filter = {}) {
    const errorContext = createErrorContext('Street', 'countDocuments', { filter });
    
    return await withErrorHandling(async () => {
      await couchdbService.initialize();
      
      const selector = { type: "street" };
      
      // Handle status filter
      if (filter.status) {
        selector.status = filter.status;
      }
      
      // Handle adoptedBy filter
      if (filter["adoptedBy.userId"]) {
        selector["adoptedBy.userId"] = filter["adoptedBy.userId"];
      }

      // Handle name search with regex (case-insensitive)
      if (filter.name && filter.name.$regex) {
        // Extract search term from regex pattern
        const searchTerm = filter.name.$regex.replace('(?i)', '').toLowerCase();
        
        // Get all streets and filter in-memory
        const query = {
          selector,
          fields: ["_id", "name"]
        };

        const docs = await couchdbService.find(query);
        const filtered = docs.filter(doc => 
          doc.name.toLowerCase().includes(searchTerm)
        );
        
        return filtered.length;
      }
      
      // 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('Street', 'create', { 
      name: data.name,
      location: data.location 
    });
    
    return await withErrorHandling(async () => {
      await couchdbService.initialize();
      
      const street = new Street(data);
      const doc = await couchdbService.createDocument(street.toJSON());
      
      return new Street(doc);
    }, errorContext);
  }

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

  // Instance methods
  async save() {
    const errorContext = createErrorContext('Street', 'save', { 
      streetId: this._id,
      name: this.name 
    });
    
    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;
      }
      
      return this;
    }, errorContext);
  }

  async delete() {
    const errorContext = createErrorContext('Street', 'delete', { 
      streetId: this._id,
      name: this.name 
    });
    
    return await withErrorHandling(async () => {
      await couchdbService.initialize();
      
      if (!this._id || !this._rev) {
        throw new ValidationError("Street 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 _handleCascadeDelete() {
    const errorContext = createErrorContext('Street', '_handleCascadeDelete', { 
      streetId: this._id,
      adoptedBy: this.adoptedBy 
    });
    
    return await withErrorHandling(async () => {
      // Remove street from user's adoptedStreets
      if (this.adoptedBy && this.adoptedBy.userId) {
        const User = require("./User");
        const user = await User.findById(this.adoptedBy.userId);
        
        if (user) {
          user.adoptedStreets = user.adoptedStreets.filter(id => id !== this._id);
          await user.save();
        }
      }

      // Delete all tasks associated with this street
      const Task = require("./Task");
      await Task.deleteMany({ "street.streetId": this._id });
    }, errorContext);
  }

  // Populate method for compatibility
  async populate(path) {
    const errorContext = createErrorContext('Street', 'populate', { 
      streetId: this._id,
      path 
    });
    
    return await withErrorHandling(async () => {
      if (path === "adoptedBy" && this.adoptedBy && this.adoptedBy.userId) {
        const User = require("./User");
        const user = await User.findById(this.adoptedBy.userId);
        
        if (user) {
          this.adoptedBy = {
            userId: user._id,
            name: user.name,
            profilePicture: user.profilePicture
          };
        }
      }
      
      return this;
    }, errorContext);
  }

// Geospatial query helper
  static async findNearby(coordinates, maxDistance = 1000) {
    const errorContext = createErrorContext('Street', 'findNearby', { 
      coordinates, 
      maxDistance 
    });
    
    return await withErrorHandling(async () => {
      await couchdbService.initialize();
      
      // Validate coordinates
      if (!Array.isArray(coordinates) || coordinates.length !== 2) {
        throw new ValidationError('Coordinates must be [longitude, latitude]', 'coordinates', coordinates);
      }
      if (typeof coordinates[0] !== 'number' || typeof coordinates[1] !== 'number') {
        throw new ValidationError('Coordinates must be numbers', 'coordinates', coordinates);
      }
      
      // For CouchDB, we'll use a bounding box approach
      // Calculate bounding box around point
      const [lng, lat] = coordinates;
      const earthRadius = 6371000; // Earth's radius in meters
      const latDelta = (maxDistance / earthRadius) * (180 / Math.PI);
      const lngDelta = (maxDistance / earthRadius) * (180 / Math.PI) / Math.cos(lat * Math.PI / 180);

      const bounds = [
        [lng - lngDelta, lat - latDelta], // Southwest corner
        [lng + lngDelta, lat + latDelta]  // Northeast corner
      ];

      const streets = await couchdbService.findStreetsByLocation(bounds);
      return streets.map(doc => new Street(doc));
    }, errorContext);
  }

  // Convert to plain object
  toJSON() {
    return {
      _id: this._id,
      _rev: this._rev,
      type: this.type,
      name: this.name,
      location: this.location,
      adoptedBy: this.adoptedBy,
      status: this.status,
      createdAt: this.createdAt,
      updatedAt: this.updatedAt,
      stats: this.stats
    };
  }

  // 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 = Street;
