<?php
/**
 * Clinic Isolation Security Service
 *
 * Ensures strict data isolation between clinics for security and compliance
 *
 * @package Care_API
 * @subpackage Services
 * @version    1.0.0
 * @author     Descomplicar® <dev@descomplicar.pt>
 * @link       https://descomplicar.pt
 * @since      1.0.0
 */

namespace Care_API\Services;

use Care_API\Utils\API_Logger;
use Care_API\Utils\Error_Handler;
use WP_Error;

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

/**
 * Class Clinic_Isolation_Service
 *
 * Provides strict data isolation and security between clinics
 *
 * @since 1.0.0
 */
class Clinic_Isolation_Service {

    /**
     * Cache for clinic access checks
     *
     * @var array
     */
    private static $access_cache = array();

    /**
     * Tables that require clinic isolation
     *
     * @var array
     */
    private static $isolated_tables = array(
        'kc_appointments' => 'clinic_id',
        'kc_patient_encounters' => 'clinic_id',
        'kc_bills' => 'clinic_id',
        'kc_prescription' => null, // Isolated via encounter
        'kc_medical_history' => null, // Isolated via encounter
        'kc_patient_clinic_mappings' => 'clinic_id',
        'kc_doctor_clinic_mappings' => 'clinic_id',
        'kc_appointment_service_mapping' => null, // Isolated via appointment
        'kc_custom_fields' => 'clinic_id',
        'kc_services' => 'clinic_id'
    );

    /**
     * Initialize clinic isolation service
     *
     * @since 1.0.0
     */
    public static function init() {
        // Hook into database queries to add clinic filters
        // add_filter( 'query', array( __CLASS__, 'filter_database_queries' ), 10, 1 );
        
        // Clear access cache periodically
        wp_schedule_event( time(), 'hourly', 'kivicare_clear_access_cache' );
        add_action( 'kivicare_clear_access_cache', array( __CLASS__, 'clear_access_cache' ) );
    }

    /**
     * Validate clinic access for current user
     *
     * @param int $clinic_id Clinic ID to check
     * @param int $user_id   User ID (optional, defaults to current user)
     * @return bool|WP_Error True if access allowed, WP_Error if denied
     * @since 1.0.0
     */
    public static function validate_clinic_access( $clinic_id, $user_id = null ) {
        if ( ! $user_id ) {
            $user_id = get_current_user_id();
        }

        if ( ! $user_id ) {
            return new WP_Error( 'no_user', 'No user provided for clinic access validation' );
        }

        // Check cache first
        $cache_key = "user_{$user_id}_clinic_{$clinic_id}";
        if ( isset( self::$access_cache[$cache_key] ) ) {
            return self::$access_cache[$cache_key];
        }

        $user = get_user_by( 'ID', $user_id );
        if ( ! $user ) {
            $result = new WP_Error( 'invalid_user', 'Invalid user ID' );
            self::$access_cache[$cache_key] = $result;
            return $result;
        }

        // Administrators have access to all clinics
        if ( in_array( 'administrator', $user->roles ) ) {
            self::$access_cache[$cache_key] = true;
            return true;
        }

        global $wpdb;
        $has_access = false;

        // Check based on user role
        if ( in_array( 'doctor', $user->roles ) ) {
            $count = $wpdb->get_var( $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->prefix}kc_doctor_clinic_mappings 
                 WHERE doctor_id = %d AND clinic_id = %d",
                $user_id, $clinic_id
            ) );
            $has_access = $count > 0;
            
        } elseif ( in_array( 'patient', $user->roles ) ) {
            $count = $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
            ) );
            $has_access = $count > 0;
            
        } elseif ( in_array( 'kivicare_receptionist', $user->roles ) ) {
            // Check if user is admin of this clinic or assigned to it
            $count = $wpdb->get_var( $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->prefix}kc_clinics 
                 WHERE id = %d AND (clinic_admin_id = %d OR id IN (
                     SELECT clinic_id FROM {$wpdb->prefix}kc_receptionist_clinic_mappings 
                     WHERE receptionist_id = %d
                 ))",
                $clinic_id, $user_id, $user_id
            ) );
            $has_access = $count > 0;
        }

        if ( ! $has_access ) {
            API_Logger::log_security_event( 
                'clinic_access_denied',
                "User {$user_id} denied access to clinic {$clinic_id}",
                array( 'user_roles' => $user->roles )
            );
            
            $result = new WP_Error( 
                'clinic_access_denied',
                'You do not have access to this clinic'
            );
        } else {
            $result = true;
        }

        self::$access_cache[$cache_key] = $result;
        return $result;
    }

    /**
     * Get user's accessible clinics
     *
     * @param int $user_id User ID (optional, defaults to current user)
     * @return array Array of clinic IDs
     * @since 1.0.0
     */
    public static function get_user_clinics( $user_id = null ) {
        if ( ! $user_id ) {
            $user_id = get_current_user_id();
        }

        if ( ! $user_id ) {
            return array();
        }

        $user = get_user_by( 'ID', $user_id );
        if ( ! $user ) {
            return array();
        }

        global $wpdb;

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

        $clinic_ids = array();

        // Get clinics based on user role
        if ( in_array( 'doctor', $user->roles ) ) {
            $ids = $wpdb->get_col( $wpdb->prepare(
                "SELECT DISTINCT clinic_id FROM {$wpdb->prefix}kc_doctor_clinic_mappings 
                 WHERE doctor_id = %d",
                $user_id
            ) );
            $clinic_ids = array_merge( $clinic_ids, $ids );
            
        } elseif ( in_array( 'patient', $user->roles ) ) {
            $ids = $wpdb->get_col( $wpdb->prepare(
                "SELECT DISTINCT clinic_id FROM {$wpdb->prefix}kc_patient_clinic_mappings 
                 WHERE patient_id = %d",
                $user_id
            ) );
            $clinic_ids = array_merge( $clinic_ids, $ids );
            
        } elseif ( in_array( 'kivicare_receptionist', $user->roles ) ) {
            // Get clinics where user is admin
            $admin_clinics = $wpdb->get_col( $wpdb->prepare(
                "SELECT id FROM {$wpdb->prefix}kc_clinics 
                 WHERE clinic_admin_id = %d AND status = 1",
                $user_id
            ) );
            $clinic_ids = array_merge( $clinic_ids, $admin_clinics );
            
            // Get clinics where user is assigned as receptionist
            $assigned_clinics = $wpdb->get_col( $wpdb->prepare(
                "SELECT DISTINCT clinic_id FROM {$wpdb->prefix}kc_receptionist_clinic_mappings 
                 WHERE receptionist_id = %d",
                $user_id
            ) );
            $clinic_ids = array_merge( $clinic_ids, $assigned_clinics );
        }

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

    /**
     * Add clinic filter to SQL WHERE clause
     *
     * @param string $where_clause Existing WHERE clause
     * @param int    $clinic_id    Clinic ID to filter by
     * @param string $table_alias  Table alias (optional)
     * @return string Modified WHERE clause
     * @since 1.0.0
     */
    public static function add_clinic_filter( $where_clause, $clinic_id, $table_alias = '' ) {
        $clinic_column = $table_alias ? "{$table_alias}.clinic_id" : 'clinic_id';
        $filter = " AND {$clinic_column} = " . intval( $clinic_id );
        
        if ( empty( $where_clause ) || trim( $where_clause ) === '1=1' ) {
            return "WHERE {$clinic_column} = " . intval( $clinic_id );
        }
        
        return $where_clause . $filter;
    }

    /**
     * Get clinic-filtered query for user
     *
     * @param string $base_query Base SQL query
     * @param string $table_name Table name
     * @param int    $user_id    User ID (optional)
     * @return string Modified query with clinic filters
     * @since 1.0.0
     */
    public static function get_clinic_filtered_query( $base_query, $table_name, $user_id = null ) {
        if ( ! $user_id ) {
            $user_id = get_current_user_id();
        }

        if ( ! $user_id ) {
            return $base_query;
        }

        // Check if table requires clinic isolation
        $table_key = str_replace( get_option( 'wpdb' )->prefix, '', $table_name );
        if ( ! isset( self::$isolated_tables[$table_key] ) ) {
            return $base_query;
        }

        $clinic_column = self::$isolated_tables[$table_key];
        if ( ! $clinic_column ) {
            return $base_query; // Table isolated via joins
        }

        $user_clinics = self::get_user_clinics( $user_id );
        if ( empty( $user_clinics ) ) {
            // User has no clinic access - return query that returns no results
            return str_replace( 'WHERE', 'WHERE 1=0 AND', $base_query );
        }

        $clinic_ids = implode( ',', $user_clinics );
        $clinic_filter = " AND {$clinic_column} IN ({$clinic_ids})";

        // Add clinic filter to WHERE clause
        if ( strpos( strtoupper( $base_query ), 'WHERE' ) !== false ) {
            return str_replace( 'WHERE', "WHERE 1=1 {$clinic_filter} AND", $base_query );
        } else {
            return $base_query . " WHERE {$clinic_column} IN ({$clinic_ids})";
        }
    }

    /**
     * Validate data access for specific record
     *
     * @param string $table_name Table name
     * @param int    $record_id  Record ID
     * @param int    $user_id    User ID (optional)
     * @return bool|WP_Error True if access allowed
     * @since 1.0.0
     */
    public static function validate_record_access( $table_name, $record_id, $user_id = null ) {
        if ( ! $user_id ) {
            $user_id = get_current_user_id();
        }

        global $wpdb;

        // Get clinic ID for the record
        $clinic_id = null;
        $table_key = str_replace( $wpdb->prefix, '', $table_name );

        if ( isset( self::$isolated_tables[$table_key] ) && self::$isolated_tables[$table_key] ) {
            $clinic_column = self::$isolated_tables[$table_key];
            $clinic_id = $wpdb->get_var( $wpdb->prepare(
                "SELECT {$clinic_column} FROM {$table_name} WHERE id = %d",
                $record_id
            ) );
        } else {
            // Handle tables isolated via joins
            switch ( $table_key ) {
                case 'kc_prescription':
                    $clinic_id = $wpdb->get_var( $wpdb->prepare(
                        "SELECT e.clinic_id FROM {$wpdb->prefix}kc_prescription p
                         INNER JOIN {$wpdb->prefix}kc_patient_encounters e ON p.encounter_id = e.id
                         WHERE p.id = %d",
                        $record_id
                    ) );
                    break;
                    
                case 'kc_medical_history':
                    $clinic_id = $wpdb->get_var( $wpdb->prepare(
                        "SELECT e.clinic_id FROM {$wpdb->prefix}kc_medical_history m
                         INNER JOIN {$wpdb->prefix}kc_patient_encounters e ON m.encounter_id = e.id
                         WHERE m.id = %d",
                        $record_id
                    ) );
                    break;
                    
                case 'kc_appointment_service_mapping':
                    $clinic_id = $wpdb->get_var( $wpdb->prepare(
                        "SELECT a.clinic_id FROM {$wpdb->prefix}kc_appointment_service_mapping asm
                         INNER JOIN {$wpdb->prefix}kc_appointments a ON asm.appointment_id = a.id
                         WHERE asm.id = %d",
                        $record_id
                    ) );
                    break;
            }
        }

        if ( ! $clinic_id ) {
            return new WP_Error( 'record_not_found', 'Record not found or no clinic association' );
        }

        return self::validate_clinic_access( $clinic_id, $user_id );
    }

    /**
     * Filter database queries for clinic isolation
     *
     * @param string $query SQL query
     * @return string Filtered query
     * @since 1.0.0
     * @deprecated 1.0.1 This method is disabled due to a security vulnerability.
     *                   A more robust solution should be implemented.
     */
    public static function filter_database_queries( $query ) {
        // Only filter SELECT queries from Care tables
        if ( strpos( strtoupper( $query ), 'SELECT' ) !== 0 ) {
            return $query;
        }

        $user_id = get_current_user_id();
        if ( ! $user_id ) {
            return $query;
        }

        // Check if query involves isolated tables
        $needs_filtering = false;
        foreach ( array_keys( self::$isolated_tables ) as $table ) {
            if ( strpos( $query, get_option( 'wpdb' )->prefix . $table ) !== false ) {
                $needs_filtering = true;
                break;
            }
        }

        if ( ! $needs_filtering ) {
            return $query;
        }

        // Skip filtering for administrators
        $user = wp_get_current_user();
        if ( $user && in_array( 'administrator', $user->roles ) ) {
            return $query;
        }

        // Apply clinic filtering based on user access
        $user_clinics = self::get_user_clinics( $user_id );
        if ( empty( $user_clinics ) ) {
            // Return query that returns no results
            return str_replace( 'SELECT', 'SELECT * FROM (SELECT', $query ) . ') AS no_access WHERE 1=0';
        }

        // This is a simplified approach - in production you might want more sophisticated query parsing
        $clinic_ids = implode( ',', $user_clinics );
        
        foreach ( self::$isolated_tables as $table => $column ) {
            if ( $column && strpos( $query, get_option( 'wpdb' )->prefix . $table ) !== false ) {
                $table_with_prefix = get_option( 'wpdb' )->prefix . $table;
                
                // Add clinic filter if not already present
                if ( strpos( $query, "{$column} IN" ) === false && strpos( $query, "{$column} =" ) === false ) {
                    if ( strpos( strtoupper( $query ), 'WHERE' ) !== false ) {
                        $query = preg_replace(
                            '/WHERE\s+/i',
                            "WHERE {$column} IN ({$clinic_ids}) AND ",
                            $query,
                            1
                        );
                    } else {
                        $query .= " WHERE {$column} IN ({$clinic_ids})";
                    }
                }
            }
        }

        return $query;
    }

    /**
     * Create secure clinic-scoped query builder
     *
     * @param string $table_name Table name
     * @param int    $clinic_id  Clinic ID
     * @return object Query builder instance
     * @since 1.0.0
     */
    /**
     * Create secure clinic-scoped query builder
     *
     * @param string $table_name Table name
     * @param int    $clinic_id  Clinic ID
     * @return object Query builder instance
     * @since 1.0.0
     * @deprecated 1.0.1 This method is disabled due to a security vulnerability.
     *                   It should be replaced with a more robust query builder.
     */
    public static function create_secure_query( $table_name, $clinic_id ) {
        throw new \Exception( 'The create_secure_query method is disabled due to a security vulnerability.' );
    }

    /**
     * Clear access cache
     *
     * @since 1.0.0
     */
    public static function clear_access_cache() {
        self::$access_cache = array();
        API_Logger::log_business_event( 'cache_cleared', 'Clinic access cache cleared' );
    }

    /**
     * Generate clinic isolation report
     *
     * @return array Isolation report data
     * @since 1.0.0
     */
    public static function generate_isolation_report() {
        global $wpdb;

        $report = array(
            'timestamp' => current_time( 'Y-m-d H:i:s' ),
            'total_clinics' => 0,
            'active_clinics' => 0,
            'user_clinic_mappings' => array(),
            'isolation_violations' => array(),
            'recommendations' => array()
        );

        // Count clinics - SECURED WITH PREPARED STATEMENTS
        $report['total_clinics'] = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}kc_clinics WHERE 1 = %d", 1 ) );
        $report['active_clinics'] = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}kc_clinics WHERE status = %d", 1 ) );

        // Count user-clinic mappings - SECURED WITH PREPARED STATEMENTS
        $report['user_clinic_mappings'] = array(
            'doctors' => (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}kc_doctor_clinic_mappings WHERE 1 = %d", 1 ) ),
            'patients' => (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}kc_patient_clinic_mappings WHERE 1 = %d", 1 ) )
        );

        // Check for potential isolation violations
        $violations = array();

        // Check for cross-clinic appointments
        $cross_clinic_appointments = $wpdb->get_results(
            "SELECT a.id, a.clinic_id, dm.clinic_id as doctor_clinic, pm.clinic_id as patient_clinic
             FROM {$wpdb->prefix}kc_appointments a
             LEFT JOIN {$wpdb->prefix}kc_doctor_clinic_mappings dm ON a.doctor_id = dm.doctor_id
             LEFT JOIN {$wpdb->prefix}kc_patient_clinic_mappings pm ON a.patient_id = pm.patient_id
             WHERE (dm.clinic_id != a.clinic_id OR pm.clinic_id != a.clinic_id)
             LIMIT 10"
        );

        if ( $cross_clinic_appointments ) {
            $violations[] = array(
                'type' => 'cross_clinic_appointments',
                'count' => count( $cross_clinic_appointments ),
                'description' => 'Appointments where doctor or patient clinic differs from appointment clinic',
                'severity' => 'high'
            );
        }

        $report['isolation_violations'] = $violations;

        // Generate recommendations
        $recommendations = array();
        
        if ( ! empty( $violations ) ) {
            $recommendations[] = 'Review and fix cross-clinic data inconsistencies';
        }
        
        if ( $report['user_clinic_mappings']['doctors'] === 0 ) {
            $recommendations[] = 'Set up doctor-clinic mappings for proper isolation';
        }
        
        if ( $report['user_clinic_mappings']['patients'] === 0 ) {
            $recommendations[] = 'Set up patient-clinic mappings for proper isolation';
        }

        $report['recommendations'] = $recommendations;

        // Log the report generation
        API_Logger::log_business_event( 'isolation_report_generated', 'Clinic isolation report generated', $report );

        return $report;
    }
}