<?php
/**
 * Permission Service
 *
 * Handles role-based access control and permission management
 *
 * @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;

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

/**
 * Class Permission_Service
 *
 * Role-based permission system for Care API
 *
 * @since 1.0.0
 */
class Permission_Service {

    /**
     * Permission matrix defining what each role can do
     *
     * @var array
     */
    private static $permission_matrix = array();

    /**
     * Resource-specific permissions
     *
     * @var array
     */
    private static $resource_permissions = array();

    /**
     * Initialize the permission service
     *
     * @since 1.0.0
     */
    public static function init() {
        self::define_permission_matrix();
        self::define_resource_permissions();
        
        // Hook into WordPress capability system
        add_filter( 'user_has_cap', array( self::class, 'user_has_cap' ), 10, 4 );
        
        // Add custom capabilities on plugin activation
        add_action( 'init', array( self::class, 'add_kivicare_capabilities' ) );
    }

    /**
     * Check if user has permission for specific action
     *
     * @param int|WP_User $user        User ID or user object
     * @param string      $permission  Permission to check
     * @param array       $context     Additional context (resource_id, clinic_id, etc.)
     * @return bool True if user has permission
     * @since 1.0.0
     */
    public static function has_permission( $user, $permission, $context = array() ) {
        if ( is_numeric( $user ) ) {
            $user = get_user_by( 'id', $user );
        }

        if ( ! $user instanceof \WP_User ) {
            return false;
        }

        // Super admin has all permissions
        if ( is_super_admin( $user->ID ) ) {
            return true;
        }

        // Get user's primary Care role
        $primary_role = self::get_primary_kivicare_role( $user );
        
        if ( ! $primary_role ) {
            return false;
        }

        // Check basic permission matrix
        if ( ! self::role_has_permission( $primary_role, $permission ) ) {
            return false;
        }

        // Apply contextual restrictions
        return self::check_contextual_permissions( $user, $permission, $context );
    }

    /**
     * Check if current user has permission
     *
     * @param string $permission Permission to check
     * @param array  $context    Additional context
     * @return bool True if current user has permission
     * @since 1.0.0
     */
    public static function current_user_can( $permission, $context = array() ) {
        $user = wp_get_current_user();
        return $user && self::has_permission( $user, $permission, $context );
    }

    /**
     * Get all permissions for a user
     *
     * @param int|WP_User $user User ID or user object
     * @return array Array of permissions
     * @since 1.0.0
     */
    public static function get_user_permissions( $user ) {
        if ( is_numeric( $user ) ) {
            $user = get_user_by( 'id', $user );
        }

        if ( ! $user instanceof \WP_User ) {
            return array();
        }

        $primary_role = self::get_primary_kivicare_role( $user );
        
        if ( ! $primary_role || ! isset( self::$permission_matrix[ $primary_role ] ) ) {
            return array();
        }

        return self::$permission_matrix[ $primary_role ];
    }

    /**
     * Check clinic access permission
     *
     * @param int|WP_User $user      User ID or user object
     * @param int         $clinic_id Clinic ID
     * @return bool True if user has access to clinic
     * @since 1.0.0
     */
    public static function can_access_clinic( $user, $clinic_id ) {
        if ( is_numeric( $user ) ) {
            $user = get_user_by( 'id', $user );
        }

        if ( ! $user instanceof \WP_User ) {
            return false;
        }

        // Administrator has access to all clinics
        if ( in_array( 'administrator', $user->roles ) ) {
            return true;
        }

        $primary_role = self::get_primary_kivicare_role( $user );

        switch ( $primary_role ) {
            case 'kivicare_doctor':
                return self::doctor_has_clinic_access( $user->ID, $clinic_id );
                
            case 'kivicare_patient':
                return self::patient_has_clinic_access( $user->ID, $clinic_id );
                
            case 'kivicare_receptionist':
                return self::receptionist_has_clinic_access( $user->ID, $clinic_id );
                
            default:
                return false;
        }
    }

    /**
     * Check patient access permission
     *
     * @param int|WP_User $user       User ID or user object
     * @param int         $patient_id Patient ID
     * @return bool True if user can access patient data
     * @since 1.0.0
     */
    public static function can_access_patient( $user, $patient_id ) {
        if ( is_numeric( $user ) ) {
            $user = get_user_by( 'id', $user );
        }

        if ( ! $user instanceof \WP_User ) {
            return false;
        }

        // Administrator has access to all patients
        if ( in_array( 'administrator', $user->roles ) ) {
            return true;
        }

        $primary_role = self::get_primary_kivicare_role( $user );

        switch ( $primary_role ) {
            case 'kivicare_doctor':
                return self::doctor_can_access_patient( $user->ID, $patient_id );
                
            case 'kivicare_patient':
                // Patients can only access their own data
                return $user->ID === $patient_id;
                
            case 'kivicare_receptionist':
                return self::receptionist_can_access_patient( $user->ID, $patient_id );
                
            default:
                return false;
        }
    }

    /**
     * Check appointment access permission
     *
     * @param int|WP_User $user           User ID or user object
     * @param int         $appointment_id Appointment ID
     * @return bool True if user can access appointment
     * @since 1.0.0
     */
    public static function can_access_appointment( $user, $appointment_id ) {
        if ( is_numeric( $user ) ) {
            $user = get_user_by( 'id', $user );
        }

        if ( ! $user instanceof \WP_User ) {
            return false;
        }

        // Get appointment data
        global $wpdb;
        $appointment = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT doctor_id, patient_id, clinic_id FROM {$wpdb->prefix}kc_appointments WHERE id = %d",
                $appointment_id
            ),
            ARRAY_A
        );

        if ( ! $appointment ) {
            return false;
        }

        // Administrator has access to all appointments
        if ( in_array( 'administrator', $user->roles ) ) {
            return true;
        }

        $primary_role = self::get_primary_kivicare_role( $user );

        switch ( $primary_role ) {
            case 'kivicare_doctor':
                // Doctors can access their own appointments or appointments in their clinics
                return $user->ID === (int) $appointment['doctor_id'] || 
                       self::doctor_has_clinic_access( $user->ID, $appointment['clinic_id'] );
                
            case 'kivicare_patient':
                // Patients can only access their own appointments
                return $user->ID === (int) $appointment['patient_id'];
                
            case 'kivicare_receptionist':
                // Receptionists can access appointments in their clinics
                return self::receptionist_has_clinic_access( $user->ID, $appointment['clinic_id'] );
                
            default:
                return false;
        }
    }

    /**
     * Get filtered clinic IDs for user
     *
     * @param int|WP_User $user User ID or user object
     * @return array Array of clinic IDs user can access
     * @since 1.0.0
     */
    public static function get_accessible_clinic_ids( $user ) {
        if ( is_numeric( $user ) ) {
            $user = get_user_by( 'id', $user );
        }

        if ( ! $user instanceof \WP_User ) {
            return array();
        }

        // Administrator has access to all clinics
        if ( in_array( 'administrator', $user->roles ) ) {
            global $wpdb;
            $clinic_ids = $wpdb->get_col( $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}kc_clinics WHERE status = %d", 1 ) );
            return array_map( 'intval', $clinic_ids );
        }

        $primary_role = self::get_primary_kivicare_role( $user );

        switch ( $primary_role ) {
            case 'kivicare_doctor':
                return self::get_doctor_clinic_ids( $user->ID );
                
            case 'kivicare_patient':
                return self::get_patient_clinic_ids( $user->ID );
                
            case 'kivicare_receptionist':
                return self::get_receptionist_clinic_ids( $user->ID );
                
            default:
                return array();
        }
    }

    /**
     * Define permission matrix for each role
     *
     * @since 1.0.0
     */
    private static function define_permission_matrix() {
        self::$permission_matrix = array(
            'administrator' => array(
                // Full system access
                'manage_clinics',
                'manage_users',
                'manage_doctors',
                'manage_patients',
                'manage_appointments',
                'manage_encounters',
                'manage_prescriptions',
                'manage_bills',
                'manage_services',
                'view_all_data',
                'manage_settings',
                'view_reports',
                'export_data'
            ),
            
            'kivicare_doctor' => array(
                // Patient management within assigned clinics
                'view_patients',
                'create_patients',
                'edit_assigned_patients',
                
                // Appointment management
                'view_own_appointments',
                'edit_own_appointments',
                'create_appointments',
                
                // Medical records
                'view_patient_encounters',
                'create_encounters',
                'edit_own_encounters',
                'view_prescriptions',
                'create_prescriptions',
                'edit_own_prescriptions',
                
                // Billing (limited)
                'view_bills',
                'create_bills',
                
                // Services
                'view_services',
                
                // Profile management
                'edit_own_profile',
                'view_own_schedule'
            ),
            
            'kivicare_patient' => array(
                // Own data access only
                'view_own_profile',
                'edit_own_profile',
                'view_own_appointments',
                'create_own_appointments', // If enabled
                'view_own_encounters',
                'view_own_prescriptions',
                'view_own_bills',
                'view_services'
            ),
            
            'kivicare_receptionist' => array(
                // Patient management for assigned clinics
                'view_patients',
                'create_patients',
                'edit_patients',
                
                // Appointment management
                'view_appointments',
                'create_appointments',
                'edit_appointments',
                'cancel_appointments',
                
                // Basic billing
                'view_bills',
                'create_bills',
                'process_payments',
                
                // Services
                'view_services',
                
                // Limited reporting
                'view_basic_reports'
            )
        );

        // Apply filters to allow customization
        self::$permission_matrix = apply_filters( 'kivicare_permission_matrix', self::$permission_matrix );
    }

    /**
     * Define resource-specific permissions
     *
     * @since 1.0.0
     */
    private static function define_resource_permissions() {
        self::$resource_permissions = array(
            'clinics' => array(
                'view' => array( 'administrator', 'kivicare_doctor', 'kivicare_receptionist' ),
                'create' => array( 'administrator' ),
                'edit' => array( 'administrator' ),
                'delete' => array( 'administrator' )
            ),
            
            'patients' => array(
                'view' => array( 'administrator', 'kivicare_doctor', 'kivicare_receptionist' ),
                'create' => array( 'administrator', 'kivicare_doctor', 'kivicare_receptionist' ),
                'edit' => array( 'administrator', 'kivicare_doctor', 'kivicare_receptionist' ),
                'delete' => array( 'administrator' )
            ),
            
            'appointments' => array(
                'view' => array( 'administrator', 'kivicare_doctor', 'kivicare_patient', 'kivicare_receptionist' ),
                'create' => array( 'administrator', 'kivicare_doctor', 'kivicare_receptionist' ),
                'edit' => array( 'administrator', 'kivicare_doctor', 'kivicare_receptionist' ),
                'delete' => array( 'administrator', 'kivicare_doctor', 'kivicare_receptionist' )
            ),
            
            'encounters' => array(
                'view' => array( 'administrator', 'kivicare_doctor' ),
                'create' => array( 'administrator', 'kivicare_doctor' ),
                'edit' => array( 'administrator', 'kivicare_doctor' ),
                'delete' => array( 'administrator' )
            ),
            
            'prescriptions' => array(
                'view' => array( 'administrator', 'kivicare_doctor', 'kivicare_patient' ),
                'create' => array( 'administrator', 'kivicare_doctor' ),
                'edit' => array( 'administrator', 'kivicare_doctor' ),
                'delete' => array( 'administrator', 'kivicare_doctor' )
            ),
            
            'bills' => array(
                'view' => array( 'administrator', 'kivicare_doctor', 'kivicare_patient', 'kivicare_receptionist' ),
                'create' => array( 'administrator', 'kivicare_doctor', 'kivicare_receptionist' ),
                'edit' => array( 'administrator', 'kivicare_receptionist' ),
                'delete' => array( 'administrator' )
            )
        );

        self::$resource_permissions = apply_filters( 'kivicare_resource_permissions', self::$resource_permissions );
    }

    /**
     * Check if role has basic permission
     *
     * @param string $role       User role
     * @param string $permission Permission to check
     * @return bool True if role has permission
     * @since 1.0.0
     */
    private static function role_has_permission( $role, $permission ) {
        if ( ! isset( self::$permission_matrix[ $role ] ) ) {
            return false;
        }

        return in_array( $permission, self::$permission_matrix[ $role ] );
    }

    /**
     * Check contextual permissions based on resource ownership and clinic access
     *
     * @param WP_User $user       User object
     * @param string  $permission Permission to check
     * @param array   $context    Context data
     * @return bool True if user has contextual permission
     * @since 1.0.0
     */
    private static function check_contextual_permissions( $user, $permission, $context ) {
        // Extract context information
        $resource_id = $context['resource_id'] ?? null;
        $clinic_id = $context['clinic_id'] ?? null;
        $patient_id = $context['patient_id'] ?? null;
        $doctor_id = $context['doctor_id'] ?? null;
        $resource_type = $context['resource_type'] ?? '';

        $primary_role = self::get_primary_kivicare_role( $user );

        // Apply role-specific contextual rules
        switch ( $primary_role ) {
            case 'kivicare_doctor':
                return self::check_doctor_context( $user->ID, $permission, $context );
                
            case 'kivicare_patient':
                return self::check_patient_context( $user->ID, $permission, $context );
                
            case 'kivicare_receptionist':
                return self::check_receptionist_context( $user->ID, $permission, $context );
                
            default:
                return true; // Administrator or unknown role
        }
    }

    /**
     * Check doctor-specific contextual permissions
     *
     * @param int    $doctor_id  Doctor ID
     * @param string $permission Permission to check
     * @param array  $context    Context data
     * @return bool True if doctor has contextual permission
     * @since 1.0.0
     */
    private static function check_doctor_context( $doctor_id, $permission, $context ) {
        $clinic_id = $context['clinic_id'] ?? null;
        $patient_id = $context['patient_id'] ?? null;
        $resource_type = $context['resource_type'] ?? '';

        // Check clinic access
        if ( $clinic_id && ! self::doctor_has_clinic_access( $doctor_id, $clinic_id ) ) {
            return false;
        }

        // Check patient access
        if ( $patient_id && ! self::doctor_can_access_patient( $doctor_id, $patient_id ) ) {
            return false;
        }

        // Resource-specific rules
        if ( $resource_type === 'appointment' ) {
            $appointment_id = $context['resource_id'] ?? null;
            if ( $appointment_id ) {
                return self::can_access_appointment( $doctor_id, $appointment_id );
            }
        }

        return true;
    }

    /**
     * Check patient-specific contextual permissions
     *
     * @param int    $patient_id Patient ID
     * @param string $permission Permission to check
     * @param array  $context    Context data
     * @return bool True if patient has contextual permission
     * @since 1.0.0
     */
    private static function check_patient_context( $patient_id, $permission, $context ) {
        // Patients can only access their own data
        $resource_patient_id = $context['patient_id'] ?? null;
        
        if ( $resource_patient_id && $resource_patient_id !== $patient_id ) {
            return false;
        }

        return true;
    }

    /**
     * Check receptionist-specific contextual permissions
     *
     * @param int    $receptionist_id Receptionist ID
     * @param string $permission      Permission to check
     * @param array  $context         Context data
     * @return bool True if receptionist has contextual permission
     * @since 1.0.0
     */
    private static function check_receptionist_context( $receptionist_id, $permission, $context ) {
        $clinic_id = $context['clinic_id'] ?? null;

        // Check clinic access
        if ( $clinic_id && ! self::receptionist_has_clinic_access( $receptionist_id, $clinic_id ) ) {
            return false;
        }

        return true;
    }

    /**
     * Get primary Care role for user
     *
     * @param WP_User $user User object
     * @return string|null Primary Care role
     * @since 1.0.0
     */
    public static function get_primary_kivicare_role( $user ) {
        $kivicare_roles = array( 'administrator', 'kivicare_doctor', 'kivicare_patient', 'kivicare_receptionist' );
        $user_roles = array_intersect( $user->roles, $kivicare_roles );

        if ( empty( $user_roles ) ) {
            return null;
        }

        // Priority order
        $priority_order = array( 'administrator', 'kivicare_doctor', 'kivicare_receptionist', 'kivicare_patient' );
        
        foreach ( $priority_order as $role ) {
            if ( in_array( $role, $user_roles ) ) {
                return $role;
            }
        }

        return reset( $user_roles );
    }

    /**
     * Check if doctor has access to specific clinic
     *
     * @param int $doctor_id Doctor ID
     * @param int $clinic_id Clinic ID
     * @return bool True if doctor has access
     * @since 1.0.0
     */
    private static function doctor_has_clinic_access( $doctor_id, $clinic_id ) {
        global $wpdb;

        $count = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->prefix}kc_doctor_clinic_mappings WHERE doctor_id = %d AND clinic_id = %d",
                $doctor_id, $clinic_id
            )
        );

        return (int) $count > 0;
    }

    /**
     * Check if patient has access to specific clinic
     *
     * @param int $patient_id Patient ID
     * @param int $clinic_id  Clinic ID
     * @return bool True if patient has access
     * @since 1.0.0
     */
    private static function patient_has_clinic_access( $patient_id, $clinic_id ) {
        global $wpdb;

        $count = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->prefix}kc_patient_clinic_mappings WHERE patient_id = %d AND clinic_id = %d",
                $patient_id, $clinic_id
            )
        );

        return (int) $count > 0;
    }

    /**
     * Check if receptionist has access to specific clinic
     *
     * @param int $receptionist_id Receptionist ID
     * @param int $clinic_id       Clinic ID
     * @return bool True if receptionist has access
     * @since 1.0.0
     */
    private static function receptionist_has_clinic_access( $receptionist_id, $clinic_id ) {
        // For now, assuming receptionists are assigned via user meta
        // This could be extended with a dedicated mapping table
        $assigned_clinics = get_user_meta( $receptionist_id, 'kivicare_assigned_clinics', true );
        
        if ( ! is_array( $assigned_clinics ) ) {
            return false;
        }

        return in_array( $clinic_id, $assigned_clinics );
    }

    /**
     * Check if doctor can access specific patient
     *
     * @param int $doctor_id  Doctor ID
     * @param int $patient_id Patient ID
     * @return bool True if doctor can access patient
     * @since 1.0.0
     */
    private static function doctor_can_access_patient( $doctor_id, $patient_id ) {
        global $wpdb;

        // Check if doctor has appointments with this patient
        $count = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->prefix}kc_appointments WHERE doctor_id = %d AND patient_id = %d",
                $doctor_id, $patient_id
            )
        );

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

        // Check if patient is in doctor's clinics
        $doctor_clinics = self::get_doctor_clinic_ids( $doctor_id );
        $patient_clinics = self::get_patient_clinic_ids( $patient_id );

        return ! empty( array_intersect( $doctor_clinics, $patient_clinics ) );
    }

    /**
     * Check if receptionist can access specific patient
     *
     * @param int $receptionist_id Receptionist ID
     * @param int $patient_id      Patient ID
     * @return bool True if receptionist can access patient
     * @since 1.0.0
     */
    private static function receptionist_can_access_patient( $receptionist_id, $patient_id ) {
        $receptionist_clinics = self::get_receptionist_clinic_ids( $receptionist_id );
        $patient_clinics = self::get_patient_clinic_ids( $patient_id );

        return ! empty( array_intersect( $receptionist_clinics, $patient_clinics ) );
    }

    /**
     * Get clinic IDs for doctor
     *
     * @param int $doctor_id Doctor ID
     * @return array Array of clinic IDs
     * @since 1.0.0
     */
    private static function get_doctor_clinic_ids( $doctor_id ) {
        global $wpdb;

        $clinic_ids = $wpdb->get_col(
            $wpdb->prepare(
                "SELECT clinic_id FROM {$wpdb->prefix}kc_doctor_clinic_mappings WHERE doctor_id = %d",
                $doctor_id
            )
        );

        return array_map( 'intval', $clinic_ids );
    }

    /**
     * Get clinic IDs for patient
     *
     * @param int $patient_id Patient ID
     * @return array Array of clinic IDs
     * @since 1.0.0
     */
    private static function get_patient_clinic_ids( $patient_id ) {
        global $wpdb;

        $clinic_ids = $wpdb->get_col(
            $wpdb->prepare(
                "SELECT clinic_id FROM {$wpdb->prefix}kc_patient_clinic_mappings WHERE patient_id = %d",
                $patient_id
            )
        );

        return array_map( 'intval', $clinic_ids );
    }

    /**
     * Get clinic IDs for receptionist
     *
     * @param int $receptionist_id Receptionist ID
     * @return array Array of clinic IDs
     * @since 1.0.0
     */
    private static function get_receptionist_clinic_ids( $receptionist_id ) {
        $assigned_clinics = get_user_meta( $receptionist_id, 'kivicare_assigned_clinics', true );
        
        if ( ! is_array( $assigned_clinics ) ) {
            return array();
        }

        return array_map( 'intval', $assigned_clinics );
    }

    /**
     * WordPress hook: Modify user capabilities
     *
     * @param array $allcaps All capabilities
     * @param array $caps    Requested capabilities
     * @param array $args    Arguments
     * @param WP_User $user  User object
     * @return array Modified capabilities
     * @since 1.0.0
     */
    public static function user_has_cap( $allcaps, $caps, $args, $user ) {
        // Only modify for Care capabilities
        foreach ( $caps as $cap ) {
            if ( strpos( $cap, 'kivicare_' ) === 0 ) {
                $allcaps[ $cap ] = self::has_permission( $user, $cap, $args );
            }
        }

        return $allcaps;
    }

    /**
     * Add Care-specific capabilities to WordPress
     *
     * @since 1.0.0
     */
    public static function add_kivicare_capabilities() {
        // Get all unique capabilities from permission matrix
        $all_capabilities = array();
        
        foreach ( self::$permission_matrix as $role_caps ) {
            $all_capabilities = array_merge( $all_capabilities, $role_caps );
        }
        
        $all_capabilities = array_unique( $all_capabilities );

        // Add capabilities to administrator role
        $admin_role = get_role( 'administrator' );
        if ( $admin_role ) {
            foreach ( $all_capabilities as $cap ) {
                $admin_role->add_cap( $cap );
            }
        }

        // Add role-specific capabilities
        foreach ( self::$permission_matrix as $role_name => $capabilities ) {
            $role = get_role( $role_name );
            if ( $role ) {
                foreach ( $capabilities as $cap ) {
                    $role->add_cap( $cap );
                }
            }
        }
    }
}