const axios = require('axios');
const winston = require('winston');

// Configure logger
const logger = winston.createLogger({
    level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
    format: winston.format.combine(
        winston.format.timestamp(),
        winston.format.json()
    ),
    transports: [
        new winston.transports.Console({
            format: winston.format.simple()
        })
    ]
});

// Cache for geocoding results (simple in-memory cache)
const geocodeCache = new Map();
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours

// Clean up old cache entries periodically
setInterval(() => {
    const now = Date.now();
    for (const [key, value] of geocodeCache.entries()) {
        if (now - value.timestamp > CACHE_TTL) {
            geocodeCache.delete(key);
        }
    }
}, 60 * 60 * 1000); // Run every hour

/**
 * Reverse geocode coordinates to get address
 * @param {number} lat - Latitude
 * @param {number} lng - Longitude
 * @returns {Promise<Object>} Geocoding result
 */
async function reverseGeocode(lat, lng) {
    // Create cache key - use full precision
    const cacheKey = `${lat},${lng}`;
    
    // Check cache first
    const cached = geocodeCache.get(cacheKey);
    if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
        logger.debug(`Geocoding cache hit for ${cacheKey}`);
        return cached.data;
    }
    
    try {
        // Add delay to respect Nominatim rate limits (max 1 request per second)
        await new Promise(resolve => setTimeout(resolve, 1000));
        
        logger.info(`Reverse geocoding: ${lat}, ${lng}`);
        
        const response = await axios.get('https://nominatim.openstreetmap.org/reverse', {
            params: {
                format: 'json',
                lat: lat,
                lon: lng,
                zoom: 18,
                addressdetails: 1,
                'accept-language': 'en'
            },
            headers: {
                'User-Agent': 'NocoDB Map Viewer 1.0 (https://github.com/yourusername/nocodb-map-viewer)'
            },
            timeout: 10000
        });
        
        if (response.data.error) {
            throw new Error(response.data.error);
        }
        
        // Process the response
        const result = processGeocodeResponse(response.data);
        
        // Cache the result
        geocodeCache.set(cacheKey, {
            data: result,
            timestamp: Date.now()
        });
        
        return result;
        
    } catch (error) {
        logger.error('Reverse geocoding error:', error.message);
        
        if (error.response?.status === 429) {
            throw new Error('Rate limit exceeded. Please try again later.');
        } else if (error.code === 'ECONNABORTED') {
            throw new Error('Geocoding service timeout');
        } else {
            throw new Error('Geocoding service unavailable');
        }
    }
}

/**
 * Forward geocode address to get coordinates
 * @param {string} address - Address to geocode
 * @returns {Promise<Object>} Geocoding result
 */
async function forwardGeocode(address) {
    // Create cache key
    const cacheKey = `addr:${address.toLowerCase()}`;
    
    // Check cache first
    const cached = geocodeCache.get(cacheKey);
    if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
        logger.debug(`Geocoding cache hit for ${cacheKey}`);
        return cached.data;
    }
    
    try {
        // Add delay to respect rate limits
        await new Promise(resolve => setTimeout(resolve, 1000));
        
        logger.info(`Forward geocoding: ${address}`);
        
        const response = await axios.get('https://nominatim.openstreetmap.org/search', {
            params: {
                format: 'json',
                q: address,
                limit: 1,
                addressdetails: 1,
                'accept-language': 'en'
            },
            headers: {
                'User-Agent': 'NocoDB Map Viewer 1.0 (https://github.com/yourusername/nocodb-map-viewer)'
            },
            timeout: 10000
        });
        
        if (!response.data || response.data.length === 0) {
            throw new Error('No results found');
        }
        
        // Process the first result
        const result = processGeocodeResponse(response.data[0]);
        
        // Cache the result
        geocodeCache.set(cacheKey, {
            data: result,
            timestamp: Date.now()
        });
        
        return result;
        
    } catch (error) {
        logger.error('Forward geocoding error:', error.message);
        
        if (error.response?.status === 429) {
            throw new Error('Rate limit exceeded. Please try again later.');
        } else if (error.code === 'ECONNABORTED') {
            throw new Error('Geocoding service timeout');
        } else {
            throw new Error('Geocoding service unavailable');
        }
    }
}

/**
 * Process geocoding response into standardized format
 * @param {Object} data - Raw geocoding response
 * @returns {Object} Processed geocoding data
 */
function processGeocodeResponse(data) {
    // Extract address components
    const addressComponents = {
        house_number: data.address?.house_number || '',
        road: data.address?.road || '',
        suburb: data.address?.suburb || data.address?.neighbourhood || '',
        city: data.address?.city || data.address?.town || data.address?.village || '',
        state: data.address?.state || data.address?.province || '',
        postcode: data.address?.postcode || '',
        country: data.address?.country || ''
    };
    
    // Create formatted address string
    let formattedAddress = '';
    if (addressComponents.house_number) formattedAddress += addressComponents.house_number + ' ';
    if (addressComponents.road) formattedAddress += addressComponents.road + ', ';
    if (addressComponents.suburb) formattedAddress += addressComponents.suburb + ', ';
    if (addressComponents.city) formattedAddress += addressComponents.city + ', ';
    if (addressComponents.state) formattedAddress += addressComponents.state + ' ';
    if (addressComponents.postcode) formattedAddress += addressComponents.postcode;
    
    // Clean up formatting
    formattedAddress = formattedAddress.trim().replace(/,$/, '');
    
    return {
        fullAddress: data.display_name || '',
        formattedAddress: formattedAddress,
        components: addressComponents,
        coordinates: {
            lat: parseFloat(data.lat),
            lng: parseFloat(data.lon)
        },
        boundingBox: data.boundingbox || null,
        placeId: data.place_id || null,
        osmType: data.osm_type || null,
        osmId: data.osm_id || null
    };
}

/**
 * Get cache statistics
 * @returns {Object} Cache statistics
 */
function getCacheStats() {
    return {
        size: geocodeCache.size,
        maxSize: 1000, // Could be made configurable
        ttl: CACHE_TTL
    };
}

/**
 * Clear the geocoding cache
 */
function clearCache() {
    geocodeCache.clear();
    logger.info('Geocoding cache cleared');
}

module.exports = {
    reverseGeocode,
    forwardGeocode,
    getCacheStats,
    clearCache
};
