<?php
/**
 * JWT Middleware
 *
 * Handles JWT authentication for all API requests
 *
 * @package Care_API
 * @subpackage Middleware
 * @version    1.0.0
 * @author     Descomplicar® <dev@descomplicar.pt>
 * @link       https://descomplicar.pt
 * @since      1.0.0
 */

namespace Care_API\Middleware;

use Care_API\Services\Auth_Service;
use Care_API\Utils\Error_Handler;
use Care_API\Utils\API_Logger;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;

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

/**
 * Class JWT_Middleware
 *
 * Middleware for JWT token validation and user authentication
 *
 * @since 1.0.0
 */
class JWT_Middleware {

    /**
     * Routes that don't require authentication
     *
     * @var array
     */
    private static $public_routes = array(
        '/care/v1/auth/login',
        '/care/v1/auth/register',
        '/care/v1/auth/forgot-password',
        '/care/v1/auth/reset-password',
        '/care/v1/system/health',
        '/care/v1/system/version'
    );

    /**
     * Initialize middleware
     *
     * @since 1.0.0
     */
    public static function init() {
        add_filter( 'rest_pre_dispatch', array( __CLASS__, 'authenticate_request' ), 10, 3 );
    }

    /**
     * Authenticate REST API request
     *
     * @param mixed           $result  Response to replace the requested version with.
     * @param WP_REST_Server  $server  Server instance.
     * @param WP_REST_Request $request Request object.
     * @return mixed|WP_REST_Response
     * @since 1.0.0
     */
    public static function authenticate_request( $result, $server, $request ) {
        $route = $request->get_route();

        // Only handle Care API routes
        if ( strpos( $route, '/care/v1/' ) === false ) {
            return $result;
        }

        // Check if route requires authentication
        if ( self::is_public_route( $route ) ) {
            return $result;
        }

        // Extract token from request
        $token = self::extract_token( $request );
        if ( ! $token ) {
            API_Logger::log_auth_event( 'token_missing', 0, false, 'no_token_provided' );
            return Error_Handler::handle_auth_error( 'no_token', 'Authentication token is required' );
        }

        // Validate token
        $validation_result = Auth_Service::validate_token( $token );
        if ( is_wp_error( $validation_result ) ) {
            API_Logger::log_auth_event( 'token_invalid', 0, false, $validation_result->get_error_code() );
            return Error_Handler::handle_auth_error( 
                $validation_result->get_error_code(),
                $validation_result->get_error_message()
            );
        }

        // Set current user
        $user_id = $validation_result['user_id'];
        wp_set_current_user( $user_id );

        // Log successful authentication
        API_Logger::log_auth_event( 'token_validated', $user_id, true );

        // Add user data to request for easy access
        $request->set_param( '_authenticated_user', $validation_result );

        return $result;
    }

    /**
     * Check if route is public (doesn't require authentication)
     *
     * @param string $route Route path
     * @return bool True if public route
     * @since 1.0.0
     */
    private static function is_public_route( $route ) {
        foreach ( self::$public_routes as $public_route ) {
            if ( $route === $public_route || strpos( $route, $public_route ) === 0 ) {
                return true;
            }
        }
        return false;
    }

    /**
     * Extract JWT token from request
     *
     * @param WP_REST_Request $request Request object
     * @return string|null Token or null if not found
     * @since 1.0.0
     */
    private static function extract_token( WP_REST_Request $request ) {
        // Check Authorization header (Bearer token)
        $headers = $request->get_headers();
        if ( isset( $headers['authorization'] ) ) {
            $auth_header = $headers['authorization'][0] ?? '';
            if ( strpos( $auth_header, 'Bearer ' ) === 0 ) {
                return substr( $auth_header, 7 );
            }
        }

        // Check for token in query parameters (fallback)
        $token = $request->get_param( 'token' );
        if ( $token ) {
            return $token;
        }

        // Check for token in custom header
        if ( isset( $headers['x-kivicare-token'] ) ) {
            return $headers['x-kivicare-token'][0] ?? null;
        }

        return null;
    }

    /**
     * Check if user has permission for specific action
     *
     * @param WP_REST_Request $request Request object
     * @param string          $action  Action to check
     * @param string          $resource Resource type
     * @param int             $resource_id Resource ID (optional)
     * @return bool|WP_Error True if allowed, WP_Error if denied
     * @since 1.0.0
     */
    public static function check_permission( WP_REST_Request $request, $action, $resource, $resource_id = null ) {
        $current_user = wp_get_current_user();
        if ( ! $current_user || ! $current_user->ID ) {
            return new WP_Error( 'user_not_authenticated', 'User not authenticated' );
        }

        $user_data = $request->get_param( '_authenticated_user' );
        if ( ! $user_data ) {
            return new WP_Error( 'invalid_user_session', 'Invalid user session' );
        }

        // Check clinic context
        $clinic_id = self::get_user_clinic_context( $current_user->ID, $request );
        if ( ! $clinic_id && $resource !== 'clinic' ) {
            return new WP_Error( 'no_clinic_context', 'No clinic context found for user' );
        }

        // Permission matrix
        $user_role = $user_data['user_role'] ?? '';
        $permission_granted = false;

        switch ( $user_role ) {
            case 'administrator':
                // Administrators can do everything
                $permission_granted = true;
                break;

            case 'doctor':
                $permission_granted = self::check_doctor_permissions( $action, $resource, $current_user->ID, $clinic_id, $resource_id );
                break;

            case 'patient':
                $permission_granted = self::check_patient_permissions( $action, $resource, $current_user->ID, $resource_id );
                break;

            case 'kivicare_receptionist':
                $permission_granted = self::check_receptionist_permissions( $action, $resource, $clinic_id, $resource_id );
                break;

            default:
                $permission_granted = false;
        }

        if ( ! $permission_granted ) {
            API_Logger::log_security_event( 
                'permission_denied',
                "User {$current_user->ID} ({$user_role}) denied {$action} access to {$resource}",
                array( 'resource_id' => $resource_id, 'clinic_id' => $clinic_id )
            );

            return new WP_Error( 
                'insufficient_permissions',
                "You don't have permission to {$action} {$resource}"
            );
        }

        return true;
    }

    /**
     * Check doctor permissions
     *
     * @param string $action      Action to check
     * @param string $resource    Resource type
     * @param int    $doctor_id   Doctor user ID
     * @param int    $clinic_id   Clinic ID
     * @param int    $resource_id Resource ID
     * @return bool Permission granted
     * @since 1.0.0
     */
    private static function check_doctor_permissions( $action, $resource, $doctor_id, $clinic_id, $resource_id = null ) {
        global $wpdb;

        switch ( $resource ) {
            case 'patient':
                // Doctors can manage patients in their clinic
                if ( $action === 'create' || $action === 'read' || $action === 'update' ) {
                    return true;
                }
                if ( $action === 'delete' && $resource_id ) {
                    // Can only delete patients they've treated
                    $count = $wpdb->get_var( $wpdb->prepare(
                        "SELECT COUNT(*) FROM {$wpdb->prefix}kc_patient_encounters 
                         WHERE doctor_id = %d AND patient_id = %d AND clinic_id = %d",
                        $doctor_id, $resource_id, $clinic_id
                    ) );
                    return $count > 0;
                }
                break;

            case 'appointment':
                if ( $action === 'read' || $action === 'update' ) {
                    if ( $resource_id ) {
                        // Can only manage own appointments
                        $count = $wpdb->get_var( $wpdb->prepare(
                            "SELECT COUNT(*) FROM {$wpdb->prefix}kc_appointments 
                             WHERE id = %d AND doctor_id = %d AND clinic_id = %d",
                            $resource_id, $doctor_id, $clinic_id
                        ) );
                        return $count > 0;
                    }
                    return true;
                }
                break;

            case 'encounter':
                // Doctors can manage encounters for their patients
                if ( $action === 'create' || $action === 'read' || $action === 'update' ) {
                    if ( $resource_id ) {
                        $count = $wpdb->get_var( $wpdb->prepare(
                            "SELECT COUNT(*) FROM {$wpdb->prefix}kc_patient_encounters 
                             WHERE id = %d AND doctor_id = %d AND clinic_id = %d",
                            $resource_id, $doctor_id, $clinic_id
                        ) );
                        return $count > 0;
                    }
                    return true;
                }
                break;

            case 'prescription':
                // Doctors can manage prescriptions
                if ( in_array( $action, array( 'create', 'read', 'update', 'delete' ) ) ) {
                    if ( $resource_id ) {
                        $count = $wpdb->get_var( $wpdb->prepare(
                            "SELECT COUNT(*) FROM {$wpdb->prefix}kc_prescription p
                             INNER JOIN {$wpdb->prefix}kc_patient_encounters e ON p.encounter_id = e.id
                             WHERE p.id = %d AND e.doctor_id = %d AND e.clinic_id = %d",
                            $resource_id, $doctor_id, $clinic_id
                        ) );
                        return $count > 0;
                    }
                    return true;
                }
                break;

            case 'bill':
                // Doctors can view bills for their encounters
                if ( $action === 'read' ) {
                    return true;
                }
                break;

            case 'report':
                // Doctors can view their own reports
                if ( $action === 'read' ) {
                    return true;
                }
                break;
        }

        return false;
    }

    /**
     * Check patient permissions
     *
     * @param string $action      Action to check
     * @param string $resource    Resource type
     * @param int    $patient_id  Patient user ID
     * @param int    $resource_id Resource ID
     * @return bool Permission granted
     * @since 1.0.0
     */
    private static function check_patient_permissions( $action, $resource, $patient_id, $resource_id = null ) {
        global $wpdb;

        switch ( $resource ) {
            case 'patient':
                // Patients can only read/update their own data
                if ( ( $action === 'read' || $action === 'update' ) && ( ! $resource_id || $resource_id == $patient_id ) ) {
                    return true;
                }
                break;

            case 'appointment':
                if ( $action === 'create' ) {
                    return true;
                }
                if ( $action === 'read' || $action === 'update' || $action === 'delete' ) {
                    if ( $resource_id ) {
                        $count = $wpdb->get_var( $wpdb->prepare(
                            "SELECT COUNT(*) FROM {$wpdb->prefix}kc_appointments 
                             WHERE id = %d AND patient_id = %d",
                            $resource_id, $patient_id
                        ) );
                        return $count > 0;
                    }
                    return true; // For listing own appointments
                }
                break;

            case 'encounter':
                // Patients can only view their own encounters
                if ( $action === 'read' ) {
                    if ( $resource_id ) {
                        $count = $wpdb->get_var( $wpdb->prepare(
                            "SELECT COUNT(*) FROM {$wpdb->prefix}kc_patient_encounters 
                             WHERE id = %d AND patient_id = %d",
                            $resource_id, $patient_id
                        ) );
                        return $count > 0;
                    }
                    return true;
                }
                break;

            case 'prescription':
                // Patients can view their own prescriptions
                if ( $action === 'read' ) {
                    if ( $resource_id ) {
                        $count = $wpdb->get_var( $wpdb->prepare(
                            "SELECT COUNT(*) FROM {$wpdb->prefix}kc_prescription 
                             WHERE id = %d AND patient_id = %d",
                            $resource_id, $patient_id
                        ) );
                        return $count > 0;
                    }
                    return true;
                }
                break;

            case 'bill':
                // Patients can view their own bills
                if ( $action === 'read' ) {
                    if ( $resource_id ) {
                        $count = $wpdb->get_var( $wpdb->prepare(
                            "SELECT COUNT(*) FROM {$wpdb->prefix}kc_bills b
                             INNER JOIN {$wpdb->prefix}kc_patient_encounters e ON b.encounter_id = e.id
                             WHERE b.id = %d AND e.patient_id = %d",
                            $resource_id, $patient_id
                        ) );
                        return $count > 0;
                    }
                    return true;
                }
                break;
        }

        return false;
    }

    /**
     * Check receptionist permissions
     *
     * @param string $action      Action to check
     * @param string $resource    Resource type
     * @param int    $clinic_id   Clinic ID
     * @param int    $resource_id Resource ID
     * @return bool Permission granted
     * @since 1.0.0
     */
    private static function check_receptionist_permissions( $action, $resource, $clinic_id, $resource_id = null ) {
        global $wpdb;

        switch ( $resource ) {
            case 'patient':
                // Receptionists can manage patients in their clinic
                if ( in_array( $action, array( 'create', 'read', 'update' ) ) ) {
                    return true;
                }
                break;

            case 'appointment':
                // Receptionists can manage all appointments in their clinic
                if ( in_array( $action, array( 'create', 'read', 'update', 'delete' ) ) ) {
                    if ( $resource_id ) {
                        $count = $wpdb->get_var( $wpdb->prepare(
                            "SELECT COUNT(*) FROM {$wpdb->prefix}kc_appointments 
                             WHERE id = %d AND clinic_id = %d",
                            $resource_id, $clinic_id
                        ) );
                        return $count > 0;
                    }
                    return true;
                }
                break;

            case 'encounter':
                // Receptionists can view encounters in their clinic
                if ( $action === 'read' ) {
                    if ( $resource_id ) {
                        $count = $wpdb->get_var( $wpdb->prepare(
                            "SELECT COUNT(*) FROM {$wpdb->prefix}kc_patient_encounters 
                             WHERE id = %d AND clinic_id = %d",
                            $resource_id, $clinic_id
                        ) );
                        return $count > 0;
                    }
                    return true;
                }
                break;

            case 'bill':
                // Receptionists can manage bills in their clinic
                if ( in_array( $action, array( 'create', 'read', 'update' ) ) ) {
                    if ( $resource_id ) {
                        $count = $wpdb->get_var( $wpdb->prepare(
                            "SELECT COUNT(*) FROM {$wpdb->prefix}kc_bills 
                             WHERE id = %d AND clinic_id = %d",
                            $resource_id, $clinic_id
                        ) );
                        return $count > 0;
                    }
                    return true;
                }
                break;

            case 'report':
                // Receptionists can view clinic reports
                if ( $action === 'read' ) {
                    return true;
                }
                break;
        }

        return false;
    }

    /**
     * Get user's clinic context
     *
     * @param int             $user_id User ID
     * @param WP_REST_Request $request Request object
     * @return int|null Clinic ID or null
     * @since 1.0.0
     */
    private static function get_user_clinic_context( $user_id, WP_REST_Request $request ) {
        global $wpdb;

        // Check if clinic_id is provided in request
        $clinic_id = $request->get_param( 'clinic_id' );
        if ( $clinic_id ) {
            // Verify user has access to this clinic
            $user = wp_get_current_user();
            
            if ( in_array( 'administrator', $user->roles ) ) {
                return (int) $clinic_id;
            }

            // Check user-clinic mapping
            $has_access = false;
            
            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;
            }

            if ( $has_access ) {
                return (int) $clinic_id;
            }
        }

        // Get user's default clinic
        $user = wp_get_current_user();
        
        if ( in_array( 'doctor', $user->roles ) ) {
            $clinic_id = $wpdb->get_var( $wpdb->prepare(
                "SELECT clinic_id FROM {$wpdb->prefix}kc_doctor_clinic_mappings 
                 WHERE doctor_id = %d LIMIT 1",
                $user_id
            ) );
        } elseif ( in_array( 'patient', $user->roles ) ) {
            $clinic_id = $wpdb->get_var( $wpdb->prepare(
                "SELECT clinic_id FROM {$wpdb->prefix}kc_patient_clinic_mappings 
                 WHERE patient_id = %d LIMIT 1",
                $user_id
            ) );
        } elseif ( in_array( 'kivicare_receptionist', $user->roles ) ) {
            // Get clinic where user is admin
            $clinic_id = $wpdb->get_var( $wpdb->prepare(
                "SELECT id FROM {$wpdb->prefix}kc_clinics 
                 WHERE clinic_admin_id = %d LIMIT 1",
                $user_id
            ) );
        }

        return $clinic_id ? (int) $clinic_id : null;
    }

    /**
     * Add CORS headers for API requests
     *
     * @since 1.0.0
     */
    public static function add_cors_headers() {
        add_action( 'rest_api_init', function() {
            remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
            add_filter( 'rest_pre_serve_request', function( $value ) {
                $allowed_origins = apply_filters( 'care_api_cors_origins', array( 
                    get_site_url(),
                    'http://localhost:3000',
                    'https://localhost:3000'
                ) );

                $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
                if ( in_array( $origin, $allowed_origins ) ) {
                    header( 'Access-Control-Allow-Origin: ' . $origin );
                }

                header( 'Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS, PATCH' );
                header( 'Access-Control-Allow-Headers: Authorization, Content-Type, X-Care-Token' );
                header( 'Access-Control-Allow-Credentials: true' );
                header( 'Access-Control-Max-Age: 86400' );

                return $value;
            });
        });

        // Handle OPTIONS requests
        add_action( 'init', function() {
            if ( $_SERVER['REQUEST_METHOD'] === 'OPTIONS' ) {
                status_header( 200 );
                exit;
            }
        });
    }
}