<?php
/**
 * Shared Dolibarr proxy logic (AJAX + REST).
 *
 * @package DoliTrainingManagement
 */

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

/**
 * Validates inputs and forwards to Doli_Training_Management_Dolibarr_Client.
 */
class Doli_Training_Management_Proxy {

    /**
     * Dolibarr client.
     *
     * @var Doli_Training_Management_Dolibarr_Client
     */
    private $client;

    /**
     * Allowlisted constant names.
     *
     * @var string[]
     */
    private $allowed_constants = array(
        'TRAININGMANAGEMENT_CATALOG_NAVIGATION_FILTER_CAT_ID',
        'TRAININGMANAGEMENT_CATALOG_ONLINE_FILTER_CAT_ID',
        'TRAININGMANAGEMENT_MODALITY_AND_ACCESS',
        'TRAININGMANAGEMENT_ACCESSIBILITY',
        'TRAININGMANAGEMENT_CONTACT_INFORMATION',
        'TRAININGMANAGEMENT_SERVICE_CATEGORY',
        'TRAININGMANAGEMENT_TRAINER_CATEGORY',
        'TRAININGMANAGEMENT_DURATION',
    );

    /**
     * Constructor.
     *
     * @param Doli_Training_Management_Dolibarr_Client $client Client.
     */
    public function __construct($client) {
        $this->client = $client;
    }

    /**
     * Allowlisted constant names (read-only).
     *
     * @return string[]
     */
    public function get_allowed_constants() {
        return $this->allowed_constants;
    }

    /**
     * GET trainingmanagement/getconstant.
     *
     * @param string $name Constant name.
     * @return mixed|\WP_Error
     */
    public function get_constant($name) {
        $name = (string) $name;
        if (!in_array($name, $this->allowed_constants, true)) {
            return new WP_Error(
                'doli_training_management_invalid_constant',
                __('Invalid constant name.', 'doli-training-management'),
                array('status' => 400)
            );
        }

        $remote = $this->client->request_json('GET', 'trainingmanagement/getconstant', array('name' => $name));
        if (is_wp_error($remote)) {
            return $remote;
        }

        return $this->normalize_constant_response($name, $remote);
    }

    /**
     * GET categories.
     *
     * @param int $type  Category type.
     * @param int $limit Limit.
     * @return mixed|\WP_Error
     */
    public function get_categories($type, $limit) {
        return $this->client->request_json(
            'GET',
            'categories',
            array(
                'type'  => (int) $type,
                'limit' => (int) $limit,
            )
        );
    }

    /**
     * GET catalog navigation bootstrap (constants for nav/filters + categories with photos).
     *
     * @return mixed|\WP_Error
     */
    public function get_navigation_bootstrap() {
        return $this->client->request_json('GET', 'trainingmanagement/catalog/navigation-bootstrap');
    }

    /**
     * GET documents for a category (list).
     *
     * @param int $category_id Category rowid.
     * @return mixed|\WP_Error
     */
    public function get_category_documents_list($category_id) {
        if ($category_id <= 0) {
            return new WP_Error(
                'doli_training_management_invalid_category',
                __('Invalid category id.', 'doli-training-management'),
                array('status' => 400)
            );
        }

        $result = $this->client->request_json(
            'GET',
            'documents',
            array(
                'modulepart' => 'category',
                'id'         => (int) $category_id,
            )
        );

        return $result;
    }

    /**
     * GET documents/download for category file (binary from Dolibarr → base64 JSON for the app).
     *
     * @param string $original_file Relative path for Dolibarr API.
     * @return array|\WP_Error Shape matches Angular DocumentFile.
     */
    public function get_category_document_download($original_file) {
        $original_file = $this->sanitize_relative_storage_path($original_file);
        if ('' === $original_file) {
            return new WP_Error(
                'doli_training_management_missing_file',
                __('Missing original_file.', 'doli-training-management'),
                array('status' => 400)
            );
        }

        $result = $this->client->request_raw(
            'GET',
            'documents/download',
            array(
                'modulepart'    => 'category',
                'original_file' => $original_file,
            )
        );

        return $this->build_document_file_payload_from_download_response(
            $result,
            basename(str_replace('\\', '/', $original_file))
        );
    }

    /**
     * GET documents for a training-related object (list).
     *
     * @param int    $object_id   Training row id (or linked product id when using modulepart product/service).
     * @param string $modulepart  Allowlisted Dolibarr modulepart (trainingmanagement, training, product, service).
     * @return mixed|\WP_Error
     */
    public function get_training_documents_list($object_id, $modulepart) {
        $modulepart = $this->sanitize_training_document_modulepart($modulepart);
        if ('' === $modulepart) {
            return new WP_Error(
                'doli_training_management_invalid_modulepart',
                __('Invalid training document modulepart.', 'doli-training-management'),
                array('status' => 400)
            );
        }

        if ($object_id <= 0) {
            return new WP_Error(
                'doli_training_management_invalid_training',
                __('Invalid training id.', 'doli-training-management'),
                array('status' => 400)
            );
        }

        return $this->client->request_json(
            'GET',
            'documents',
            array(
                'modulepart' => $modulepart,
                'id'         => (int) $object_id,
            )
        );
    }

    /**
     * GET documents/download for a training-related file (same JSON shape as category download).
     *
     * @param string $modulepart    Allowlisted modulepart.
     * @param string $original_file Relative storage path for Dolibarr API.
     * @return array|\WP_Error
     */
    public function get_training_document_download($modulepart, $original_file) {
        $modulepart = $this->sanitize_training_document_modulepart($modulepart);
        if ('' === $modulepart) {
            return new WP_Error(
                'doli_training_management_invalid_modulepart',
                __('Invalid training document modulepart.', 'doli-training-management'),
                array('status' => 400)
            );
        }

        $original_file = $this->sanitize_relative_storage_path($original_file);
        if ('' === $original_file) {
            return new WP_Error(
                'doli_training_management_missing_file',
                __('Missing original_file.', 'doli-training-management'),
                array('status' => 400)
            );
        }

        $result = $this->client->request_raw(
            'GET',
            'documents/download',
            array(
                'modulepart'    => $modulepart,
                'original_file' => $original_file,
            )
        );

        return $this->build_document_file_payload_from_download_response(
            $result,
            basename(str_replace('\\', '/', $original_file))
        );
    }

    /**
     * Allowlisted modulepart values for training program / attached files proxy.
     *
     * @param string $modulepart Raw input.
     * @return string Normalized modulepart or empty string if invalid.
     */
    private function sanitize_training_document_modulepart($modulepart) {
        $modulepart = strtolower(trim((string) $modulepart));
        $allowed    = array('trainingmanagement', 'training', 'product', 'service');

        return in_array($modulepart, $allowed, true) ? $modulepart : '';
    }

    /**
     * Build Angular DocumentFile array from Dolibarr documents/download response.
     *
     * @param array|\WP_Error $result          Result from request_raw.
     * @param string          $fallback_basename Filename if not in JSON wrapper.
     * @return array|\WP_Error
     */
    private function build_document_file_payload_from_download_response($result, $fallback_basename) {
        if (is_wp_error($result)) {
            return $result;
        }

        $headers      = $result['headers'];
        $raw          = $result['body'];
        $content_type = 'application/octet-stream';
        if (is_array($headers)) {
            if (!empty($headers['content-type'])) {
                $content_type = (string) $headers['content-type'];
            }
        } elseif (is_object($headers) && isset($headers['content-type'])) {
            $content_type = (string) $headers['content-type'];
        }

        $filename = $fallback_basename;

        // Some Dolibarr setups return a JSON payload with base64 content instead of raw binary.
        // In that case, avoid double-encoding the whole JSON object.
        $decoded = json_decode((string) $raw, true);
        if (is_array($decoded) && !empty($decoded['content']) && is_string($decoded['content'])) {
            $jsonFilename = isset($decoded['filename']) && is_string($decoded['filename']) ? $decoded['filename'] : $filename;
            $jsonType     = isset($decoded['content-type']) && is_string($decoded['content-type']) ? $decoded['content-type'] : $content_type;
            $jsonEncoding = isset($decoded['encoding']) && is_string($decoded['encoding']) ? $decoded['encoding'] : 'base64';
            $jsonFileSize = isset($decoded['filesize']) ? (int) $decoded['filesize'] : 0;

            return array(
                'filename'     => $jsonFilename,
                'content-type' => $jsonType,
                'filesize'     => $jsonFileSize,
                'content'      => trim($decoded['content']),
                'encoding'     => $jsonEncoding,
            );
        }

        return array(
            'filename'     => $filename,
            'content-type' => $content_type,
            'filesize'     => strlen($raw),
            'content'      => base64_encode($raw),
            'encoding'     => 'base64',
        );
    }

    /**
     * GET products.
     *
     * @param int    $limit    Limit.
     * @param string $sort     Sort field.
     * @param string $order    ASC|DESC.
     * @param int    $category Category filter.
     * @return mixed|\WP_Error
     */
    public function get_products($limit, $sort, $order, $category) {
        $safe_order = in_array(strtoupper($order), array('ASC', 'DESC'), true) ? strtoupper($order) : 'ASC';

        return $this->client->request_json(
            'GET',
            'products',
            array(
                'limit'    => (int) $limit,
                'sort'     => (string) $sort,
                'order'    => $safe_order,
                'category' => (int) $category,
            )
        );
    }

    /**
     * GET trainings by category.
     *
     * @param int $category_id Category id.
     * @return mixed|\WP_Error
     */
    public function get_trainings_by_category($category_id) {
        if ($category_id <= 0) {
            return new WP_Error(
                'doli_training_management_invalid_category',
                __('Invalid category id.', 'doli-training-management'),
                array('status' => 400)
            );
        }

        return $this->client->request_json('GET', 'trainingmanagement/trainings/bycategory/' . (int) $category_id);
    }

    /**
     * GET full catalog trainings bootstrap (sessions + inline images + program metadata) for navigation root category.
     *
     * @param int $root_navigation_category_id Root navigation category id (catalog scope).
     * @return mixed|\WP_Error
     */
    public function get_trainings_catalog_bootstrap($root_navigation_category_id) {
        if ($root_navigation_category_id <= 0) {
            return new WP_Error(
                'doli_training_management_invalid_category',
                __('Invalid root navigation category id.', 'doli-training-management'),
                array('status' => 400)
            );
        }

        return $this->client->request_json(
            'GET',
            'trainingmanagement/catalog/trainings-bootstrap',
            array(
                'root_navigation_category_id' => (int) $root_navigation_category_id,
            )
        );
    }

    /**
     * GET a single training details payload.
     *
     * @param int $training_id Training id.
     * @return mixed|\WP_Error
     */
    public function get_training($training_id) {
        if ($training_id <= 0) {
            return new WP_Error(
                'doli_training_management_invalid_training',
                __('Invalid training id.', 'doli-training-management'),
                array('status' => 400)
            );
        }

        return $this->client->request_json('GET', 'trainingmanagement/trainings/' . (int) $training_id);
    }

    /**
     * GET envisaged sessions.
     *
     * @param int $training_id Training id.
     * @return mixed|\WP_Error
     */
    public function get_training_sessions($training_id) {
        if ($training_id <= 0) {
            return new WP_Error(
                'doli_training_management_invalid_training',
                __('Invalid training id.', 'doli-training-management'),
                array('status' => 400)
            );
        }

        return $this->client->request_json(
            'GET',
            'trainingmanagement/trainings/' . (int) $training_id . '/sessions/envisagees'
        );
    }

    /**
     * Raw binary training image download.
     *
     * @param int $training_id Training id.
     * @return array|\WP_Error Keys: body, content_type.
     */
    public function get_training_image_download_raw($training_id) {
        if ($training_id <= 0) {
            return new WP_Error(
                'doli_training_management_invalid_training',
                __('Invalid training id.', 'doli-training-management'),
                array('status' => 400)
            );
        }

        return $this->client->request_raw(
            'GET',
            'trainingmanagement/trainings/' . (int) $training_id . '/image/download'
        );
    }

    /**
     * GET training image JSON metadata.
     *
     * @param int $training_id Training id.
     * @return mixed|\WP_Error
     */
    public function get_training_image_meta($training_id) {
        if ($training_id <= 0) {
            return new WP_Error(
                'doli_training_management_invalid_training',
                __('Invalid training id.', 'doli-training-management'),
                array('status' => 400)
            );
        }

        return $this->client->request_json(
            'GET',
            'trainingmanagement/trainings/' . (int) $training_id . '/image'
        );
    }

    /**
     * POST inquiry.
     *
     * @param array $payload Sanitized payload.
     * @return mixed|\WP_Error
     */
    public function create_inquiry($payload) {
        if (empty($payload['training_id']) || empty($payload['visitor_email']) || empty($payload['message'])) {
            return new WP_Error(
                'doli_training_management_inquiry_fields',
                __('Missing required inquiry fields.', 'doli-training-management'),
                array('status' => 400)
            );
        }

        return $this->client->request_json('POST', 'trainingmanagement/inquiries', array(), $payload);
    }

    /**
     * Build a stable { name, value } object for the Angular client.
     *
     * @param string $name    Allowlisted constant name.
     * @param mixed  $decoded JSON-decoded Dolibarr body.
     * @return array{name:string,value:string}
     */
    private function normalize_constant_response($name, $decoded) {
        if (is_array($decoded) && array_key_exists('value', $decoded)) {
            $value = $decoded['value'];
            $out   = '';
            if (is_string($value) || is_numeric($value)) {
                $out = (string) $value;
            }

            return array(
                'name'  => isset($decoded['name']) && is_string($decoded['name']) ? $decoded['name'] : $name,
                'value' => $out,
            );
        }

        if (is_string($decoded) || is_numeric($decoded)) {
            return array(
                'name'  => $name,
                'value' => (string) $decoded,
            );
        }

        return array(
            'name'  => $name,
            'value' => '',
        );
    }

    /**
     * Dolibarr original_file relative path: allow safe segments only (no directory traversal).
     *
     * @param string $path Raw path.
     * @return string
     */
    private function sanitize_relative_storage_path($path) {
        $path = wp_unslash((string) $path);
        if ('' === $path || false !== strpos($path, '..')) {
            return '';
        }
        // Allow typical Dolibarr paths: digits/slashes/letters/underscore/hyphen/dot.
        if (!preg_match('/^[a-zA-Z0-9_.\/-]+$/', $path)) {
            return '';
        }

        return $path;
    }
}
