<?php
/**
 * Cache Service
 *
 * WordPress Object Cache implementation with advanced caching strategies
 *
 * @package Care_API
 * @subpackage Services
 * @version    1.0.0
 * @author     Descomplicar® <dev@descomplicar.pt>
 * @link       https://descomplicar.pt
 * @since      1.0.0
 */

namespace Care_API\Services;

use Care_API\Utils\API_Logger;

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

/**
 * Class Cache_Service
 *
 * Advanced caching with WordPress Object Cache integration
 *
 * @since 1.0.0
 */
class Cache_Service {

    /**
     * Cache groups
     *
     * @var array
     */
    private static $cache_groups = array(
        'patients' => 3600,      // 1 hour
        'doctors' => 3600,       // 1 hour  
        'appointments' => 1800,  // 30 minutes
        'encounters' => 3600,    // 1 hour
        'prescriptions' => 3600, // 1 hour
        'bills' => 1800,         // 30 minutes
        'clinics' => 7200,       // 2 hours
        'statistics' => 900,     // 15 minutes
        'queries' => 300,        // 5 minutes
        'sessions' => 86400      // 24 hours
    );

    /**
     * Cache prefixes
     *
     * @var array
     */
    private static $cache_prefixes = array(
        'object' => 'kivicare_obj_',
        'query' => 'kivicare_query_',
        'list' => 'kivicare_list_',
        'stats' => 'kivicare_stats_',
        'user' => 'kivicare_user_'
    );

    /**
     * Invalidation tags
     *
     * @var array
     */
    private static $invalidation_tags = array();

    /**
     * Cache statistics
     *
     * @var array
     */
    private static $stats = array(
        'hits' => 0,
        'misses' => 0,
        'sets' => 0,
        'deletes' => 0
    );

    /**
     * Initialize cache service
     *
     * @since 1.0.0
     */
    public static function init() {
        // Register cache groups
        self::register_cache_groups();
        
        // Setup cache invalidation hooks
        self::setup_invalidation_hooks();
        
        // Schedule cache cleanup
        if ( ! wp_next_scheduled( 'kivicare_cache_cleanup' ) ) {
            wp_schedule_event( time(), 'hourly', 'kivicare_cache_cleanup' );
        }
        add_action( 'kivicare_cache_cleanup', array( __CLASS__, 'cleanup_expired_cache' ) );

        // Add cache warming hooks
        add_action( 'wp_loaded', array( __CLASS__, 'warm_critical_cache' ) );
        
        // Setup cache statistics collection
        add_action( 'shutdown', array( __CLASS__, 'log_cache_statistics' ) );
    }

    /**
     * Register WordPress cache groups
     *
     * @since 1.0.0
     */
    private static function register_cache_groups() {
        foreach ( array_keys( self::$cache_groups ) as $group ) {
            wp_cache_add_non_persistent_groups( $group );
        }
    }

    /**
     * Setup cache invalidation hooks
     *
     * @since 1.0.0
     */
    private static function setup_invalidation_hooks() {
        // Patient invalidation
        add_action( 'kivicare_patient_created', array( __CLASS__, 'invalidate_patient_cache' ), 10, 2 );
        add_action( 'kivicare_patient_updated', array( __CLASS__, 'invalidate_patient_cache' ), 10, 2 );
        add_action( 'kivicare_patient_deleted', array( __CLASS__, 'invalidate_patient_cache' ), 10, 1 );

        // Doctor invalidation  
        add_action( 'kivicare_doctor_updated', array( __CLASS__, 'invalidate_doctor_cache' ), 10, 2 );

        // Appointment invalidation
        add_action( 'kivicare_appointment_created', array( __CLASS__, 'invalidate_appointment_cache' ), 10, 2 );
        add_action( 'kivicare_appointment_updated', array( __CLASS__, 'invalidate_appointment_cache' ), 10, 2 );
        add_action( 'kivicare_appointment_cancelled', array( __CLASS__, 'invalidate_appointment_cache' ), 10, 2 );

        // Encounter invalidation
        add_action( 'kivicare_encounter_created', array( __CLASS__, 'invalidate_encounter_cache' ), 10, 2 );
        add_action( 'kivicare_encounter_updated', array( __CLASS__, 'invalidate_encounter_cache' ), 10, 2 );

        // Statistics invalidation
        add_action( 'kivicare_statistics_changed', array( __CLASS__, 'invalidate_statistics_cache' ) );
    }

    /**
     * Get cached data
     *
     * @param string $key   Cache key
     * @param string $group Cache group
     * @return mixed|false Cached data or false if not found
     * @since 1.0.0
     */
    public static function get( $key, $group = 'default' ) {
        $prefixed_key = self::get_prefixed_key( $key, $group );
        $found = false;
        $data = wp_cache_get( $prefixed_key, $group, false, $found );

        if ( $found ) {
            self::$stats['hits']++;
            
            // Check if data has invalidation tags
            if ( is_array( $data ) && isset( $data['_cache_tags'] ) ) {
                $cache_tags = $data['_cache_tags'];
                unset( $data['_cache_tags'] );
                
                // Check if any tags are invalidated
                if ( self::are_tags_invalidated( $cache_tags ) ) {
                    self::delete( $key, $group );
                    self::$stats['misses']++;
                    return false;
                }
            }
            
            API_Logger::log_performance_issue( null, 0 ); // Log cache hit for debugging
            return $data;
        }

        self::$stats['misses']++;
        return false;
    }

    /**
     * Set cached data
     *
     * @param string $key        Cache key
     * @param mixed  $data       Data to cache
     * @param string $group      Cache group
     * @param int    $expiration Expiration time in seconds (optional)
     * @param array  $tags       Invalidation tags (optional)
     * @return bool Success status
     * @since 1.0.0
     */
    public static function set( $key, $data, $group = 'default', $expiration = null, $tags = array() ) {
        if ( $expiration === null && isset( self::$cache_groups[$group] ) ) {
            $expiration = self::$cache_groups[$group];
        }

        $prefixed_key = self::get_prefixed_key( $key, $group );
        
        // Add invalidation tags if provided
        if ( ! empty( $tags ) ) {
            if ( ! is_array( $data ) ) {
                $data = array( 'data' => $data );
            }
            $data['_cache_tags'] = $tags;
            
            // Store tag mappings
            foreach ( $tags as $tag ) {
                self::add_tag_mapping( $tag, $prefixed_key, $group );
            }
        }

        $result = wp_cache_set( $prefixed_key, $data, $group, $expiration );
        
        if ( $result ) {
            self::$stats['sets']++;
        }

        return $result;
    }

    /**
     * Delete cached data
     *
     * @param string $key   Cache key
     * @param string $group Cache group
     * @return bool Success status
     * @since 1.0.0
     */
    public static function delete( $key, $group = 'default' ) {
        $prefixed_key = self::get_prefixed_key( $key, $group );
        $result = wp_cache_delete( $prefixed_key, $group );
        
        if ( $result ) {
            self::$stats['deletes']++;
        }

        return $result;
    }

    /**
     * Flush cache group
     *
     * @param string $group Cache group to flush
     * @return bool Success status
     * @since 1.0.0
     */
    public static function flush_group( $group ) {
        // WordPress doesn't have group-specific flush, so we track keys manually
        $group_keys = get_option( "kivicare_cache_keys_{$group}", array() );
        
        foreach ( $group_keys as $key ) {
            wp_cache_delete( $key, $group );
        }
        
        delete_option( "kivicare_cache_keys_{$group}" );
        
        API_Logger::log_business_event( 
            'cache_group_flushed',
            "Cache group '{$group}' flushed",
            array( 'keys_count' => count( $group_keys ) )
        );
        
        return true;
    }

    /**
     * Get or set cached data with callback
     *
     * @param string   $key        Cache key
     * @param callable $callback   Callback to generate data if cache miss
     * @param string   $group      Cache group
     * @param int      $expiration Expiration time in seconds
     * @param array    $tags       Invalidation tags
     * @return mixed Cached or generated data
     * @since 1.0.0
     */
    public static function remember( $key, $callback, $group = 'default', $expiration = null, $tags = array() ) {
        $data = self::get( $key, $group );
        
        if ( $data !== false ) {
            return is_array( $data ) && isset( $data['data'] ) ? $data['data'] : $data;
        }

        // Generate data using callback
        $generated_data = call_user_func( $callback );
        
        // Cache the generated data
        self::set( $key, $generated_data, $group, $expiration, $tags );
        
        return $generated_data;
    }

    /**
     * Cache database query result
     *
     * @param string   $query      SQL query
     * @param callable $callback   Callback to execute query
     * @param int      $expiration Cache expiration
     * @return mixed Query result
     * @since 1.0.0
     */
    public static function cache_query( $query, $callback, $expiration = 300 ) {
        $cache_key = 'query_' . md5( $query );
        
        return self::remember( $cache_key, $callback, 'queries', $expiration );
    }

    /**
     * Get patient data with caching
     *
     * @param int  $patient_id Patient ID
     * @param bool $include_related Include related data
     * @return object|null Patient data
     * @since 1.0.0
     */
    public static function get_patient( $patient_id, $include_related = false ) {
        $cache_key = "patient_{$patient_id}";
        if ( $include_related ) {
            $cache_key .= '_with_relations';
        }

        return self::remember( 
            $cache_key,
            function() use ( $patient_id, $include_related ) {
                $patient_service = Integration_Service::get_service( 'patient' );
                $patient = $patient_service->get_by_id( $patient_id );
                
                if ( $include_related && $patient ) {
                    // Add related data like appointments, encounters, etc.
                    $patient->appointments = self::get_patient_appointments( $patient_id );
                    $patient->recent_encounters = self::get_patient_recent_encounters( $patient_id, 5 );
                }
                
                return $patient;
            },
            'patients',
            null,
            array( "patient_{$patient_id}" )
        );
    }

    /**
     * Get doctor data with caching
     *
     * @param int $doctor_id Doctor ID
     * @return object|null Doctor data
     * @since 1.0.0
     */
    public static function get_doctor( $doctor_id ) {
        return self::remember(
            "doctor_{$doctor_id}",
            function() use ( $doctor_id ) {
                $doctor_service = Integration_Service::get_service( 'doctor' );
                return $doctor_service->get_by_id( $doctor_id );
            },
            'doctors',
            null,
            array( "doctor_{$doctor_id}" )
        );
    }

    /**
     * Get clinic statistics with caching
     *
     * @param int   $clinic_id   Clinic ID
     * @param array $date_range  Date range
     * @return array Clinic statistics
     * @since 1.0.0
     */
    public static function get_clinic_statistics( $clinic_id, $date_range = array() ) {
        $cache_key = "clinic_stats_{$clinic_id}_" . md5( serialize( $date_range ) );
        
        return self::remember(
            $cache_key,
            function() use ( $clinic_id, $date_range ) {
                return Integration_Service::execute_operation( 'calculate_clinic_statistics', array(
                    'clinic_id' => $clinic_id,
                    'date_range' => $date_range
                ) );
            },
            'statistics',
            900, // 15 minutes
            array( "clinic_{$clinic_id}_stats" )
        );
    }

    /**
     * Get appointment slots with caching
     *
     * @param int    $doctor_id Doctor ID
     * @param string $date      Date
     * @return array Available slots
     * @since 1.0.0
     */
    public static function get_available_slots( $doctor_id, $date ) {
        $cache_key = "slots_{$doctor_id}_{$date}";
        
        return self::remember(
            $cache_key,
            function() use ( $doctor_id, $date ) {
                $appointment_service = Integration_Service::get_service( 'appointment' );
                return $appointment_service->get_available_slots( $doctor_id, $date );
            },
            'appointments',
            1800, // 30 minutes
            array( "doctor_{$doctor_id}_slots", "appointments_{$date}" )
        );
    }

    /**
     * Warm critical cache data
     *
     * @since 1.0.0
     */
    public static function warm_critical_cache() {
        // Only warm cache during off-peak hours or when explicitly requested
        if ( ! self::should_warm_cache() ) {
            return;
        }

        // Warm frequently accessed clinic data
        self::warm_clinic_cache();
        
        // Warm active doctor data
        self::warm_doctor_cache();
        
        // Warm today's appointment data
        self::warm_today_appointments();
        
        API_Logger::log_business_event( 'cache_warmed', 'Critical cache data warmed' );
    }

    /**
     * Check if cache should be warmed
     *
     * @return bool Whether to warm cache
     * @since 1.0.0
     */
    private static function should_warm_cache() {
        $current_hour = (int) date( 'H' );
        
        // Warm cache during off-peak hours (2 AM - 6 AM)
        if ( $current_hour >= 2 && $current_hour <= 6 ) {
            return true;
        }
        
        // Check if explicitly requested
        return get_option( 'kivicare_force_cache_warm', false );
    }

    /**
     * Warm clinic cache
     *
     * @since 1.0.0
     */
    private static function warm_clinic_cache() {
        global $wpdb;
        
        $clinic_ids = $wpdb->get_col(
            $wpdb->prepare(
                "SELECT id FROM {$wpdb->prefix}kc_clinics WHERE status = %d LIMIT %d",
                1, 10
            )
        );
        
        foreach ( $clinic_ids as $clinic_id ) {
            self::get_clinic_statistics( $clinic_id );
        }
    }

    /**
     * Warm doctor cache
     *
     * @since 1.0.0
     */
    private static function warm_doctor_cache() {
        global $wpdb;
        
        $doctor_ids = $wpdb->get_col(
            $wpdb->prepare(
                "SELECT DISTINCT doctor_id FROM {$wpdb->prefix}kc_appointments 
                 WHERE appointment_start_date >= CURDATE() 
                 AND appointment_start_date <= DATE_ADD(CURDATE(), INTERVAL 7 DAY)
                 LIMIT %d",
                20
            )
        );
        
        foreach ( $doctor_ids as $doctor_id ) {
            self::get_doctor( $doctor_id );
        }
    }

    /**
     * Warm today's appointment cache
     *
     * @since 1.0.0
     */
    private static function warm_today_appointments() {
        global $wpdb;
        
        $appointments = $wpdb->get_results( $wpdb->prepare(
            "SELECT patient_id, doctor_id FROM {$wpdb->prefix}kc_appointments 
             WHERE appointment_start_date = %s
             LIMIT 50",
            date( 'Y-m-d' )
        ) );
        
        foreach ( $appointments as $appointment ) {
            self::get_patient( $appointment->patient_id );
            self::get_doctor( $appointment->doctor_id );
        }
    }

    /**
     * Invalidate patient cache
     *
     * @param int   $patient_id   Patient ID
     * @param array $patient_data Patient data
     * @since 1.0.0
     */
    public static function invalidate_patient_cache( $patient_id, $patient_data = array() ) {
        self::invalidate_by_tag( "patient_{$patient_id}" );
        self::flush_group( 'statistics' );
        
        API_Logger::log_business_event( 'cache_invalidated', "Patient {$patient_id} cache invalidated" );
    }

    /**
     * Invalidate doctor cache
     *
     * @param int   $doctor_id   Doctor ID
     * @param array $doctor_data Doctor data
     * @since 1.0.0
     */
    public static function invalidate_doctor_cache( $doctor_id, $doctor_data = array() ) {
        self::invalidate_by_tag( "doctor_{$doctor_id}" );
        self::invalidate_by_tag( "doctor_{$doctor_id}_slots" );
        
        API_Logger::log_business_event( 'cache_invalidated', "Doctor {$doctor_id} cache invalidated" );
    }

    /**
     * Invalidate appointment cache
     *
     * @param int   $appointment_id   Appointment ID
     * @param array $appointment_data Appointment data
     * @since 1.0.0
     */
    public static function invalidate_appointment_cache( $appointment_id, $appointment_data = array() ) {
        if ( isset( $appointment_data['doctor_id'] ) ) {
            self::invalidate_by_tag( "doctor_{$appointment_data['doctor_id']}_slots" );
        }
        
        if ( isset( $appointment_data['appointment_start_date'] ) ) {
            self::invalidate_by_tag( "appointments_{$appointment_data['appointment_start_date']}" );
        }
        
        self::flush_group( 'statistics' );
    }

    /**
     * Invalidate encounter cache
     *
     * @param int   $encounter_id   Encounter ID
     * @param array $encounter_data Encounter data
     * @since 1.0.0
     */
    public static function invalidate_encounter_cache( $encounter_id, $encounter_data = array() ) {
        if ( isset( $encounter_data['patient_id'] ) ) {
            self::invalidate_by_tag( "patient_{$encounter_data['patient_id']}" );
        }
        
        self::flush_group( 'statistics' );
    }

    /**
     * Invalidate statistics cache
     *
     * @since 1.0.0
     */
    public static function invalidate_statistics_cache() {
        self::flush_group( 'statistics' );
    }

    /**
     * Invalidate cache by tag
     *
     * @param string $tag Cache tag
     * @since 1.0.0
     */
    public static function invalidate_by_tag( $tag ) {
        $tag_mappings = get_option( "kivicare_cache_tag_{$tag}", array() );
        
        foreach ( $tag_mappings as $mapping ) {
            wp_cache_delete( $mapping['key'], $mapping['group'] );
        }
        
        delete_option( "kivicare_cache_tag_{$tag}" );
        self::$invalidation_tags[$tag] = time();
    }

    /**
     * Clean up expired cache
     *
     * @since 1.0.0
     */
    public static function cleanup_expired_cache() {
        // Clean up tag mappings older than 24 hours
        $options = wp_load_alloptions();
        $expired_count = 0;
        
        foreach ( $options as $option_name => $option_value ) {
            if ( strpos( $option_name, 'kivicare_cache_tag_' ) === 0 ) {
                $tag_data = maybe_unserialize( $option_value );
                if ( is_array( $tag_data ) && isset( $tag_data['timestamp'] ) ) {
                    if ( time() - $tag_data['timestamp'] > 86400 ) { // 24 hours
                        delete_option( $option_name );
                        $expired_count++;
                    }
                }
            }
        }
        
        if ( $expired_count > 0 ) {
            API_Logger::log_business_event( 
                'cache_cleanup_completed',
                "Cleaned up {$expired_count} expired cache entries"
            );
        }
    }

    /**
     * Get cache statistics
     *
     * @return array Cache statistics
     * @since 1.0.0
     */
    public static function get_statistics() {
        $total_requests = self::$stats['hits'] + self::$stats['misses'];
        $hit_ratio = $total_requests > 0 ? ( self::$stats['hits'] / $total_requests ) * 100 : 0;
        
        return array(
            'hits' => self::$stats['hits'],
            'misses' => self::$stats['misses'],
            'sets' => self::$stats['sets'],
            'deletes' => self::$stats['deletes'],
            'hit_ratio' => round( $hit_ratio, 2 ),
            'total_requests' => $total_requests
        );
    }

    /**
     * Log cache statistics
     *
     * @since 1.0.0
     */
    public static function log_cache_statistics() {
        $stats = self::get_statistics();
        
        if ( $stats['total_requests'] > 0 ) {
            API_Logger::log_business_event( 
                'cache_statistics',
                'Cache performance statistics',
                $stats
            );
        }
    }

    /**
     * Helper methods
     */

    /**
     * Get prefixed cache key
     *
     * @param string $key   Original key
     * @param string $group Cache group
     * @return string Prefixed key
     * @since 1.0.0
     */
    private static function get_prefixed_key( $key, $group ) {
        $prefix = self::$cache_prefixes['object'];
        
        if ( $group === 'queries' ) {
            $prefix = self::$cache_prefixes['query'];
        } elseif ( $group === 'statistics' ) {
            $prefix = self::$cache_prefixes['stats'];
        }
        
        return $prefix . $key;
    }

    /**
     * Add tag mapping
     *
     * @param string $tag   Cache tag
     * @param string $key   Cache key
     * @param string $group Cache group
     * @since 1.0.0
     */
    private static function add_tag_mapping( $tag, $key, $group ) {
        $mappings = get_option( "kivicare_cache_tag_{$tag}", array() );
        $mappings[] = array( 'key' => $key, 'group' => $group );
        update_option( "kivicare_cache_tag_{$tag}", $mappings );
    }

    /**
     * Check if tags are invalidated
     *
     * @param array $tags Cache tags to check
     * @return bool True if any tag is invalidated
     * @since 1.0.0
     */
    private static function are_tags_invalidated( $tags ) {
        foreach ( $tags as $tag ) {
            if ( isset( self::$invalidation_tags[$tag] ) ) {
                return true;
            }
        }
        return false;
    }

    /**
     * Get patient appointments (helper for caching)
     *
     * @param int $patient_id Patient ID
     * @return array Appointments
     * @since 1.0.0
     */
    private static function get_patient_appointments( $patient_id ) {
        $appointment_service = Integration_Service::get_service( 'appointment' );
        return $appointment_service->get_by_patient( $patient_id, array( 'limit' => 10 ) );
    }

    /**
     * Get patient recent encounters (helper for caching)
     *
     * @param int $patient_id Patient ID
     * @param int $limit      Limit
     * @return array Recent encounters
     * @since 1.0.0
     */
    private static function get_patient_recent_encounters( $patient_id, $limit = 5 ) {
        $encounter_service = Integration_Service::get_service( 'encounter' );
        return $encounter_service->get_by_patient( $patient_id, array( 'limit' => $limit ) );
    }
}