<?php
/**
 * Venue Geocoding Service
 *
 * Handles geocoding for TEC venues to add lat/lng coordinates.
 *
 * @package HVAC_Community_Events
 * @since 2.2.0
 */

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

/**
 * Class HVAC_Venue_Geocoding
 *
 * Manages geocoding of venue addresses using Google Maps Geocoding API.
 * Auto-geocodes new venues and provides batch processing for existing venues.
 */
class HVAC_Venue_Geocoding {

    /**
     * Singleton instance
     *
     * @var HVAC_Venue_Geocoding|null
     */
    private static ?self $instance = null;

    /**
     * Google Maps API key
     *
     * @var string
     */
    private string $api_key = '';

    /**
     * Rate limit per minute
     *
     * @var int
     */
    private int $rate_limit = 50;

    /**
     * Cache duration for geocoding results
     *
     * @var int
     */
    private int $cache_duration = DAY_IN_SECONDS;

    /**
     * Get singleton instance
     *
     * @return HVAC_Venue_Geocoding
     */
    public static function get_instance(): self {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Constructor
     */
    private function __construct() {
        $this->load_api_key();
        $this->init_hooks();
    }

    /**
     * Load API key from secure storage
     */
    private function load_api_key(): void {
        if (class_exists('HVAC_Secure_Storage')) {
            $this->api_key = HVAC_Secure_Storage::get_credential('hvac_google_maps_api_key', '');
        }
    }

    /**
     * Initialize hooks
     */
    private function init_hooks(): void {
        // Auto-geocode new venues on save
        add_action('save_post_tribe_venue', [$this, 'maybe_geocode_venue'], 20, 2);

        // Register geocoding action for async processing
        add_action('hvac_geocode_venue', [$this, 'geocode_venue']);

        // Admin action for batch geocoding
        add_action('wp_ajax_hvac_batch_geocode_venues', [$this, 'ajax_batch_geocode']);

        // Clear venue coordinates when address changes
        add_action('updated_post_meta', [$this, 'on_venue_meta_update'], 10, 4);
    }

    /**
     * Maybe geocode venue on save
     *
     * @param int $venue_id Venue post ID
     * @param WP_Post $post Post object
     */
    public function maybe_geocode_venue(int $venue_id, WP_Post $post): void {
        // Skip autosaves and revisions
        if (wp_is_post_autosave($venue_id) || wp_is_post_revision($venue_id)) {
            return;
        }

        // Check if coordinates already exist
        $has_coords = $this->venue_has_coordinates($venue_id);

        if (!$has_coords) {
            // Schedule geocoding to avoid blocking save
            wp_schedule_single_event(time() + 5, 'hvac_geocode_venue', [$venue_id]);
        }
    }

    /**
     * Check if venue already has coordinates
     *
     * @param int $venue_id Venue post ID
     * @return bool
     */
    public function venue_has_coordinates(int $venue_id): bool {
        // Check custom coordinates
        $lat = get_post_meta($venue_id, 'venue_latitude', true);
        $lng = get_post_meta($venue_id, 'venue_longitude', true);

        if (!empty($lat) && !empty($lng)) {
            return true;
        }

        // Check TEC built-in coordinates
        $tec_lat = get_post_meta($venue_id, '_VenueLat', true);
        $tec_lng = get_post_meta($venue_id, '_VenueLng', true);

        return !empty($tec_lat) && !empty($tec_lng);
    }

    /**
     * Geocode a venue
     *
     * @param int $venue_id Venue post ID
     * @return bool Success
     */
    public function geocode_venue(int $venue_id): bool {
        // Rate limiting check
        if (!$this->check_rate_limit()) {
            // Reschedule
            wp_schedule_single_event(time() + 60, 'hvac_geocode_venue', [$venue_id]);
            return false;
        }

        // Build address
        $address = $this->build_venue_address($venue_id);

        if (empty($address)) {
            update_post_meta($venue_id, '_venue_geocoding_status', 'no_address');
            return false;
        }

        update_post_meta($venue_id, '_venue_geocoding_attempt', time());

        // Check cache first
        $cache_key = 'venue_geo_' . md5($address);
        $cached = get_transient($cache_key);

        if ($cached !== false) {
            return $this->save_coordinates($venue_id, $cached);
        }

        // Make API request
        $result = $this->geocode_address($address);

        if ($result && isset($result['lat'], $result['lng'])) {
            // Cache result
            set_transient($cache_key, $result, $this->cache_duration);

            return $this->save_coordinates($venue_id, $result);
        }

        // Handle failure
        $this->handle_geocoding_failure($venue_id, $result);
        return false;
    }

    /**
     * Build venue address string from TEC meta
     *
     * @param int $venue_id Venue post ID
     * @return string Full address
     */
    private function build_venue_address(int $venue_id): string {
        $parts = [];

        $address = get_post_meta($venue_id, '_VenueAddress', true);
        $city = get_post_meta($venue_id, '_VenueCity', true);
        $state = get_post_meta($venue_id, '_VenueStateProvince', true) ?: get_post_meta($venue_id, '_VenueState', true);
        $zip = get_post_meta($venue_id, '_VenueZip', true);
        $country = get_post_meta($venue_id, '_VenueCountry', true);

        if (!empty($address)) {
            $parts[] = $address;
        }
        if (!empty($city)) {
            $parts[] = $city;
        }
        if (!empty($state)) {
            $parts[] = $state;
        }
        if (!empty($zip)) {
            $parts[] = $zip;
        }
        if (!empty($country)) {
            $parts[] = $country;
        }

        return implode(', ', $parts);
    }

    /**
     * Make geocoding API request
     *
     * @param string $address Address to geocode
     * @return array|null Result with lat/lng or null on failure
     */
    private function geocode_address(string $address): ?array {
        if (empty($this->api_key)) {
            return ['error' => 'No API key configured'];
        }

        $url = 'https://maps.googleapis.com/maps/api/geocode/json';
        $params = [
            'address' => $address,
            'key' => $this->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 Training 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'],
            'place_id' => $data['results'][0]['place_id'] ?? ''
        ];
    }

    /**
     * Save coordinates to venue
     *
     * @param int $venue_id Venue post ID
     * @param array $result Geocoding result
     * @return bool Success
     */
    private function save_coordinates(int $venue_id, array $result): bool {
        if (!isset($result['lat']) || !isset($result['lng'])) {
            return false;
        }

        // Save to our custom meta
        update_post_meta($venue_id, 'venue_latitude', $result['lat']);
        update_post_meta($venue_id, 'venue_longitude', $result['lng']);

        // Also update TEC's meta for compatibility
        update_post_meta($venue_id, '_VenueLat', $result['lat']);
        update_post_meta($venue_id, '_VenueLng', $result['lng']);

        if (!empty($result['formatted_address'])) {
            update_post_meta($venue_id, '_venue_formatted_address', $result['formatted_address']);
        }

        update_post_meta($venue_id, '_venue_geocoding_status', 'success');
        update_post_meta($venue_id, '_venue_geocoding_date', time());

        return true;
    }

    /**
     * Handle geocoding failure
     *
     * @param int $venue_id Venue post ID
     * @param array|null $result Error result
     */
    private function handle_geocoding_failure(int $venue_id, ?array $result): void {
        $error = $result['error'] ?? 'Unknown error';

        update_post_meta($venue_id, '_venue_geocoding_status', 'failed');
        update_post_meta($venue_id, '_venue_geocoding_error', $error);

        // Handle specific errors
        switch ($error) {
            case 'OVER_QUERY_LIMIT':
                // Retry in 1 hour
                wp_schedule_single_event(time() + HOUR_IN_SECONDS, 'hvac_geocode_venue', [$venue_id]);
                break;

            case 'ZERO_RESULTS':
                // Try fallback with less specific address
                $this->try_fallback_geocoding($venue_id);
                break;

            default:
                // Log error
                if (defined('WP_DEBUG') && WP_DEBUG) {
                    error_log("HVAC Venue Geocoding failed for venue {$venue_id}: {$error}");
                }
        }
    }

    /**
     * Try fallback geocoding with city/state only
     *
     * @param int $venue_id Venue post ID
     */
    private function try_fallback_geocoding(int $venue_id): void {
        $city = get_post_meta($venue_id, '_VenueCity', true);
        $state = get_post_meta($venue_id, '_VenueStateProvince', true) ?: get_post_meta($venue_id, '_VenueState', true);
        $country = get_post_meta($venue_id, '_VenueCountry', true) ?: 'USA';

        if (empty($city) && empty($state)) {
            return;
        }

        $address = implode(', ', array_filter([$city, $state, $country]));
        $result = $this->geocode_address($address);

        if ($result && isset($result['lat'], $result['lng'])) {
            $this->save_coordinates($venue_id, $result);
            update_post_meta($venue_id, '_venue_geocoding_status', 'success_fallback');
        }
    }

    /**
     * Check rate limiting
     *
     * @return bool Can make request
     */
    private function check_rate_limit(): bool {
        $rate_key = 'hvac_venue_geocoding_rate_' . gmdate('Y-m-d-H-i');
        $current = get_transient($rate_key) ?: 0;

        if ($current >= $this->rate_limit) {
            return false;
        }

        set_transient($rate_key, $current + 1, 60);
        return true;
    }

    /**
     * Clear coordinates when venue address changes
     *
     * @param int $meta_id Meta ID
     * @param int $post_id Post ID
     * @param string $meta_key Meta key
     * @param mixed $meta_value Meta value
     */
    public function on_venue_meta_update(int $meta_id, int $post_id, string $meta_key, $meta_value): void {
        if (get_post_type($post_id) !== 'tribe_venue') {
            return;
        }

        $address_fields = ['_VenueAddress', '_VenueCity', '_VenueStateProvince', '_VenueState', '_VenueZip'];

        if (in_array($meta_key, $address_fields, true)) {
            // Address changed - clear coordinates to force re-geocoding
            delete_post_meta($post_id, 'venue_latitude');
            delete_post_meta($post_id, 'venue_longitude');
            delete_post_meta($post_id, '_venue_geocoding_status');

            // Schedule re-geocoding
            wp_schedule_single_event(time() + 5, 'hvac_geocode_venue', [$post_id]);
        }
    }

    /**
     * AJAX handler for batch geocoding venues
     */
    public function ajax_batch_geocode(): void {
        // Check permissions
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Permission denied']);
            return;
        }

        // Verify nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_batch_geocode_venues')) {
            wp_send_json_error(['message' => 'Invalid security token']);
            return;
        }

        $limit = absint($_POST['limit'] ?? 10);
        $result = $this->batch_geocode_venues($limit);

        wp_send_json_success($result);
    }

    /**
     * Batch geocode venues without coordinates
     *
     * @param int $limit Maximum venues to process
     * @return array Results
     */
    public function batch_geocode_venues(int $limit = 10): array {
        $venues = get_posts([
            'post_type' => 'tribe_venue',
            'posts_per_page' => $limit,
            'post_status' => 'publish',
            'meta_query' => [
                'relation' => 'AND',
                [
                    'key' => 'venue_latitude',
                    'compare' => 'NOT EXISTS'
                ],
                [
                    'key' => '_VenueLat',
                    'compare' => 'NOT EXISTS'
                ]
            ]
        ]);

        $results = [
            'processed' => 0,
            'success' => 0,
            'failed' => 0,
            'remaining' => 0
        ];

        foreach ($venues as $venue) {
            if (!$this->check_rate_limit()) {
                break;
            }

            $results['processed']++;

            if ($this->geocode_venue($venue->ID)) {
                $results['success']++;
            } else {
                $results['failed']++;
            }
        }

        // Count remaining
        $remaining_query = new WP_Query([
            'post_type' => 'tribe_venue',
            'posts_per_page' => 1,
            'post_status' => 'publish',
            'fields' => 'ids',
            'meta_query' => [
                'relation' => 'AND',
                [
                    'key' => 'venue_latitude',
                    'compare' => 'NOT EXISTS'
                ],
                [
                    'key' => '_VenueLat',
                    'compare' => 'NOT EXISTS'
                ]
            ]
        ]);

        $results['remaining'] = $remaining_query->found_posts;

        return $results;
    }

    /**
     * Get geocoding status for a venue
     *
     * @param int $venue_id Venue post ID
     * @return array Status info
     */
    public function get_geocoding_status(int $venue_id): array {
        return [
            'has_coordinates' => $this->venue_has_coordinates($venue_id),
            'latitude' => get_post_meta($venue_id, 'venue_latitude', true) ?: get_post_meta($venue_id, '_VenueLat', true),
            'longitude' => get_post_meta($venue_id, 'venue_longitude', true) ?: get_post_meta($venue_id, '_VenueLng', true),
            'status' => get_post_meta($venue_id, '_venue_geocoding_status', true),
            'error' => get_post_meta($venue_id, '_venue_geocoding_error', true),
            'last_attempt' => get_post_meta($venue_id, '_venue_geocoding_attempt', true),
            'geocoded_date' => get_post_meta($venue_id, '_venue_geocoding_date', true)
        ];
    }

    /**
     * Clear coordinates for a venue
     *
     * @param int $venue_id Venue post ID
     */
    public function clear_coordinates(int $venue_id): void {
        delete_post_meta($venue_id, 'venue_latitude');
        delete_post_meta($venue_id, 'venue_longitude');
        delete_post_meta($venue_id, '_VenueLat');
        delete_post_meta($venue_id, '_VenueLng');
        delete_post_meta($venue_id, '_venue_formatted_address');
        delete_post_meta($venue_id, '_venue_geocoding_status');
        delete_post_meta($venue_id, '_venue_geocoding_error');
        delete_post_meta($venue_id, '_venue_geocoding_date');
    }
}
