<?php
/**
 * Appointment Model
 *
 * Handles appointment entity operations, scheduling and availability
 *
 * @package Care_API
 * @subpackage Models
 * @version    1.0.0
 * @author     Descomplicar® <dev@descomplicar.pt>
 * @link       https://descomplicar.pt
 * @since      1.0.0
 */

namespace Care_API\Models;

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

/**
 * Class Appointment
 *
 * Model for handling appointment scheduling, availability and management
 *
 * @since 1.0.0
 */
class Appointment {

    /**
     * Database table name
     *
     * @var string
     */
    private static $table_name = 'kc_appointments';

    /**
     * Appointment ID
     *
     * @var int
     */
    public $id;

    /**
     * Appointment data
     *
     * @var array
     */
    private $data = array();

    /**
     * Required fields for appointment creation
     *
     * @var array
     */
    private static $required_fields = array(
        'appointment_start_date',
        'appointment_start_time',
        'appointment_end_time',
        'clinic_id',
        'doctor_id',
        'patient_id'
    );

    /**
     * Valid appointment statuses
     *
     * @var array
     */
    private static $valid_statuses = array(
        1 => 'scheduled',
        2 => 'completed',
        3 => 'cancelled',
        4 => 'no_show'
    );

    /**
     * Constructor
     *
     * @param int|array $appointment_id_or_data Appointment ID or data array
     * @since 1.0.0
     */
    public function __construct( $appointment_id_or_data = null ) {
        if ( is_numeric( $appointment_id_or_data ) ) {
            $this->id = (int) $appointment_id_or_data;
            $this->load_data();
        } elseif ( is_array( $appointment_id_or_data ) ) {
            $this->data = $appointment_id_or_data;
            $this->id = isset( $this->data['id'] ) ? (int) $this->data['id'] : null;
        }
    }

    /**
     * Load appointment data from database
     *
     * @return bool True on success, false on failure
     * @since 1.0.0
     */
    private function load_data() {
        if ( ! $this->id ) {
            return false;
        }

        $appointment_data = self::get_appointment_full_data( $this->id );
        
        if ( $appointment_data ) {
            $this->data = $appointment_data;
            return true;
        }

        return false;
    }

    /**
     * Create a new appointment
     *
     * @param array $appointment_data Appointment data
     * @return int|WP_Error Appointment ID on success, WP_Error on failure
     * @since 1.0.0
     */
    public static function create( $appointment_data ) {
        // Validate required fields
        $validation = self::validate_appointment_data( $appointment_data );
        if ( is_wp_error( $validation ) ) {
            return $validation;
        }

        // Check availability
        $availability_check = self::check_availability( $appointment_data );
        if ( is_wp_error( $availability_check ) ) {
            return $availability_check;
        }

        global $wpdb;
        $table = $wpdb->prefix . self::$table_name;

        // Calculate end date (assume same day if not provided)
        $end_date = isset( $appointment_data['appointment_end_date'] ) 
            ? $appointment_data['appointment_end_date'] 
            : $appointment_data['appointment_start_date'];

        // Prepare data for insertion
        $insert_data = array(
            'appointment_start_date' => sanitize_text_field( $appointment_data['appointment_start_date'] ),
            'appointment_start_time' => sanitize_text_field( $appointment_data['appointment_start_time'] ),
            'appointment_end_date' => sanitize_text_field( $end_date ),
            'appointment_end_time' => sanitize_text_field( $appointment_data['appointment_end_time'] ),
            'visit_type' => isset( $appointment_data['visit_type'] ) ? sanitize_text_field( $appointment_data['visit_type'] ) : 'consultation',
            'clinic_id' => (int) $appointment_data['clinic_id'],
            'doctor_id' => (int) $appointment_data['doctor_id'],
            'patient_id' => (int) $appointment_data['patient_id'],
            'description' => isset( $appointment_data['description'] ) ? sanitize_textarea_field( $appointment_data['description'] ) : '',
            'status' => isset( $appointment_data['status'] ) ? (int) $appointment_data['status'] : 1,
            'created_at' => current_time( 'mysql' ),
            'appointment_report' => ''
        );

        $result = $wpdb->insert( $table, $insert_data );

        if ( $result === false ) {
            return new \WP_Error(
                'appointment_creation_failed',
                'Failed to create appointment: ' . $wpdb->last_error,
                array( 'status' => 500 )
            );
        }

        $appointment_id = $wpdb->insert_id;

        // Create service mappings if provided
        if ( ! empty( $appointment_data['services'] ) && is_array( $appointment_data['services'] ) ) {
            self::create_service_mappings( $appointment_id, $appointment_data['services'] );
        }

        return $appointment_id;
    }

    /**
     * Update appointment data
     *
     * @param int   $appointment_id Appointment ID
     * @param array $appointment_data Updated appointment data
     * @return bool|WP_Error True on success, WP_Error on failure
     * @since 1.0.0
     */
    public static function update( $appointment_id, $appointment_data ) {
        if ( ! self::exists( $appointment_id ) ) {
            return new \WP_Error(
                'appointment_not_found',
                'Appointment not found',
                array( 'status' => 404 )
            );
        }

        // If updating schedule details, check availability
        if ( isset( $appointment_data['appointment_start_date'] ) ||
             isset( $appointment_data['appointment_start_time'] ) ||
             isset( $appointment_data['appointment_end_time'] ) ||
             isset( $appointment_data['doctor_id'] ) ) {
            
            $current_data = self::get_by_id( $appointment_id );
            $check_data = array_merge( $current_data, $appointment_data );
            $check_data['exclude_appointment_id'] = $appointment_id;
            
            $availability_check = self::check_availability( $check_data );
            if ( is_wp_error( $availability_check ) ) {
                return $availability_check;
            }
        }

        global $wpdb;
        $table = $wpdb->prefix . self::$table_name;

        // Prepare update data
        $update_data = array();
        $allowed_fields = array(
            'appointment_start_date', 'appointment_start_time', 'appointment_end_date',
            'appointment_end_time', 'visit_type', 'clinic_id', 'doctor_id', 'patient_id',
            'description', 'status', 'appointment_report'
        );

        foreach ( $allowed_fields as $field ) {
            if ( isset( $appointment_data[ $field ] ) ) {
                $value = $appointment_data[ $field ];
                
                switch ( $field ) {
                    case 'clinic_id':
                    case 'doctor_id':
                    case 'patient_id':
                    case 'status':
                        $update_data[ $field ] = (int) $value;
                        break;
                    case 'description':
                    case 'appointment_report':
                        $update_data[ $field ] = sanitize_textarea_field( $value );
                        break;
                    default:
                        $update_data[ $field ] = sanitize_text_field( $value );
                }
            }
        }

        if ( empty( $update_data ) ) {
            return new \WP_Error(
                'no_data_to_update',
                'No valid data provided for update',
                array( 'status' => 400 )
            );
        }

        $result = $wpdb->update(
            $table,
            $update_data,
            array( 'id' => $appointment_id ),
            null,
            array( '%d' )
        );

        if ( $result === false ) {
            return new \WP_Error(
                'appointment_update_failed',
                'Failed to update appointment: ' . $wpdb->last_error,
                array( 'status' => 500 )
            );
        }

        // Update service mappings if provided
        if ( isset( $appointment_data['services'] ) && is_array( $appointment_data['services'] ) ) {
            self::update_service_mappings( $appointment_id, $appointment_data['services'] );
        }

        return true;
    }

    /**
     * Delete an appointment
     *
     * @param int $appointment_id Appointment ID
     * @return bool|WP_Error True on success, WP_Error on failure
     * @since 1.0.0
     */
    public static function delete( $appointment_id ) {
        if ( ! self::exists( $appointment_id ) ) {
            return new \WP_Error(
                'appointment_not_found',
                'Appointment not found',
                array( 'status' => 404 )
            );
        }

        // Check if appointment can be deleted (not completed with encounters)
        if ( self::has_completed_encounter( $appointment_id ) ) {
            return new \WP_Error(
                'appointment_has_encounter',
                'Cannot delete appointment with completed encounter',
                array( 'status' => 409 )
            );
        }

        global $wpdb;

        // Delete service mappings first
        $wpdb->delete(
            "{$wpdb->prefix}kc_appointment_service_mapping",
            array( 'appointment_id' => $appointment_id ),
            array( '%d' )
        );

        // Delete appointment
        $table = $wpdb->prefix . self::$table_name;
        $result = $wpdb->delete(
            $table,
            array( 'id' => $appointment_id ),
            array( '%d' )
        );

        if ( $result === false ) {
            return new \WP_Error(
                'appointment_deletion_failed',
                'Failed to delete appointment: ' . $wpdb->last_error,
                array( 'status' => 500 )
            );
        }

        return true;
    }

    /**
     * Get appointment by ID
     *
     * @param int $appointment_id Appointment ID
     * @return array|null Appointment data or null if not found
     * @since 1.0.0
     */
    public static function get_by_id( $appointment_id ) {
        return self::get_appointment_full_data( $appointment_id );
    }

    /**
     * Get all appointments with optional filtering
     *
     * @param array $args Query arguments
     * @return array Array of appointment data
     * @since 1.0.0
     */
    public static function get_all( $args = array() ) {
        global $wpdb;
        $table = $wpdb->prefix . self::$table_name;

        $defaults = array(
            'clinic_id' => null,
            'doctor_id' => null,
            'patient_id' => null,
            'status' => null,
            'date_from' => null,
            'date_to' => null,
            'search' => '',
            'limit' => 50,
            'offset' => 0,
            'orderby' => 'appointment_start_date',
            'order' => 'ASC'
        );

        $args = wp_parse_args( $args, $defaults );

        $where_clauses = array( '1=1' );
        $where_values = array();

        // Clinic filter
        if ( ! is_null( $args['clinic_id'] ) ) {
            $where_clauses[] = 'a.clinic_id = %d';
            $where_values[] = $args['clinic_id'];
        }

        // Doctor filter
        if ( ! is_null( $args['doctor_id'] ) ) {
            $where_clauses[] = 'a.doctor_id = %d';
            $where_values[] = $args['doctor_id'];
        }

        // Patient filter
        if ( ! is_null( $args['patient_id'] ) ) {
            $where_clauses[] = 'a.patient_id = %d';
            $where_values[] = $args['patient_id'];
        }

        // Status filter
        if ( ! is_null( $args['status'] ) ) {
            $where_clauses[] = 'a.status = %d';
            $where_values[] = $args['status'];
        }

        // Date range filters
        if ( ! is_null( $args['date_from'] ) ) {
            $where_clauses[] = 'a.appointment_start_date >= %s';
            $where_values[] = $args['date_from'];
        }

        if ( ! is_null( $args['date_to'] ) ) {
            $where_clauses[] = 'a.appointment_start_date <= %s';
            $where_values[] = $args['date_to'];
        }

        // Search filter
        if ( ! empty( $args['search'] ) ) {
            $where_clauses[] = '(p.first_name LIKE %s OR p.last_name LIKE %s OR d.first_name LIKE %s OR d.last_name LIKE %s OR c.name LIKE %s)';
            $search_term = '%' . $wpdb->esc_like( $args['search'] ) . '%';
            $where_values = array_merge( $where_values, array_fill( 0, 5, $search_term ) );
        }

        $where_sql = implode( ' AND ', $where_clauses );

        // Whitelist for orderby
        $allowed_orderby = array( 'appointment_start_date', 'patient_name', 'doctor_name', 'clinic_name', 'status' );
        $orderby = in_array( $args['orderby'], $allowed_orderby, true ) ? $args['orderby'] : 'appointment_start_date';
        
        // Whitelist for order
        $order = in_array( strtoupper( $args['order'] ), array( 'ASC', 'DESC' ), true ) ? strtoupper( $args['order'] ) : 'ASC';

        // Build query
        $query = "SELECT a.*, 
                         c.name as clinic_name,
                         CONCAT(p.first_name, ' ', p.last_name) as patient_name,
                         p.user_email as patient_email,
                         CONCAT(d.first_name, ' ', d.last_name) as doctor_name,
                         d.user_email as doctor_email
                  FROM {$table} a
                  LEFT JOIN {$wpdb->prefix}kc_clinics c ON a.clinic_id = c.id
                  LEFT JOIN {$wpdb->prefix}users p ON a.patient_id = p.ID
                  LEFT JOIN {$wpdb->prefix}users d ON a.doctor_id = d.ID
                  WHERE {$where_sql}
                  ORDER BY {$orderby} {$order}
                  LIMIT %d OFFSET %d";

        $safe_query = $wpdb->prepare(
            $query,
            array_merge(
                $where_values,
                array( $args['limit'], $args['offset'] )
            )
        );

        $appointments = $wpdb->get_results( $safe_query, ARRAY_A );

        return array_map( array( self::class, 'format_appointment_data' ), $appointments );
    }

    /**
     * Get available time slots for a doctor on a specific date
     *
     * @param array $args Availability parameters
     * @return array|WP_Error Available slots or error
     * @since 1.0.0
     */
    public static function get_available_slots( $args ) {
        $defaults = array(
            'doctor_id' => null,
            'clinic_id' => null,
            'date' => date( 'Y-m-d' ),
            'duration' => 30 // minutes
        );

        $args = wp_parse_args( $args, $defaults );

        if ( is_null( $args['doctor_id'] ) ) {
            return new \WP_Error(
                'missing_doctor_id',
                'Doctor ID is required',
                array( 'status' => 400 )
            );
        }

        // Get doctor's working hours for the day
        $day_of_week = strtolower( date( 'l', strtotime( $args['date'] ) ) );
        $working_hours = get_user_meta( $args['doctor_id'], 'working_hours', true );
        
        if ( is_string( $working_hours ) ) {
            $working_hours = json_decode( $working_hours, true ) ?: array();
        }

        if ( empty( $working_hours[ $day_of_week ] ) || 
             ! isset( $working_hours[ $day_of_week ]['start_time'] ) ||
             ! isset( $working_hours[ $day_of_week ]['end_time'] ) ) {
            return array(
                'available_slots' => array(),
                'message' => 'Doctor is not available on this day'
            );
        }

        $start_time = $working_hours[ $day_of_week ]['start_time'];
        $end_time = $working_hours[ $day_of_week ]['end_time'];

        // Get existing appointments for the day
        global $wpdb;
        $table = $wpdb->prefix . self::$table_name;

        $where_clauses = array(
            'doctor_id = %d',
            'appointment_start_date = %s',
            'status IN (1, 2)' // scheduled or completed
        );
        $where_values = array( $args['doctor_id'], $args['date'] );

        if ( ! is_null( $args['clinic_id'] ) ) {
            $where_clauses[] = 'clinic_id = %d';
            $where_values[] = $args['clinic_id'];
        }

        $existing_appointments = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT appointment_start_time, appointment_end_time 
                 FROM {$table} 
                 WHERE " . implode( ' AND ', $where_clauses ),
                $where_values
            ),
            ARRAY_A
        );

        // Generate all possible slots
        $all_slots = self::generate_time_slots( $start_time, $end_time, $args['duration'] );
        
        // Remove booked slots
        $available_slots = array();
        foreach ( $all_slots as $slot ) {
            $is_available = true;
            
            foreach ( $existing_appointments as $appointment ) {
                if ( self::times_overlap( 
                    $slot['start_time'], 
                    $slot['end_time'],
                    $appointment['appointment_start_time'],
                    $appointment['appointment_end_time']
                ) ) {
                    $is_available = false;
                    break;
                }
            }
            
            if ( $is_available ) {
                $available_slots[] = $slot;
            }
        }

        return array(
            'date' => $args['date'],
            'doctor_id' => $args['doctor_id'],
            'clinic_id' => $args['clinic_id'],
            'working_hours' => array(
                'start' => $start_time,
                'end' => $end_time
            ),
            'slot_duration' => $args['duration'],
            'total_slots' => count( $all_slots ),
            'available_slots' => $available_slots,
            'booked_slots' => count( $all_slots ) - count( $available_slots )
        );
    }

    /**
     * Check appointment availability
     *
     * @param array $appointment_data Appointment data to check
     * @return bool|WP_Error True if available, WP_Error if conflict
     * @since 1.0.0
     */
    public static function check_availability( $appointment_data ) {
        global $wpdb;
        $table = $wpdb->prefix . self::$table_name;

        $where_clauses = array(
            'doctor_id = %d',
            'appointment_start_date = %s',
            'status IN (1, 2)' // scheduled or completed
        );
        
        $where_values = array(
            $appointment_data['doctor_id'],
            $appointment_data['appointment_start_date']
        );

        // Exclude current appointment if updating
        if ( isset( $appointment_data['exclude_appointment_id'] ) ) {
            $where_clauses[] = 'id != %d';
            $where_values[] = $appointment_data['exclude_appointment_id'];
        }

        $conflicting_appointments = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT id, appointment_start_time, appointment_end_time 
                 FROM {$table} 
                 WHERE " . implode( ' AND ', $where_clauses ),
                $where_values
            ),
            ARRAY_A
        );

        $start_time = $appointment_data['appointment_start_time'];
        $end_time = $appointment_data['appointment_end_time'];

        foreach ( $conflicting_appointments as $existing ) {
            if ( self::times_overlap( 
                $start_time, 
                $end_time,
                $existing['appointment_start_time'],
                $existing['appointment_end_time']
            ) ) {
                return new \WP_Error(
                    'appointment_conflict',
                    'Doctor is not available at the requested time',
                    array( 
                        'status' => 409,
                        'conflicting_appointment_id' => $existing['id']
                    )
                );
            }
        }

        return true;
    }

    /**
     * Get appointment full data with related entities
     *
     * @param int $appointment_id Appointment ID
     * @return array|null Full appointment data or null if not found
     * @since 1.0.0
     */
    public static function get_appointment_full_data( $appointment_id ) {
        global $wpdb;
        $table = $wpdb->prefix . self::$table_name;

        $appointment = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT a.*, 
                        c.name as clinic_name, c.address as clinic_address,
                        CONCAT(p.first_name, ' ', p.last_name) as patient_name,
                        p.user_email as patient_email,
                        CONCAT(d.first_name, ' ', d.last_name) as doctor_name,
                        d.user_email as doctor_email
                 FROM {$table} a
                 LEFT JOIN {$wpdb->prefix}kc_clinics c ON a.clinic_id = c.id
                 LEFT JOIN {$wpdb->prefix}users p ON a.patient_id = p.ID
                 LEFT JOIN {$wpdb->prefix}users d ON a.doctor_id = d.ID
                 WHERE a.id = %d",
                $appointment_id
            ),
            ARRAY_A
        );

        if ( ! $appointment ) {
            return null;
        }

        return self::format_appointment_data( $appointment );
    }

    /**
     * Get appointment services
     *
     * @param int $appointment_id Appointment ID
     * @return array Array of services
     * @since 1.0.0
     */
    public static function get_services( $appointment_id ) {
        global $wpdb;

        $services = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT s.*, asm.created_at as mapped_at
                 FROM {$wpdb->prefix}kc_appointment_service_mapping asm
                 LEFT JOIN {$wpdb->prefix}kc_services s ON asm.service_id = s.id
                 WHERE asm.appointment_id = %d",
                $appointment_id
            ),
            ARRAY_A
        );

        return array_map( function( $service ) {
            return array(
                'id' => (int) $service['id'],
                'name' => $service['name'],
                'type' => $service['type'],
                'price' => (float) $service['price'],
                'status' => (int) $service['status'],
                'mapped_at' => $service['mapped_at']
            );
        }, $services );
    }

    /**
     * Check if appointment exists
     *
     * @param int $appointment_id Appointment ID
     * @return bool True if exists, false otherwise
     * @since 1.0.0
     */
    public static function exists( $appointment_id ) {
        global $wpdb;
        $table = $wpdb->prefix . self::$table_name;

        $count = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$table} WHERE id = %d",
                $appointment_id
            )
        );

        return (int) $count > 0;
    }

    /**
     * Check if appointment has completed encounter
     *
     * @param int $appointment_id Appointment ID
     * @return bool True if has encounter, false otherwise
     * @since 1.0.0
     */
    private static function has_completed_encounter( $appointment_id ) {
        global $wpdb;

        $count = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->prefix}kc_patient_encounters 
                 WHERE appointment_id = %d AND status = 1",
                $appointment_id
            )
        );

        return (int) $count > 0;
    }

    /**
     * Validate appointment data
     *
     * @param array $appointment_data Appointment data to validate
     * @return bool|WP_Error True if valid, WP_Error if invalid
     * @since 1.0.0
     */
    private static function validate_appointment_data( $appointment_data ) {
        $errors = array();

        // Check required fields
        foreach ( self::$required_fields as $field ) {
            if ( empty( $appointment_data[ $field ] ) ) {
                $errors[] = "Field '{$field}' is required";
            }
        }

        // Validate date format
        if ( ! empty( $appointment_data['appointment_start_date'] ) ) {
            $date = \DateTime::createFromFormat( 'Y-m-d', $appointment_data['appointment_start_date'] );
            if ( ! $date || $date->format( 'Y-m-d' ) !== $appointment_data['appointment_start_date'] ) {
                $errors[] = 'Invalid start date format. Use YYYY-MM-DD';
            }
        }

        // Validate time formats
        $time_fields = array( 'appointment_start_time', 'appointment_end_time' );
        foreach ( $time_fields as $field ) {
            if ( ! empty( $appointment_data[ $field ] ) ) {
                if ( ! preg_match( '/^([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$/', $appointment_data[ $field ] ) ) {
                    $errors[] = "Invalid {$field} format. Use HH:MM or HH:MM:SS";
                }
            }
        }

        // Check if end time is after start time
        if ( ! empty( $appointment_data['appointment_start_time'] ) && 
             ! empty( $appointment_data['appointment_end_time'] ) ) {
            $start = strtotime( $appointment_data['appointment_start_time'] );
            $end = strtotime( $appointment_data['appointment_end_time'] );
            
            if ( $end <= $start ) {
                $errors[] = 'End time must be after start time';
            }
        }

        // Validate entities exist
        if ( ! empty( $appointment_data['clinic_id'] ) && 
             ! Clinic::exists( $appointment_data['clinic_id'] ) ) {
            $errors[] = 'Clinic not found';
        }

        if ( ! empty( $appointment_data['doctor_id'] ) && 
             ! Doctor::exists( $appointment_data['doctor_id'] ) ) {
            $errors[] = 'Doctor not found';
        }

        if ( ! empty( $appointment_data['patient_id'] ) && 
             ! Patient::exists( $appointment_data['patient_id'] ) ) {
            $errors[] = 'Patient not found';
        }

        // Validate status
        if ( isset( $appointment_data['status'] ) && 
             ! array_key_exists( $appointment_data['status'], self::$valid_statuses ) ) {
            $errors[] = 'Invalid status value';
        }

        if ( ! empty( $errors ) ) {
            return new \WP_Error(
                'appointment_validation_failed',
                'Appointment validation failed',
                array( 
                    'status' => 400,
                    'errors' => $errors 
                )
            );
        }

        return true;
    }

    /**
     * Format appointment data for API response
     *
     * @param array $appointment_data Raw appointment data
     * @return array Formatted appointment data
     * @since 1.0.0
     */
    private static function format_appointment_data( $appointment_data ) {
        if ( ! $appointment_data ) {
            return null;
        }

        $formatted = array(
            'id' => (int) $appointment_data['id'],
            'start_date' => $appointment_data['appointment_start_date'],
            'start_time' => $appointment_data['appointment_start_time'],
            'end_date' => $appointment_data['appointment_end_date'],
            'end_time' => $appointment_data['appointment_end_time'],
            'visit_type' => $appointment_data['visit_type'],
            'status' => (int) $appointment_data['status'],
            'status_text' => self::$valid_statuses[ $appointment_data['status'] ] ?? 'unknown',
            'description' => $appointment_data['description'],
            'appointment_report' => $appointment_data['appointment_report'],
            'created_at' => $appointment_data['created_at'],
            'clinic' => array(
                'id' => (int) $appointment_data['clinic_id'],
                'name' => $appointment_data['clinic_name'] ?? '',
                'address' => $appointment_data['clinic_address'] ?? ''
            ),
            'patient' => array(
                'id' => (int) $appointment_data['patient_id'],
                'name' => $appointment_data['patient_name'] ?? '',
                'email' => $appointment_data['patient_email'] ?? ''
            ),
            'doctor' => array(
                'id' => (int) $appointment_data['doctor_id'],
                'name' => $appointment_data['doctor_name'] ?? '',
                'email' => $appointment_data['doctor_email'] ?? ''
            )
        );

        return $formatted;
    }

    /**
     * Create service mappings for appointment
     *
     * @param int   $appointment_id Appointment ID
     * @param array $service_ids    Array of service IDs
     * @return bool True on success
     * @since 1.0.0
     */
    private static function create_service_mappings( $appointment_id, $service_ids ) {
        global $wpdb;

        foreach ( $service_ids as $service_id ) {
            $wpdb->insert(
                "{$wpdb->prefix}kc_appointment_service_mapping",
                array(
                    'appointment_id' => $appointment_id,
                    'service_id' => (int) $service_id,
                    'created_at' => current_time( 'mysql' )
                ),
                array( '%d', '%d', '%s' )
            );
        }

        return true;
    }

    /**
     * Update service mappings for appointment
     *
     * @param int   $appointment_id Appointment ID
     * @param array $service_ids    Array of service IDs
     * @return bool True on success
     * @since 1.0.0
     */
    private static function update_service_mappings( $appointment_id, $service_ids ) {
        global $wpdb;

        // Delete existing mappings
        $wpdb->delete(
            "{$wpdb->prefix}kc_appointment_service_mapping",
            array( 'appointment_id' => $appointment_id ),
            array( '%d' )
        );

        // Create new mappings
        return self::create_service_mappings( $appointment_id, $service_ids );
    }

    /**
     * Generate time slots between start and end time
     *
     * @param string $start_time Start time (HH:MM)
     * @param string $end_time   End time (HH:MM)
     * @param int    $duration   Slot duration in minutes
     * @return array Array of time slots
     * @since 1.0.0
     */
    private static function generate_time_slots( $start_time, $end_time, $duration ) {
        $slots = array();
        $current = strtotime( $start_time );
        $end = strtotime( $end_time );
        
        while ( $current < $end ) {
            $slot_end = $current + ( $duration * 60 );
            
            if ( $slot_end <= $end ) {
                $slots[] = array(
                    'start_time' => date( 'H:i', $current ),
                    'end_time' => date( 'H:i', $slot_end ),
                    'duration' => $duration
                );
            }
            
            $current = $slot_end;
        }
        
        return $slots;
    }

    /**
     * Check if two time ranges overlap
     *
     * @param string $start1 First range start time
     * @param string $end1   First range end time
     * @param string $start2 Second range start time
     * @param string $end2   Second range end time
     * @return bool True if overlapping
     * @since 1.0.0
     */
    private static function times_overlap( $start1, $end1, $start2, $end2 ) {
        $start1_ts = strtotime( $start1 );
        $end1_ts = strtotime( $end1 );
        $start2_ts = strtotime( $start2 );
        $end2_ts = strtotime( $end2 );
        
        return ( $start1_ts < $end2_ts && $end1_ts > $start2_ts );
    }

    /**
     * Get appointment statistics
     *
     * @param array $filters Optional filters
     * @return array Appointment statistics
     * @since 1.0.0
     */
    public static function get_statistics( $filters = array() ) {
        global $wpdb;
        $table = $wpdb->prefix . self::$table_name;

        $where_clauses = array( '1=1' );
        $where_values = array();

        if ( ! empty( $filters['clinic_id'] ) ) {
            $where_clauses[] = 'clinic_id = %d';
            $where_values[] = $filters['clinic_id'];
        }

        if ( ! empty( $filters['doctor_id'] ) ) {
            $where_clauses[] = 'doctor_id = %d';
            $where_values[] = $filters['doctor_id'];
        }

        $where_sql = implode( ' AND ', $where_clauses );

        $stats = array(
            'total_appointments' => 0,
            'scheduled_appointments' => 0,
            'completed_appointments' => 0,
            'cancelled_appointments' => 0,
            'no_show_appointments' => 0,
            'appointments_today' => 0,
            'appointments_this_week' => 0,
            'appointments_this_month' => 0
        );

        // Total appointments
        $query = "SELECT COUNT(*) FROM {$table} WHERE {$where_sql}";
        $stats['total_appointments'] = (int) $wpdb->get_var( $wpdb->prepare( $query, $where_values ) );

        // Appointments by status
        foreach ( self::$valid_statuses as $status_id => $status_name ) {
            $status_where_sql = $where_sql . ' AND status = %d';
            $query = "SELECT COUNT(*) FROM {$table} WHERE {$status_where_sql}";
            $stats[ $status_name . '_appointments' ] = (int) $wpdb->get_var( $wpdb->prepare( $query, array_merge( $where_values, array( $status_id ) ) ) );
        }

        // Appointments today
        $today_where_sql = $where_sql . ' AND appointment_start_date = CURDATE()';
        $query = "SELECT COUNT(*) FROM {$table} WHERE {$today_where_sql}";
        $stats['appointments_today'] = (int) $wpdb->get_var( $wpdb->prepare( $query, $where_values ) );

        // Appointments this week
        $week_where_sql = $where_sql . ' AND WEEK(appointment_start_date) = WEEK(CURDATE()) AND YEAR(appointment_start_date) = YEAR(CURDATE())';
        $query = "SELECT COUNT(*) FROM {$table} WHERE {$week_where_sql}";
        $stats['appointments_this_week'] = (int) $wpdb->get_var( $wpdb->prepare( $query, $where_values ) );

        // Appointments this month
        $month_where_sql = $where_sql . ' AND MONTH(appointment_start_date) = MONTH(CURDATE()) AND YEAR(appointment_start_date) = YEAR(CURDATE())';
        $query = "SELECT COUNT(*) FROM {$table} WHERE {$month_where_sql}";
        $stats['appointments_this_month'] = (int) $wpdb->get_var( $wpdb->prepare( $query, $where_values ) );

        return $stats;
    }

    /**
     * Get appointments by date range
     *
     * @param string $start_date Start date (Y-m-d format)
     * @param string $end_date End date (Y-m-d format)
     * @param array $args Additional query arguments
     * @return array Array of appointment data
     * @since 1.0.0
     */
    public static function get_by_date_range( $start_date, $end_date, $args = array() ) {
        global $wpdb;
        $table = $wpdb->prefix . self::$table_name;

        $defaults = array(
            'clinic_id' => null,
            'doctor_id' => null,
            'patient_id' => null,
            'status' => null,
            'limit' => 100,
            'offset' => 0,
            'orderby' => 'appointment_start_date',
            'order' => 'ASC'
        );

        $args = wp_parse_args( $args, $defaults );

        $where_clauses = array(
            'appointment_start_date >= %s',
            'appointment_start_date <= %s'
        );
        $where_values = array( $start_date, $end_date );

        // Add filters
        if ( ! is_null( $args['clinic_id'] ) ) {
            $where_clauses[] = 'clinic_id = %d';
            $where_values[] = $args['clinic_id'];
        }

        if ( ! is_null( $args['doctor_id'] ) ) {
            $where_clauses[] = 'doctor_id = %d';
            $where_values[] = $args['doctor_id'];
        }

        if ( ! is_null( $args['patient_id'] ) ) {
            $where_clauses[] = 'patient_id = %d';
            $where_values[] = $args['patient_id'];
        }

        if ( ! is_null( $args['status'] ) ) {
            $where_clauses[] = 'status = %d';
            $where_values[] = $args['status'];
        }

        $where_sql = implode( ' AND ', $where_clauses );

        // Whitelist for orderby
        $allowed_orderby = array( 'appointment_start_date', 'clinic_name', 'doctor_name', 'patient_name', 'status' );
        $orderby = in_array( $args['orderby'], $allowed_orderby, true ) ? $args['orderby'] : 'appointment_start_date';

        // Whitelist for order
        $order = in_array( strtoupper( $args['order'] ), array( 'ASC', 'DESC' ), true ) ? strtoupper( $args['order'] ) : 'ASC';

        $query = $wpdb->prepare(
            "SELECT a.*, 
                    c.name as clinic_name,
                    CONCAT(du.first_name, ' ', du.last_name) as doctor_name,
                    CONCAT(pu.first_name, ' ', pu.last_name) as patient_name
             FROM {$table} a
             LEFT JOIN {$wpdb->prefix}kc_clinics c ON a.clinic_id = c.id
             LEFT JOIN {$wpdb->prefix}users du ON a.doctor_id = du.ID
             LEFT JOIN {$wpdb->prefix}users pu ON a.patient_id = pu.ID
             WHERE {$where_sql}
             ORDER BY {$orderby} {$order}
             LIMIT %d OFFSET %d",
            array_merge( $where_values, array( $args['limit'], $args['offset'] ) )
        );

        $appointments = $wpdb->get_results( $query, ARRAY_A );

        return array_map( array( self::class, 'format_appointment_data' ), $appointments );
    }
}