<?php

namespace traits;

trait economic_endpoint_t
{
    private string $api_url = 'https://restapi.e-conomic.com';
    private string $appAccessGrant;
    private string $app_token;
    private string $appAccessGrant2;
    private static bool $reportedMissingRequiredCredentials = false;
    private static bool $reportedMissingGrant2 = false;

    public function __construct()
    {
        global $ECONOMIC_API;
        $this->app_token = (string)($ECONOMIC_API['app_secret_token'] ?? '');
        $this->appAccessGrant = (string)($ECONOMIC_API['app_access_grant'] ?? '');
        $this->appAccessGrant2 = (string)($ECONOMIC_API['app_access_grant2'] ?? '');
    }

    protected function resolve_app_secret_token(): string
    {
        $appSecretToken = trim($this->app_token);
        if ($appSecretToken === '') {
            if (!self::$reportedMissingRequiredCredentials) {
                error_log('[economic_endpoint_t] Missing ECONOMIC_API_APP_SECRET_TOKEN. e-conomic requests will fail until configuration is fixed.');
                self::$reportedMissingRequiredCredentials = true;
            }
            throw new \RuntimeException('Missing e-conomic app secret token. Set ECONOMIC_API_APP_SECRET_TOKEN and recreate php containers.');
        }
        return $appSecretToken;
    }

    protected function resolve_agreement_grant_token(bool $authToken2): string
    {
        $primaryGrant = trim($this->appAccessGrant);
        $secondaryGrant = trim($this->appAccessGrant2);

        if ($primaryGrant === '') {
            if (!self::$reportedMissingRequiredCredentials) {
                error_log('[economic_endpoint_t] Missing ECONOMIC_API_APP_ACCESS_GRANT. e-conomic requests will fail until configuration is fixed.');
                self::$reportedMissingRequiredCredentials = true;
            }
            throw new \RuntimeException('Missing e-conomic agreement grant token. Set ECONOMIC_API_APP_ACCESS_GRANT and recreate php containers.');
        }

        if ($authToken2 && $secondaryGrant !== '') {
            return $secondaryGrant;
        }

        if ($authToken2 && $secondaryGrant === '' && !self::$reportedMissingGrant2) {
            error_log('[economic_endpoint_t] ECONOMIC_API_APP_ACCESS_GRANT2 is missing. Falling back to ECONOMIC_API_APP_ACCESS_GRANT.');
            self::$reportedMissingGrant2 = true;
        }

        return $primaryGrant;
    }

    /**
     * Filters for e-conomic requests
     * @param array $filters
     * @return string The filters for the request (e.g. 'customerNumber$eq:12345678')
     * @example ['customer.customerNumber' => '12345678']
     */
    public static function filters(array $filters): string
    {
        $filter_string = '';
        foreach ( $filters as $key => $value ) {
            // If the filter string is not empty, and the key has a value, add an $and
            if (!empty($filter_string) && !empty($value)) {
                $filter_string .= '$and:';
            }
            // Check if there's a "$" in the key, and if not, add it as $eq (equals) by default
            if (!str_contains($value, '$') && !empty($value)) {
                $value = '$eq:' . $value;
            }
            $filter_string .= $key . $value;
        }
        return $filter_string;
    }

    /**
     * Pagination for e-conomic requests
     * @param array $pagination
     * @return string The pagination for the request (e.g. '&maxPageSize=100&skipPages=0')
     * @example ['maxPageSize' => 100, 'skipPages' => 0]
     */
    public static function pagination(array $pagination): string
    {
        $pagination_string = '';
        foreach ( $pagination as $key => $value ) {
            $pagination_string .= '&' . $key . '=' . $value;
        }
        return $pagination_string;
    }

    protected function build_request_url_with_base_url(string $base_url, string $url): string
    {
        $path = '/' . ltrim($url, '/');
        $query_separator_position = strpos($path, '?');

        if ($query_separator_position === false) {
            return $base_url . $path;
        }

        $path_without_query = substr($path, 0, $query_separator_position);
        $query_string = substr($path, $query_separator_position + 1);

        if ($query_string === '') {
            return $base_url . $path_without_query;
        }

        $normalized_pairs = [];
        foreach (explode('&', $query_string) as $pair) {
            if ($pair === '') {
                continue;
            }

            $parts = explode('=', $pair, 2);
            if (count($parts) === 1) {
                $normalized_pairs[] = rawurlencode(rawurldecode($parts[0]));
                continue;
            }

            $normalized_pairs[] = rawurlencode(rawurldecode($parts[0]))
                . '='
                . rawurlencode(rawurldecode($parts[1]));
        }

        return $base_url . $path_without_query . '?' . implode('&', $normalized_pairs);
    }

    protected function build_request_url(string $url): string
    {
        return $this->build_request_url_with_base_url($this->api_url, $url);
    }

    protected function create_curl_handle_for_base_url(string $base_url, string $url, string $method, string $data = '', bool $authToken2 = false)
    {
        $appSecretToken = $this->resolve_app_secret_token();
        $agreementGrantToken = $this->resolve_agreement_grant_token($authToken2);

        $curl = curl_init();
        curl_setopt_array($curl, array(
            CURLOPT_URL => $this->build_request_url_with_base_url($base_url, $url),
            CURLOPT_RETURNTRANSFER => true,
            //CURLOPT_ENCODING => '',
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_CONNECTTIMEOUT => 3,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2TLS,
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_HTTPHEADER => array(
                'X-AppSecretToken: ' . $appSecretToken,
                'X-AgreementGrantToken: ' . $agreementGrantToken,
                'Content-Type: application/json'
            ),
        ));

        if ($method === 'POST') {
            curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        }

        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);

        return $curl;
    }

    protected function create_curl_handle(string $url, string $method, string $data = '', bool $authToken2 = false)
    {
        return $this->create_curl_handle_for_base_url($this->api_url, $url, $method, $data, $authToken2);
    }

    /**
     * Send a request to the Economic API
     * @param string $url
     * @param string $method
     * @param string $data
     * @param bool $authToken2
     * @return string
     */
    public function send_request($url, $method, $data = '', bool $authToken2 = false): string
    {
        $curl = $this->create_curl_handle($url, $method, $data, $authToken2);
        $response = curl_exec($curl);
        // Check for errors
        if ($response === false) {
            $error = curl_error($curl);
            curl_close($curl);
            throw new \RuntimeException('Curl error: ' . $error);
        }
        if (curl_errno($curl)) {
            $error = curl_error($curl);
            curl_close($curl);
            throw new \RuntimeException('Curl error: ' . $error);
        }
        $httpStatusCode = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
        curl_close($curl);
        return $this->assert_successful_response($httpStatusCode, $response);
    }

    public function send_file_download_request($url, $method, $data = '', bool $authToken2 = false, $outputFile = null)
    {
        // Initialize cURL session
        $curl = $this->create_curl_handle($url, $method, $data, $authToken2);

        // Execute the request
        $response = curl_exec($curl);

        // Handle errors
        if (curl_errno($curl)) {
            $error = curl_error($curl);
            curl_close($curl);
            throw new \RuntimeException('Curl error: ' . $error);
        }

        // Close the cURL session
        curl_close($curl);

        // If a file path is provided, save the response to the file
        if ($outputFile) {
            // Write file contents
            file_put_contents($outputFile, $response);
            return "File saved to: " . $outputFile;
        }

        // Otherwise, return the raw content (e.g., for inline use)
        return $response;
    }

    /**
     * Validate e-conomic HTTP responses and convert upstream errors into deterministic runtime exceptions.
     */
    protected function assert_successful_response(int $httpStatusCode, string|false $response): string
    {
        if ($response === false) {
            throw new \RuntimeException('e-conomic request failed without a response body.');
        }

        if ($httpStatusCode >= 400) {
            throw new \RuntimeException($this->format_upstream_error_message($httpStatusCode, $response));
        }

        return $response;
    }

    /**
     * Build a sanitized error message from a non-2xx e-conomic response body.
     */
    protected function format_upstream_error_message(int $httpStatusCode, string $response): string
    {
        $prefix = 'e-conomic request failed with HTTP ' . $httpStatusCode;
        $decoded = json_decode($response, true);

        if (!is_array($decoded)) {
            return $prefix . '.';
        }

        $message = isset($decoded['message']) && is_string($decoded['message'])
            ? trim($decoded['message'])
            : 'Upstream e-conomic error';

        $details = [];

        if (isset($decoded['errors']) && is_array($decoded['errors'])) {
            $safeErrors = [];
            foreach ( $decoded['errors'] as $error ) {
                if (is_scalar($error)) {
                    $safeErrors[] = (string)$error;
                }
            }
            if (!empty($safeErrors)) {
                $details['errors'] = $safeErrors;
            }
        }

        if (isset($decoded['logId']) && is_scalar($decoded['logId'])) {
            $details['logId'] = (string)$decoded['logId'];
        }

        if (isset($decoded['httpStatusCode']) && is_numeric($decoded['httpStatusCode'])) {
            $details['httpStatusCode'] = (int)$decoded['httpStatusCode'];
        }

        if (empty($details)) {
            return $prefix . ': ' . $message;
        }

        return $prefix
            . ': '
            . $message
            . ' | details='
            . json_encode($details, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    }
}
