<?php

if (!defined('ABSPATH')) {
    exit;
}

class HVAC_Geocoding_Service {
    
    private static $instance = null;
    private static $api_key;
    private static $rate_limit = 50; // requests per minute
    private static $cache_duration = DAY_IN_SECONDS;
    
    public static function get_instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    private function __construct() {
        // Load secure storage class
        if (!class_exists('HVAC_Secure_Storage')) {
            require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-secure-storage.php';
        }
        
        self::$api_key = HVAC_Secure_Storage::get_credential('hvac_google_maps_api_key');
        
        // Hook into profile address updates
        add_action('updated_post_meta', [$this, 'maybe_geocode'], 10, 4);
        add_action('hvac_profile_address_updated', [$this, 'schedule_geocoding']);
        
        // Register geocoding action
        add_action('hvac_geocode_address', [$this, 'geocode_address']);
    }
    
    public function maybe_geocode($meta_id, $post_id, $meta_key, $meta_value) {
        if (get_post_type($post_id) !== 'trainer_profile') {
            return;
        }
        
        $address_fields = ['trainer_city', 'trainer_state', 'trainer_country'];
        if (!in_array($meta_key, $address_fields)) {
            return;
        }
        
        // Debounce rapid updates
        $last_geocode = get_post_meta($post_id, '_last_geocode_attempt', true);
        if ($last_geocode && (time() - $last_geocode) < 30) {
            return;
        }
        
        // Schedule geocoding with delay to allow all address fields to be saved
        $this->schedule_geocoding($post_id);
    }
    
    public function schedule_geocoding($post_id) {
        wp_schedule_single_event(time() + 5, 'hvac_geocode_address', [$post_id]);
    }
    
    public function geocode_address($post_id) {
        // Check rate limiting
        if (!$this->check_rate_limit()) {
            // Reschedule for later
            wp_schedule_single_event(time() + 60, 'hvac_geocode_address', [$post_id]);
            return;
        }
        
        $address = $this->build_address($post_id);
        if (empty($address)) {
            return;
        }
        
        update_post_meta($post_id, '_last_geocode_attempt', time());
        
        // Check cache first
        $cache_key = 'geocode_' . md5($address);
        $cached = get_transient($cache_key);
        
        if ($cached !== false) {
            $this->update_coordinates($post_id, $cached);
            return;
        }
        
        // Make API request
        $result = $this->make_geocoding_request($address);
        
        if ($result && isset($result['lat'], $result['lng'])) {
            // Cache successful result
            set_transient($cache_key, $result, self::$cache_duration);
            $this->update_coordinates($post_id, $result);
            
            update_post_meta($post_id, '_geocoding_status', 'success');
            update_post_meta($post_id, '_last_geocoded', time());
        } else {
            // Handle failure
            $this->handle_geocoding_failure($post_id, $result);
        }
    }
    
    private function build_address($post_id) {
        $city = get_post_meta($post_id, 'trainer_city', true);
        $state = get_post_meta($post_id, 'trainer_state', true);
        $country = get_post_meta($post_id, 'trainer_country', true);
        
        $address_parts = array_filter([$city, $state, $country]);
        return implode(', ', $address_parts);
    }
    
    private function check_rate_limit() {
        $rate_key = 'hvac_geocoding_rate_' . gmdate('Y-m-d-H-i');
        $current_count = get_transient($rate_key) ?: 0;
        
        if ($current_count >= self::$rate_limit) {
            return false;
        }
        
        set_transient($rate_key, $current_count + 1, 60);
        return true;
    }
    
    private function make_geocoding_request($address) {
        if (empty(self::$api_key)) {
            return ['error' => 'No API key configured'];
        }
        
        $url = 'https://maps.googleapis.com/maps/api/geocode/json';
        $params = [
            'address' => $address,
            'key' => self::$api_key,
            'components' => 'country:US|country:CA' // Restrict to North America
        ];
        
        $response = wp_remote_get($url . '?' . http_build_query($params), [
            'timeout' => 10,
            'user-agent' => 'HVAC Trainer Directory/1.0'
        ]);
        
        if (is_wp_error($response)) {
            return ['error' => $response->get_error_message()];
        }
        
        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);
        
        if (!$data || $data['status'] !== 'OK' || empty($data['results'])) {
            return ['error' => $data['status'] ?? 'Unknown error'];
        }
        
        $location = $data['results'][0]['geometry']['location'];
        return [
            'lat' => $location['lat'],
            'lng' => $location['lng'],
            'formatted_address' => $data['results'][0]['formatted_address'],
            'confidence' => $this->calculate_confidence($data['results'][0])
        ];
    }
    
    private function update_coordinates($post_id, $result) {
        update_post_meta($post_id, 'latitude', $result['lat']);
        update_post_meta($post_id, 'longitude', $result['lng']);
        
        if (isset($result['formatted_address'])) {
            update_post_meta($post_id, 'formatted_address', $result['formatted_address']);
        }
        
        if (isset($result['confidence'])) {
            update_post_meta($post_id, 'geocoding_confidence', $result['confidence']);
        }
        
        if (isset($result['source'])) {
            update_post_meta($post_id, 'geocoding_source', $result['source']);
        }
    }
    
    private function handle_geocoding_failure($post_id, $error_result) {
        $error_message = $error_result['error'] ?? 'Unknown error';
        
        update_post_meta($post_id, '_geocoding_status', 'failed');
        update_post_meta($post_id, '_geocoding_error', $error_message);
        
        // Implement retry logic based on error type
        switch ($error_message) {
            case 'OVER_QUERY_LIMIT':
                // Retry in 1 hour
                wp_schedule_single_event(time() + HOUR_IN_SECONDS, 'hvac_geocode_address', [$post_id]);
                break;
                
            case 'ZERO_RESULTS':
                // Try fallback geocoding service
                $this->try_fallback_geocoding($post_id);
                break;
                
            case 'REQUEST_DENIED':
                // Log API key issue
                error_log("HVAC Geocoding: API key issue - {$error_message}");
                break;
                
            default:
                // Retry in 5 minutes for transient errors
                wp_schedule_single_event(time() + 300, 'hvac_geocode_address', [$post_id]);
        }
    }
    
    private function try_fallback_geocoding($post_id) {
        // Implement OpenStreetMap Nominatim as fallback
        $address = $this->build_address($post_id);
        
        $url = 'https://nominatim.openstreetmap.org/search';
        $params = [
            'q' => $address,
            'format' => 'json',
            'limit' => 1,
            'countrycodes' => 'us,ca'
        ];
        
        $response = wp_remote_get($url . '?' . http_build_query($params), [
            'timeout' => 10,
            'user-agent' => 'HVAC Trainer Directory/1.0'
        ]);
        
        if (!is_wp_error($response)) {
            $body = wp_remote_retrieve_body($response);
            $data = json_decode($body, true);
            
            if (!empty($data) && isset($data[0]['lat'], $data[0]['lon'])) {
                $result = [
                    'lat' => floatval($data[0]['lat']),
                    'lng' => floatval($data[0]['lon']),
                    'formatted_address' => $data[0]['display_name'],
                    'confidence' => 0.7, // Lower confidence for fallback
                    'source' => 'nominatim'
                ];
                
                $this->update_coordinates($post_id, $result);
                update_post_meta($post_id, '_geocoding_status', 'success_fallback');
                return;
            }
        }
        
        // Final fallback failed
        update_post_meta($post_id, '_geocoding_status', 'failed_all');
    }
    
    private function calculate_confidence($result) {
        $types = $result['types'] ?? [];
        
        // Higher confidence for more specific location types
        if (in_array('street_address', $types)) return 1.0;
        if (in_array('premise', $types)) return 0.9;
        if (in_array('subpremise', $types)) return 0.85;
        if (in_array('locality', $types)) return 0.8;
        if (in_array('administrative_area_level_3', $types)) return 0.7;
        if (in_array('administrative_area_level_2', $types)) return 0.6;
        if (in_array('administrative_area_level_1', $types)) return 0.5;
        
        return 0.4; // Low confidence for country-level matches
    }
    
    // Public methods for manual geocoding
    public function geocode_profile($profile_id) {
        return $this->geocode_address($profile_id);
    }
    
    public function get_coordinates($profile_id) {
        $lat = get_post_meta($profile_id, 'latitude', true);
        $lng = get_post_meta($profile_id, 'longitude', true);
        
        if ($lat && $lng) {
            return [
                'latitude' => floatval($lat),
                'longitude' => floatval($lng),
                'formatted_address' => get_post_meta($profile_id, 'formatted_address', true),
                'confidence' => get_post_meta($profile_id, 'geocoding_confidence', true),
                'source' => get_post_meta($profile_id, 'geocoding_source', true) ?: 'google',
                'last_geocoded' => get_post_meta($profile_id, '_last_geocoded', true)
            ];
        }
        
        return null;
    }
    
    public function get_geocoding_status($profile_id) {
        return [
            'status' => get_post_meta($profile_id, '_geocoding_status', true),
            'error' => get_post_meta($profile_id, '_geocoding_error', true),
            'last_attempt' => get_post_meta($profile_id, '_last_geocode_attempt', true),
            'last_success' => get_post_meta($profile_id, '_last_geocoded', true)
        ];
    }
    
    public function clear_coordinates($profile_id) {
        delete_post_meta($profile_id, 'latitude');
        delete_post_meta($profile_id, 'longitude');
        delete_post_meta($profile_id, 'formatted_address');
        delete_post_meta($profile_id, 'geocoding_confidence');
        delete_post_meta($profile_id, 'geocoding_source');
        delete_post_meta($profile_id, '_geocoding_status');
        delete_post_meta($profile_id, '_geocoding_error');
        delete_post_meta($profile_id, '_last_geocoded');
    }
    
    // Proximity search functionality
    public function find_nearby_profiles($latitude, $longitude, $radius_km = 50, $limit = 20) {
        global $wpdb;
        
        $query = $wpdb->prepare("
            SELECT p.ID, p.post_title,
                   lat_meta.meta_value as latitude,
                   lng_meta.meta_value as longitude,
                   (
                       6371 * acos(
                           cos(radians(%f)) * 
                           cos(radians(CAST(lat_meta.meta_value AS DECIMAL(10,8)))) * 
                           cos(radians(CAST(lng_meta.meta_value AS DECIMAL(11,8))) - radians(%f)) + 
                           sin(radians(%f)) * 
                           sin(radians(CAST(lat_meta.meta_value AS DECIMAL(10,8))))
                       )
                   ) AS distance
            FROM {$wpdb->posts} p
            LEFT JOIN {$wpdb->postmeta} lat_meta ON p.ID = lat_meta.post_id AND lat_meta.meta_key = 'latitude'
            LEFT JOIN {$wpdb->postmeta} lng_meta ON p.ID = lng_meta.post_id AND lng_meta.meta_key = 'longitude'
            LEFT JOIN {$wpdb->postmeta} public_meta ON p.ID = public_meta.post_id AND public_meta.meta_key = 'is_public_profile'
            WHERE p.post_type = 'trainer_profile'
            AND p.post_status = 'publish'
            AND lat_meta.meta_value IS NOT NULL
            AND lng_meta.meta_value IS NOT NULL
            AND public_meta.meta_value = '1'
            HAVING distance < %d
            ORDER BY distance ASC
            LIMIT %d
        ", $latitude, $longitude, $latitude, $radius_km, $limit);
        
        return $wpdb->get_results($query);
    }
    
    // Bulk geocoding for migration
    public function bulk_geocode_profiles($limit = 10) {
        $profiles = get_posts([
            'post_type' => 'trainer_profile',
            'posts_per_page' => $limit,
            'meta_query' => [
                'relation' => 'AND',
                [
                    'relation' => 'OR',
                    [
                        'key' => 'trainer_city',
                        'compare' => 'EXISTS'
                    ],
                    [
                        'key' => 'trainer_state', 
                        'compare' => 'EXISTS'
                    ]
                ],
                [
                    'key' => 'latitude',
                    'compare' => 'NOT EXISTS'
                ]
            ]
        ]);
        
        $processed = 0;
        foreach ($profiles as $profile) {
            if ($this->check_rate_limit()) {
                $this->geocode_address($profile->ID);
                $processed++;
            } else {
                break;
            }
        }
        
        return $processed;
    }
}

// Initialize the geocoding service
HVAC_Geocoding_Service::get_instance();