<?php
/**
 * Authentication Service
 *
 * Handles JWT authentication, user validation and security
 *
 * @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 Auth_Service
 *
 * JWT Authentication service for Care API
 *
 * @since 1.0.0
 */
class Auth_Service {

    /**
     * JWT Secret key
     *
     * @var string
     */
    private static $jwt_secret;

    /**
     * Token expiration time (24 hours)
     *
     * @var int
     */
    private static $token_expiration = 86400;

    /**
     * Refresh token expiration (7 days)
     *
     * @var int
     */
    private static $refresh_token_expiration = 604800;

    /**
     * Valid Care roles
     *
     * @var array
     */
    private static $valid_roles = array(
        'administrator',
        'kivicare_doctor',
        'kivicare_patient',
        'kivicare_receptionist'
    );

    /**
     * Initialize the authentication service
     *
     * @since 1.0.0
     */
    public static function init() {
        self::$jwt_secret = self::get_jwt_secret();
        
        // Hook into WordPress authentication
        add_filter( 'determine_current_user', array( self::class, 'determine_current_user' ), 20 );
        add_filter( 'rest_authentication_errors', array( self::class, 'rest_authentication_errors' ) );
        
        // Add custom headers
        add_action( 'rest_api_init', array( self::class, 'add_cors_headers' ) );
    }

    /**
     * Authenticate user with username/email and password
     *
     * @param string $username Username or email
     * @param string $password Password
     * @return array|WP_Error Authentication response or error
     * @since 1.0.0
     */
    public static function authenticate( $username, $password ) {
        // Input validation
        if ( empty( $username ) || empty( $password ) ) {
            return new \WP_Error(
                'missing_credentials',
                'Username and password are required',
                array( 'status' => 400 )
            );
        }

        // Attempt to get user by username or email
        $user = self::get_user_by_login( $username );
        
        if ( ! $user ) {
            return new \WP_Error(
                'invalid_username',
                'Invalid username or email address',
                array( 'status' => 401 )
            );
        }

        // Verify password
        if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
            // Log failed login attempt
            self::log_failed_login( $user->ID, $username );
            
            return new \WP_Error(
                'invalid_password',
                'Invalid password',
                array( 'status' => 401 )
            );
        }

        // Check if user has valid Care role
        if ( ! self::has_valid_role( $user ) ) {
            return new \WP_Error(
                'insufficient_permissions',
                'User does not have permission to access Care API',
                array( 'status' => 403 )
            );
        }

        // Check if user account is active
        $user_status = get_user_meta( $user->ID, 'kivicare_user_status', true );
        if ( $user_status === 'inactive' || $user_status === 'suspended' ) {
            return new \WP_Error(
                'account_inactive',
                'User account is inactive or suspended',
                array( 'status' => 403 )
            );
        }

        // Generate tokens
        $access_token = self::generate_jwt_token( $user );
        $refresh_token = self::generate_refresh_token( $user );

        // Update user login metadata
        self::update_login_metadata( $user->ID );

        // Return authentication response
        return array(
            'success' => true,
            'data' => array(
                'user' => self::format_user_data( $user ),
                'access_token' => $access_token,
                'refresh_token' => $refresh_token,
                'token_type' => 'Bearer',
                'expires_in' => self::$token_expiration
            )
        );
    }

    /**
     * Refresh access token using refresh token
     *
     * @param string $refresh_token Refresh token
     * @return array|WP_Error New tokens or error
     * @since 1.0.0
     */
    public static function refresh_token( $refresh_token ) {
        if ( empty( $refresh_token ) ) {
            return new \WP_Error(
                'missing_refresh_token',
                'Refresh token is required',
                array( 'status' => 400 )
            );
        }

        // Validate refresh token
        $user_id = self::validate_refresh_token( $refresh_token );
        
        if ( is_wp_error( $user_id ) ) {
            return $user_id;
        }

        $user = get_user_by( 'id', $user_id );
        if ( ! $user ) {
            return new \WP_Error(
                'user_not_found',
                'User not found',
                array( 'status' => 404 )
            );
        }

        // Check if user still has valid permissions
        if ( ! self::has_valid_role( $user ) ) {
            return new \WP_Error(
                'insufficient_permissions',
                'User no longer has permission to access Care API',
                array( 'status' => 403 )
            );
        }

        // Generate new tokens
        $new_access_token = self::generate_jwt_token( $user );
        $new_refresh_token = self::generate_refresh_token( $user );

        return array(
            'success' => true,
            'data' => array(
                'access_token' => $new_access_token,
                'refresh_token' => $new_refresh_token,
                'token_type' => 'Bearer',
                'expires_in' => self::$token_expiration
            )
        );
    }

    /**
     * Validate JWT token and return user
     *
     * @param string $token JWT token
     * @return WP_User|WP_Error User object or error
     * @since 1.0.0
     */
    public static function validate_token( $token ) {
        if ( empty( $token ) ) {
            return new \WP_Error(
                'missing_token',
                'Authentication token is required',
                array( 'status' => 401 )
            );
        }

        try {
            // Remove 'Bearer ' prefix if present
            $token = str_replace( 'Bearer ', '', $token );
            
            // Decode JWT token
            $payload = self::decode_jwt_token( $token );
            
            if ( is_wp_error( $payload ) ) {
                return $payload;
            }

            // Get user
            $user = get_user_by( 'id', $payload->user_id );
            
            if ( ! $user ) {
                return new \WP_Error(
                    'user_not_found',
                    'User not found',
                    array( 'status' => 401 )
                );
            }

            // Validate user still has proper role
            if ( ! self::has_valid_role( $user ) ) {
                return new \WP_Error(
                    'insufficient_permissions',
                    'User no longer has permission to access Care API',
                    array( 'status' => 403 )
                );
            }

            return $user;
            
        } catch ( Exception $e ) {
            return new \WP_Error(
                'token_validation_failed',
                'Token validation failed: ' . $e->getMessage(),
                array( 'status' => 401 )
            );
        }
    }

    /**
     * Logout user by invalidating tokens
     *
     * @param int $user_id User ID
     * @return bool Success status
     * @since 1.0.0
     */
    public static function logout( $user_id ) {
        // Invalidate all refresh tokens for this user
        delete_user_meta( $user_id, 'kivicare_refresh_token' );
        delete_user_meta( $user_id, 'kivicare_refresh_token_expires' );
        
        // Update logout timestamp
        update_user_meta( $user_id, 'kivicare_last_logout', current_time( 'mysql' ) );
        
        // Log logout action
        self::log_user_action( $user_id, 'logout' );
        
        return true;
    }

    /**
     * Get current authenticated user
     *
     * @return WP_User|null Current user or null
     * @since 1.0.0
     */
    public static function get_current_user() {
        $user_id = get_current_user_id();
        return $user_id ? get_user_by( 'id', $user_id ) : null;
    }

    /**
     * Check if current user has specific capability
     *
     * @param string $capability Capability to check
     * @param array  $args       Additional arguments
     * @return bool True if user has capability
     * @since 1.0.0
     */
    public static function current_user_can( $capability, $args = array() ) {
        $user = self::get_current_user();
        
        if ( ! $user ) {
            return false;
        }

        // Check WordPress capability
        if ( ! empty( $args ) ) {
            return user_can( $user, $capability, ...$args );
        }
        
        return user_can( $user, $capability );
    }

    /**
     * Generate JWT token for user
     *
     * @param WP_User $user User object
     * @return string JWT token
     * @since 1.0.0
     */
    private static function generate_jwt_token( $user ) {
        $issued_at = time();
        $expiration = $issued_at + self::$token_expiration;
        
        $payload = array(
            'iss' => get_site_url(),
            'aud' => 'care-api',
            'iat' => $issued_at,
            'exp' => $expiration,
            'user_id' => $user->ID,
            'username' => $user->user_login,
            'email' => $user->user_email,
            'roles' => $user->roles,
            'jti' => wp_generate_uuid4()
        );

        return self::encode_jwt_token( $payload );
    }

    /**
     * Generate refresh token for user
     *
     * @param WP_User $user User object
     * @return string Refresh token
     * @since 1.0.0
     */
    private static function generate_refresh_token( $user ) {
        $refresh_token = wp_generate_uuid4();
        $expires_at = time() + self::$refresh_token_expiration;
        
        // Store refresh token in user meta
        update_user_meta( $user->ID, 'kivicare_refresh_token', wp_hash( $refresh_token ) );
        update_user_meta( $user->ID, 'kivicare_refresh_token_expires', $expires_at );
        
        return $refresh_token;
    }

    /**
     * Validate refresh token
     *
     * @param string $refresh_token Refresh token
     * @return int|WP_Error User ID or error
     * @since 1.0.0
     */
    private static function validate_refresh_token( $refresh_token ) {
        // Find user with this refresh token
        $users = get_users( array(
            'meta_key' => 'kivicare_refresh_token',
            'meta_value' => wp_hash( $refresh_token ),
            'number' => 1
        ) );

        if ( empty( $users ) ) {
            return new \WP_Error(
                'invalid_refresh_token',
                'Invalid refresh token',
                array( 'status' => 401 )
            );
        }

        $user = $users[0];
        $expires_at = get_user_meta( $user->ID, 'kivicare_refresh_token_expires', true );

        // Check if token is expired
        if ( $expires_at && time() > $expires_at ) {
            // Clean up expired token
            delete_user_meta( $user->ID, 'kivicare_refresh_token' );
            delete_user_meta( $user->ID, 'kivicare_refresh_token_expires' );
            
            return new \WP_Error(
                'refresh_token_expired',
                'Refresh token has expired',
                array( 'status' => 401 )
            );
        }

        return $user->ID;
    }

    /**
     * Encode JWT token
     *
     * @param array $payload Token payload
     * @return string Encoded token
     * @since 1.0.0
     */
    private static function encode_jwt_token( $payload ) {
        $header = array(
            'typ' => 'JWT',
            'alg' => 'HS256'
        );

        $header_encoded = self::base64url_encode( wp_json_encode( $header ) );
        $payload_encoded = self::base64url_encode( wp_json_encode( $payload ) );
        
        $signature = hash_hmac(
            'sha256',
            $header_encoded . '.' . $payload_encoded,
            self::$jwt_secret,
            true
        );
        
        $signature_encoded = self::base64url_encode( $signature );

        return $header_encoded . '.' . $payload_encoded . '.' . $signature_encoded;
    }

    /**
     * Decode JWT token
     *
     * @param string $token JWT token
     * @return object|WP_Error Token payload or error
     * @since 1.0.0
     */
    private static function decode_jwt_token( $token ) {
        $parts = explode( '.', $token );
        
        if ( count( $parts ) !== 3 ) {
            return new \WP_Error(
                'invalid_token_format',
                'Invalid token format',
                array( 'status' => 401 )
            );
        }

        list( $header_encoded, $payload_encoded, $signature_encoded ) = $parts;

        // Verify signature
        $signature = self::base64url_decode( $signature_encoded );
        $expected_signature = hash_hmac(
            'sha256',
            $header_encoded . '.' . $payload_encoded,
            self::$jwt_secret,
            true
        );

        if ( ! hash_equals( $signature, $expected_signature ) ) {
            return new \WP_Error(
                'invalid_token_signature',
                'Invalid token signature',
                array( 'status' => 401 )
            );
        }

        // Decode payload
        $payload = json_decode( self::base64url_decode( $payload_encoded ) );
        
        if ( ! $payload ) {
            return new \WP_Error(
                'invalid_token_payload',
                'Invalid token payload',
                array( 'status' => 401 )
            );
        }

        // Check expiration
        if ( isset( $payload->exp ) && time() > $payload->exp ) {
            return new \WP_Error(
                'token_expired',
                'Token has expired',
                array( 'status' => 401 )
            );
        }

        return $payload;
    }

    /**
     * Base64URL encode
     *
     * @param string $data Data to encode
     * @return string Encoded data
     * @since 1.0.0
     */
    private static function base64url_encode( $data ) {
        return rtrim( strtr( base64_encode( $data ), '+/', '-_' ), '=' );
    }

    /**
     * Base64URL decode
     *
     * @param string $data Data to decode
     * @return string Decoded data
     * @since 1.0.0
     */
    private static function base64url_decode( $data ) {
        return base64_decode( str_pad( strtr( $data, '-_', '+/' ), strlen( $data ) % 4, '=', STR_PAD_RIGHT ) );
    }

    /**
     * Get or create JWT secret
     *
     * @return string JWT secret
     * @since 1.0.0
     */
    private static function get_jwt_secret() {
        // Try to get from wp-config constant first
        if ( defined( 'KIVICARE_JWT_SECRET' ) && ! empty( KIVICARE_JWT_SECRET ) ) {
            return KIVICARE_JWT_SECRET;
        }

        // Get from options
        $secret = get_option( 'kivicare_jwt_secret' );
        
        if ( empty( $secret ) ) {
            // Generate new secret
            $secret = wp_generate_password( 64, true, true );
            update_option( 'kivicare_jwt_secret', $secret );
        }

        return $secret;
    }

    /**
     * Get user by username or email
     *
     * @param string $login Username or email
     * @return WP_User|false User object or false
     * @since 1.0.0
     */
    private static function get_user_by_login( $login ) {
        // Try by username first
        $user = get_user_by( 'login', $login );
        
        // If not found, try by email
        if ( ! $user && is_email( $login ) ) {
            $user = get_user_by( 'email', $login );
        }

        return $user;
    }

    /**
     * Check if user has valid Care role
     *
     * @param WP_User $user User object
     * @return bool True if has valid role
     * @since 1.0.0
     */
    private static function has_valid_role( $user ) {
        $user_roles = $user->roles;
        return ! empty( array_intersect( $user_roles, self::$valid_roles ) );
    }

    /**
     * Format user data for API response
     *
     * @param WP_User $user User object
     * @return array Formatted user data
     * @since 1.0.0
     */
    private static function format_user_data( $user ) {
        return array(
            '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,
            'roles' => $user->roles,
            'primary_role' => self::get_primary_role( $user ),
            'avatar_url' => get_avatar_url( $user->ID ),
            'registered_date' => $user->user_registered
        );
    }

    /**
     * Get primary Care role for user
     *
     * @param WP_User $user User object
     * @return string Primary role
     * @since 1.0.0
     */
    private static function get_primary_role( $user ) {
        $kivicare_roles = array_intersect( $user->roles, self::$valid_roles );
        
        // Priority order for Care roles
        $role_priority = array(
            'administrator',
            'kivicare_doctor',
            'kivicare_receptionist',
            'kivicare_patient'
        );

        foreach ( $role_priority as $role ) {
            if ( in_array( $role, $kivicare_roles ) ) {
                return $role;
            }
        }

        return 'kivicare_patient'; // Default fallback
    }

    /**
     * Update user login metadata
     *
     * @param int $user_id User ID
     * @since 1.0.0
     */
    private static function update_login_metadata( $user_id ) {
        update_user_meta( $user_id, 'kivicare_last_login', current_time( 'mysql' ) );
        update_user_meta( $user_id, 'kivicare_login_count', (int) get_user_meta( $user_id, 'kivicare_login_count', true ) + 1 );
        update_user_meta( $user_id, 'kivicare_last_ip', self::get_client_ip() );
        
        // Log successful login
        self::log_user_action( $user_id, 'login' );
    }

    /**
     * Log failed login attempt
     *
     * @param int    $user_id User ID (if found)
     * @param string $username Attempted username
     * @since 1.0.0
     */
    private static function log_failed_login( $user_id, $username ) {
        $log_data = array(
            'user_id' => $user_id,
            'username' => $username,
            'ip_address' => self::get_client_ip(),
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
            'timestamp' => current_time( 'mysql' )
        );

        // Could be extended to store in custom table or send alerts
        do_action( 'kivicare_failed_login', $log_data );
    }

    /**
     * Log user action
     *
     * @param int    $user_id User ID
     * @param string $action  Action performed
     * @since 1.0.0
     */
    private static function log_user_action( $user_id, $action ) {
        $log_data = array(
            'user_id' => $user_id,
            'action' => $action,
            'ip_address' => self::get_client_ip(),
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
            'timestamp' => current_time( 'mysql' )
        );

        // Could be extended for audit logging
        do_action( 'kivicare_user_action', $log_data );
    }

    /**
     * Get client IP address
     *
     * @return string IP address
     * @since 1.0.0
     */
    private static function get_client_ip() {
        $ip_keys = array(
            'HTTP_CF_CONNECTING_IP',
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_FORWARDED',
            'HTTP_X_CLUSTER_CLIENT_IP',
            'HTTP_FORWARDED_FOR',
            'HTTP_FORWARDED',
            'REMOTE_ADDR'
        );

        foreach ( $ip_keys as $key ) {
            if ( ! empty( $_SERVER[ $key ] ) ) {
                $ip = $_SERVER[ $key ];
                if ( strpos( $ip, ',' ) !== false ) {
                    $ip = explode( ',', $ip )[0];
                }
                $ip = trim( $ip );
                if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
                    return $ip;
                }
            }
        }

        return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    }

    /**
     * WordPress hook: Determine current user from JWT token
     *
     * @param int $user_id Current user ID
     * @return int User ID
     * @since 1.0.0
     */
    public static function determine_current_user( $user_id ) {
        // Skip if user is already determined
        if ( $user_id ) {
            return $user_id;
        }

        // Only for REST API requests
        if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) {
            return $user_id;
        }

        // Get authorization header
        $auth_header = self::get_authorization_header();
        
        if ( empty( $auth_header ) ) {
            return $user_id;
        }

        // Validate token
        $user = self::validate_token( $auth_header );
        
        if ( is_wp_error( $user ) ) {
            return $user_id;
        }

        return $user->ID;
    }

    /**
     * WordPress hook: REST authentication errors
     *
     * @param WP_Error|null|bool $result Previous result
     * @return WP_Error|null|bool Authentication result
     * @since 1.0.0
     */
    public static function rest_authentication_errors( $result ) {
        // Skip if already processed
        if ( ! empty( $result ) ) {
            return $result;
        }

        // Check if this is a Care API request
        $request_uri = $_SERVER['REQUEST_URI'] ?? '';
        if ( strpos( $request_uri, '/wp-json/kivicare/v1' ) === false ) {
            return $result;
        }

        // Allow authentication endpoints without token
        $public_endpoints = array(
            '/wp-json/care/v1/auth/login',
            '/wp-json/care/v1/auth/refresh'
        );

        foreach ( $public_endpoints as $endpoint ) {
            if ( strpos( $request_uri, $endpoint ) !== false ) {
                return $result;
            }
        }

        // Require authentication for all other Care endpoints
        if ( ! get_current_user_id() ) {
            return new \WP_Error(
                'rest_not_logged_in',
                'You are not currently logged in.',
                array( 'status' => 401 )
            );
        }

        return $result;
    }

    /**
     * Get authorization header
     *
     * @return string|null Authorization header
     * @since 1.0.0
     */
    private static function get_authorization_header() {
        $headers = array(
            'HTTP_AUTHORIZATION',
            'REDIRECT_HTTP_AUTHORIZATION'
        );

        foreach ( $headers as $header ) {
            if ( ! empty( $_SERVER[ $header ] ) ) {
                return trim( $_SERVER[ $header ] );
            }
        }

        // Check if using PHP-CGI
        if ( function_exists( 'apache_request_headers' ) ) {
            $apache_headers = apache_request_headers();
            if ( isset( $apache_headers['Authorization'] ) ) {
                return trim( $apache_headers['Authorization'] );
            }
        }

        return null;
    }

    /**
     * Add CORS headers for API requests
     *
     * @since 1.0.0
     */
    public static function add_cors_headers() {
        // Allow specific origins (should be configured)
        $allowed_origins = apply_filters( 'care_api_allowed_origins', array() );
        
        if ( ! empty( $allowed_origins ) ) {
            $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' );
        header( 'Access-Control-Allow-Headers: Authorization, Content-Type, X-WP-Nonce' );
        header( 'Access-Control-Allow-Credentials: true' );
    }
}