<?php
/**
 * Patient Model
 *
 * Handles patient entity operations and medical data management
 *
 * @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 Patient
 *
 * Model for handling patient data, medical history and clinic associations
 *
 * @since 1.0.0
 */
class Patient {

    /**
     * Patient user ID (wp_users table)
     *
     * @var int
     */
    public $user_id;

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

    /**
     * Required fields for patient registration
     *
     * @var array
     */
    private static $required_fields = array(
        'first_name',
        'last_name',
        'user_email',
        'birth_date',
        'gender'
    );

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

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

        $patient_data = self::get_patient_full_data( $this->user_id );
        
        if ( $patient_data ) {
            $this->data = $patient_data;
            return true;
        }

        return false;
    }

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

        // Create WordPress user
        $user_data = array(
            'user_login' => self::generate_unique_username( $patient_data ),
            'user_email' => sanitize_email( $patient_data['user_email'] ),
            'first_name' => sanitize_text_field( $patient_data['first_name'] ),
            'last_name' => sanitize_text_field( $patient_data['last_name'] ),
            'role' => 'kivicare_patient',
            'user_pass' => isset( $patient_data['user_pass'] ) ? $patient_data['user_pass'] : wp_generate_password()
        );

        $user_id = wp_insert_user( $user_data );

        if ( is_wp_error( $user_id ) ) {
            return new \WP_Error(
                'patient_creation_failed',
                'Failed to create patient user: ' . $user_id->get_error_message(),
                array( 'status' => 500 )
            );
        }

        // Add patient meta data
        $meta_fields = array(
            'birth_date' => sanitize_text_field( $patient_data['birth_date'] ),
            'gender' => sanitize_text_field( $patient_data['gender'] ),
            'mobile_number' => isset( $patient_data['mobile_number'] ) ? sanitize_text_field( $patient_data['mobile_number'] ) : '',
            'address' => isset( $patient_data['address'] ) ? sanitize_textarea_field( $patient_data['address'] ) : '',
            'city' => isset( $patient_data['city'] ) ? sanitize_text_field( $patient_data['city'] ) : '',
            'state' => isset( $patient_data['state'] ) ? sanitize_text_field( $patient_data['state'] ) : '',
            'country' => isset( $patient_data['country'] ) ? sanitize_text_field( $patient_data['country'] ) : '',
            'postal_code' => isset( $patient_data['postal_code'] ) ? sanitize_text_field( $patient_data['postal_code'] ) : '',
            'blood_group' => isset( $patient_data['blood_group'] ) ? sanitize_text_field( $patient_data['blood_group'] ) : '',
            'emergency_contact' => isset( $patient_data['emergency_contact'] ) ? sanitize_text_field( $patient_data['emergency_contact'] ) : '',
            'medical_notes' => isset( $patient_data['medical_notes'] ) ? sanitize_textarea_field( $patient_data['medical_notes'] ) : '',
            'patient_registration_date' => current_time( 'mysql' )
        );

        foreach ( $meta_fields as $meta_key => $meta_value ) {
            update_user_meta( $user_id, $meta_key, $meta_value );
        }

        // Create clinic mapping if clinic_id provided
        if ( ! empty( $patient_data['clinic_id'] ) ) {
            self::assign_to_clinic( $user_id, $patient_data['clinic_id'] );
        }

        return $user_id;
    }

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

        // Update user data
        $user_update_data = array( 'ID' => $user_id );
        $user_fields = array( 'first_name', 'last_name', 'user_email' );

        foreach ( $user_fields as $field ) {
            if ( isset( $patient_data[ $field ] ) ) {
                $user_update_data[ $field ] = sanitize_text_field( $patient_data[ $field ] );
            }
        }

        if ( count( $user_update_data ) > 1 ) {
            $result = wp_update_user( $user_update_data );
            if ( is_wp_error( $result ) ) {
                return new \WP_Error(
                    'patient_update_failed',
                    'Failed to update patient: ' . $result->get_error_message(),
                    array( 'status' => 500 )
                );
            }
        }

        // Update meta fields
        $meta_fields = array(
            'birth_date', 'gender', 'mobile_number', 'address', 'city', 
            'state', 'country', 'postal_code', 'blood_group', 
            'emergency_contact', 'medical_notes'
        );

        foreach ( $meta_fields as $meta_key ) {
            if ( isset( $patient_data[ $meta_key ] ) ) {
                $value = $patient_data[ $meta_key ];
                
                if ( in_array( $meta_key, array( 'address', 'medical_notes' ) ) ) {
                    $value = sanitize_textarea_field( $value );
                } else {
                    $value = sanitize_text_field( $value );
                }
                
                update_user_meta( $user_id, $meta_key, $value );
            }
        }

        return true;
    }

    /**
     * Delete a patient (soft delete - deactivate user)
     *
     * @param int $user_id Patient user ID
     * @return bool|WP_Error True on success, WP_Error on failure
     * @since 1.0.0
     */
    public static function delete( $user_id ) {
        if ( ! self::exists( $user_id ) ) {
            return new \WP_Error(
                'patient_not_found',
                'Patient not found',
                array( 'status' => 404 )
            );
        }

        // Check for dependencies
        if ( self::has_dependencies( $user_id ) ) {
            return new \WP_Error(
                'patient_has_dependencies',
                'Cannot delete patient with existing appointments or medical records',
                array( 'status' => 409 )
            );
        }

        // Soft delete - set user status to inactive
        update_user_meta( $user_id, 'patient_status', 'inactive' );

        return true;
    }

    /**
     * Get patient by ID
     *
     * @param int $user_id Patient user ID
     * @return array|null Patient data or null if not found
     * @since 1.0.0
     */
    public static function get_by_id( $user_id ) {
        if ( ! self::exists( $user_id ) ) {
            return null;
        }

        return self::get_patient_full_data( $user_id );
    }

    /**
     * Get all patients with optional filtering
     *
     * @param array $args Query arguments
     * @return array Array of patient data
     * @since 1.0.0
     */
    public static function get_all( $args = array() ) {
        $defaults = array(
            'clinic_id' => null,
            'status' => 'active',
            'search' => '',
            'limit' => 50,
            'offset' => 0,
            'orderby' => 'display_name',
            'order' => 'ASC'
        );

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

        $user_query_args = array(
            'role' => 'kivicare_patient',
            'number' => $args['limit'],
            'offset' => $args['offset'],
            'orderby' => $args['orderby'],
            'order' => $args['order'],
            'fields' => 'ID'
        );

        // Add search
        if ( ! empty( $args['search'] ) ) {
            $user_query_args['search'] = '*' . $args['search'] . '*';
            $user_query_args['search_columns'] = array( 'user_login', 'user_email', 'display_name' );
        }

        // Add status filter via meta query
        if ( ! empty( $args['status'] ) ) {
            $user_query_args['meta_query'] = array(
                'relation' => 'OR',
                array(
                    'key' => 'patient_status',
                    'value' => $args['status'],
                    'compare' => '='
                ),
                array(
                    'key' => 'patient_status',
                    'compare' => 'NOT EXISTS'
                )
            );

            if ( $args['status'] === 'active' ) {
                $user_query_args['meta_query'][1]['value'] = 'active';
            }
        }

        $user_query = new \WP_User_Query( $user_query_args );
        $user_ids = $user_query->get_results();

        $patients = array();
        foreach ( $user_ids as $user_id ) {
            $patient_data = self::get_patient_full_data( $user_id );
            
            // Filter by clinic if specified
            if ( ! is_null( $args['clinic_id'] ) && 
                 (int) $patient_data['clinic_id'] !== (int) $args['clinic_id'] ) {
                continue;
            }
            
            if ( $patient_data ) {
                $patients[] = $patient_data;
            }
        }

        return $patients;
    }

    /**
     * Get patient full data with clinic information
     *
     * @param int $user_id Patient user ID
     * @return array|null Full patient data or null if not found
     * @since 1.0.0
     */
    public static function get_patient_full_data( $user_id ) {
        global $wpdb;

        $user = get_user_by( 'id', $user_id );
        if ( ! $user || ! in_array( 'kivicare_patient', $user->roles ) ) {
            return null;
        }

        // Get clinic mapping
        $clinic_mapping = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT pcm.clinic_id, c.name as clinic_name 
                 FROM {$wpdb->prefix}kc_patient_clinic_mappings pcm
                 LEFT JOIN {$wpdb->prefix}kc_clinics c ON pcm.clinic_id = c.id
                 WHERE pcm.patient_id = %d
                 LIMIT 1",
                $user_id
            ),
            ARRAY_A
        );

        // Get patient meta data
        $patient_data = array(
            'user_id' => $user_id,
            'username' => $user->user_login,
            'email' => $user->user_email,
            'first_name' => $user->first_name,
            'last_name' => $user->last_name,
            'display_name' => $user->display_name,
            'birth_date' => get_user_meta( $user_id, 'birth_date', true ),
            'gender' => get_user_meta( $user_id, 'gender', true ),
            'mobile_number' => get_user_meta( $user_id, 'mobile_number', true ),
            'address' => get_user_meta( $user_id, 'address', true ),
            'city' => get_user_meta( $user_id, 'city', true ),
            'state' => get_user_meta( $user_id, 'state', true ),
            'country' => get_user_meta( $user_id, 'country', true ),
            'postal_code' => get_user_meta( $user_id, 'postal_code', true ),
            'blood_group' => get_user_meta( $user_id, 'blood_group', true ),
            'emergency_contact' => get_user_meta( $user_id, 'emergency_contact', true ),
            'medical_notes' => get_user_meta( $user_id, 'medical_notes', true ),
            'status' => get_user_meta( $user_id, 'patient_status', true ) ?: 'active',
            'registration_date' => get_user_meta( $user_id, 'patient_registration_date', true ),
            'clinic_id' => $clinic_mapping ? (int) $clinic_mapping['clinic_id'] : null,
            'clinic_name' => $clinic_mapping ? $clinic_mapping['clinic_name'] : null,
            'age' => self::calculate_age( get_user_meta( $user_id, 'birth_date', true ) ),
            'total_appointments' => self::get_appointment_count( $user_id ),
            'last_visit' => self::get_last_visit_date( $user_id )
        );

        return $patient_data;
    }

    /**
     * Get patient medical history
     *
     * @param int $user_id Patient user ID
     * @param array $args Query arguments
     * @return array Patient medical history
     * @since 1.0.0
     */
    public static function get_medical_history( $user_id, $args = array() ) {
        global $wpdb;

        $defaults = array(
            'type' => null,
            'limit' => 50,
            'offset' => 0,
            'orderby' => 'created_at',
            'order' => 'DESC'
        );

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

        $where_clauses = array( 'patient_id = %d' );
        $where_values = array( $user_id );

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

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

        // Whitelist for orderby
        $allowed_orderby = array( 'created_at', 'type', 'title' );
        $orderby = in_array( $args['orderby'], $allowed_orderby, true ) ? $args['orderby'] : 'created_at';

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

        $query = $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}kc_medical_history 
             WHERE {$where_sql} 
             ORDER BY {$orderby} {$order} 
             LIMIT %d OFFSET %d",
            array_merge( $where_values, array( $args['limit'], $args['offset'] ) )
        );

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

        return array_map( function( $item ) {
            return array(
                'id' => (int) $item['id'],
                'encounter_id' => (int) $item['encounter_id'],
                'type' => $item['type'],
                'title' => $item['title'],
                'date' => $item['created_at'],
                'added_by' => (int) $item['added_by']
            );
        }, $history );
    }

    /**
     * Get patient encounters
     *
     * @param int $user_id Patient user ID
     * @param array $args Query arguments
     * @return array Patient encounters
     * @since 1.0.0
     */
    public static function get_encounters( $user_id, $args = array() ) {
        global $wpdb;

        $defaults = array(
            'limit' => 50,
            'offset' => 0,
            'orderby' => 'encounter_date',
            'order' => 'DESC',
            'status' => null
        );

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

        $where_clauses = array( 'e.patient_id = %d' );
        $where_values = array( $user_id );

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

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

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

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

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

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

        return array_map( function( $encounter ) {
            return array(
                'id' => (int) $encounter['id'],
                'encounter_date' => $encounter['encounter_date'],
                'description' => $encounter['description'],
                'status' => (int) $encounter['status'],
                'clinic' => array(
                    'id' => (int) $encounter['clinic_id'],
                    'name' => $encounter['clinic_name']
                ),
                'doctor' => array(
                    'id' => (int) $encounter['doctor_id'],
                    'name' => $encounter['doctor_name']
                ),
                'appointment_id' => (int) $encounter['appointment_id'],
                'template_id' => (int) $encounter['template_id'],
                'created_at' => $encounter['created_at']
            );
        }, $encounters );
    }

    /**
     * Assign patient to clinic
     *
     * @param int $user_id Patient user ID
     * @param int $clinic_id Clinic ID
     * @return bool|WP_Error True on success, WP_Error on failure
     * @since 1.0.0
     */
    public static function assign_to_clinic( $user_id, $clinic_id ) {
        global $wpdb;

        // Check if clinic exists
        if ( ! Clinic::exists( $clinic_id ) ) {
            return new \WP_Error(
                'clinic_not_found',
                'Clinic not found',
                array( 'status' => 404 )
            );
        }

        // Check if mapping already exists
        $existing_mapping = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->prefix}kc_patient_clinic_mappings 
                 WHERE patient_id = %d AND clinic_id = %d",
                $user_id, $clinic_id
            )
        );

        if ( (int) $existing_mapping > 0 ) {
            return true; // Already assigned
        }

        // Remove existing mappings (patient can only be assigned to one clinic at a time)
        $wpdb->delete(
            "{$wpdb->prefix}kc_patient_clinic_mappings",
            array( 'patient_id' => $user_id ),
            array( '%d' )
        );

        // Create new mapping
        $result = $wpdb->insert(
            "{$wpdb->prefix}kc_patient_clinic_mappings",
            array(
                'patient_id' => $user_id,
                'clinic_id' => $clinic_id,
                'created_at' => current_time( 'mysql' )
            ),
            array( '%d', '%d', '%s' )
        );

        if ( $result === false ) {
            return new \WP_Error(
                'clinic_assignment_failed',
                'Failed to assign patient to clinic: ' . $wpdb->last_error,
                array( 'status' => 500 )
            );
        }

        return true;
    }

    /**
     * Check if patient exists
     *
     * @param int $user_id Patient user ID
     * @return bool True if exists, false otherwise
     * @since 1.0.0
     */
    public static function exists( $user_id ) {
        $user = get_user_by( 'id', $user_id );
        return $user && in_array( 'kivicare_patient', $user->roles );
    }

    /**
     * Check if patient has dependencies (appointments, encounters, etc.)
     *
     * @param int $user_id Patient user ID
     * @return bool True if has dependencies, false otherwise
     * @since 1.0.0
     */
    private static function has_dependencies( $user_id ) {
        global $wpdb;

        // Check appointments
        $appointments_count = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->prefix}kc_appointments WHERE patient_id = %d",
                $user_id
            )
        );

        if ( (int) $appointments_count > 0 ) {
            return true;
        }

        // Check encounters
        $encounters_count = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->prefix}kc_patient_encounters WHERE patient_id = %d",
                $user_id
            )
        );

        return (int) $encounters_count > 0;
    }

    /**
     * Validate patient data
     *
     * @param array $patient_data Patient data to validate
     * @return bool|WP_Error True if valid, WP_Error if invalid
     * @since 1.0.0
     */
    private static function validate_patient_data( $patient_data ) {
        $errors = array();

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

        // Validate email format
        if ( ! empty( $patient_data['user_email'] ) && ! is_email( $patient_data['user_email'] ) ) {
            $errors[] = 'Invalid email format';
        }

        // Check for duplicate email
        if ( ! empty( $patient_data['user_email'] ) ) {
            $existing_user = get_user_by( 'email', $patient_data['user_email'] );
            if ( $existing_user ) {
                $errors[] = 'A user with this email already exists';
            }
        }

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

        // Validate gender
        if ( ! empty( $patient_data['gender'] ) && ! in_array( $patient_data['gender'], array( 'M', 'F', 'O' ) ) ) {
            $errors[] = 'Invalid gender. Use M, F, or O';
        }

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

        return true;
    }

    /**
     * Generate unique username for patient
     *
     * @param array $patient_data Patient data
     * @return string Unique username
     * @since 1.0.0
     */
    private static function generate_unique_username( $patient_data ) {
        $base_username = strtolower( 
            $patient_data['first_name'] . '.' . $patient_data['last_name'] 
        );
        $base_username = sanitize_user( $base_username );
        
        $username = $base_username;
        $counter = 1;
        
        while ( username_exists( $username ) ) {
            $username = $base_username . $counter;
            $counter++;
        }
        
        return $username;
    }

    /**
     * Calculate patient age from birth date
     *
     * @param string $birth_date Birth date in Y-m-d format
     * @return int|null Age in years or null if invalid
     * @since 1.0.0
     */
    private static function calculate_age( $birth_date ) {
        if ( empty( $birth_date ) ) {
            return null;
        }

        $birth = \DateTime::createFromFormat( 'Y-m-d', $birth_date );
        if ( ! $birth ) {
            return null;
        }

        $now = new \DateTime();
        $age = $now->diff( $birth );

        return $age->y;
    }

    /**
     * Get patient appointment count
     *
     * @param int $user_id Patient user ID
     * @return int Total appointments
     * @since 1.0.0
     */
    private static function get_appointment_count( $user_id ) {
        global $wpdb;

        return (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->prefix}kc_appointments WHERE patient_id = %d",
                $user_id
            )
        );
    }

    /**
     * Get patient's last visit date
     *
     * @param int $user_id Patient user ID
     * @return string|null Last visit date or null
     * @since 1.0.0
     */
    private static function get_last_visit_date( $user_id ) {
        global $wpdb;

        return $wpdb->get_var(
            $wpdb->prepare(
                "SELECT MAX(encounter_date) FROM {$wpdb->prefix}kc_patient_encounters WHERE patient_id = %d",
                $user_id
            )
        );
    }

    /**
     * Get patient statistics
     *
     * @param int $user_id Patient user ID
     * @return array Patient statistics
     * @since 1.0.0
     */
    public static function get_statistics( $user_id ) {
        global $wpdb;

        $stats = array(
            'total_appointments' => self::get_appointment_count( $user_id ),
            'completed_encounters' => 0,
            'pending_appointments' => 0,
            'total_prescriptions' => 0,
            'last_visit' => self::get_last_visit_date( $user_id ),
            'next_appointment' => null
        );

        // Completed encounters
        $stats['completed_encounters'] = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->prefix}kc_patient_encounters WHERE patient_id = %d AND status = 1",
                $user_id
            )
        );

        // Pending appointments
        $stats['pending_appointments'] = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->prefix}kc_appointments 
                 WHERE patient_id = %d AND status = 1 AND appointment_start_date >= CURDATE()",
                $user_id
            )
        );

        // Total prescriptions
        $stats['total_prescriptions'] = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->prefix}kc_prescription WHERE patient_id = %d",
                $user_id
            )
        );

        // Next appointment
        $stats['next_appointment'] = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT MIN(appointment_start_date) FROM {$wpdb->prefix}kc_appointments 
                 WHERE patient_id = %d AND status = 1 AND appointment_start_date >= CURDATE()",
                $user_id
            )
        );

        return $stats;
    }
}