<?php
/**
 * CloudApi.php
 *
 * @since      1.0.0
 * @author     alfiopiccione <alfio.piccione@gmail.com>
 * @license    http://opensource.org/licenses/gpl-2.0.php GPL v2
 *
 * Copyright (C) 2025 POP
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

namespace WcElectronInvoice\Functions;

use WcElectronInvoice\Providers\OrderQuery;
use WcElectronInvoice\Xml\CreateXml;

if (! defined('ABSPATH')) {
    exit; // Exit if accessed directly.
}

/**
 * Parse POP API error body as associative array.
 *
 * @param mixed $body
 *
 * @return array|null
 */
function parsePopApiErrorBody($body): ?array
{
    if (! is_string($body) || '' === trim($body)) {
        return null;
    }

    $decoded = json_decode($body, true);
    if (! is_array($decoded)) {
        return null;
    }

    return $decoded;
}

/**
 * Safely decode JSON body and preserve decode error details.
 *
 * @param mixed $body
 *
 * @return array{ok:bool,data:array|null,error:string}
 */
function parsePopApiJsonBody($body): array
{
    if (! is_string($body)) {
        return [
            'ok'    => false,
            'data'  => null,
            'error' => 'json_decode_input_not_string',
        ];
    }

    if ('' === trim($body)) {
        return [
            'ok'    => false,
            'data'  => null,
            'error' => 'json_decode_input_empty',
        ];
    }

    $decoded = json_decode($body, true);
    if (! is_array($decoded) || JSON_ERROR_NONE !== json_last_error()) {
        return [
            'ok'    => false,
            'data'  => null,
            'error' => json_last_error_msg(),
        ];
    }

    return [
        'ok'    => true,
        'data'  => $decoded,
        'error' => '',
    ];
}

/**
 * Return a redacted version of request headers safe for debug logs.
 *
 * @param array $headers
 *
 * @return array
 */
function sanitizePopApiDebugHeaders(array $headers): array
{
    $sanitized = [];

    foreach ($headers as $key => $value) {
        $normalizedKey = strtolower((string)$key);
        if (in_array($normalizedKey, ['authorization', 'cookie', 'x-api-key'], true)) {
            $sanitized[$key] = '[redacted]';
            continue;
        }

        $sanitized[$key] = is_scalar($value) ? (string)$value : '[non-scalar]';
    }

    return $sanitized;
}

/**
 * Build compact debug context for POP API failures.
 *
 * @param string     $url
 * @param array      $args
 * @param mixed      $response
 * @param int        $code
 * @param mixed      $body
 * @param int        $attempt
 * @param int        $maxAttempts
 * @param mixed      $objID
 * @param mixed      $provider
 * @param bool       $integrationUse
 *
 * @return string
 */
function buildPopApiFailureDebugContext(
    string $url,
    array $args,
    $response,
    int $code,
    $body,
    int $attempt,
    int $maxAttempts,
    $objID,
    $provider,
    bool $integrationUse
): string {
    $context = [
        'url'             => $url,
        'method'          => isset($args['method']) ? (string)$args['method'] : 'POST',
        'timeout'         => isset($args['timeout']) ? (float)$args['timeout'] : null,
        'redirection'     => isset($args['redirection']) ? (int)$args['redirection'] : null,
        'sslverify'       => array_key_exists('sslverify', $args) ? (bool)$args['sslverify'] : null,
        'headers'         => sanitizePopApiDebugHeaders(isset($args['headers']) && is_array($args['headers']) ? $args['headers'] : []),
        'body_length'     => isset($args['body']) && is_string($args['body']) ? strlen($args['body']) : 0,
        'response_code'   => $code,
        'response_length' => is_string($body) ? strlen($body) : 0,
        'attempt'         => $attempt,
        'max_attempts'    => $maxAttempts,
        'integration'     => $integrationUse,
        'object_id'       => $objID,
        'provider'        => $provider,
    ];

    if (is_wp_error($response)) {
        $context['wp_error_code'] = $response->get_error_code();
        $context['wp_error_message'] = $response->get_error_message();
        $context['wp_error_data'] = $response->get_error_data();
    }

    $encoded = wp_json_encode($context);

    return false !== $encoded ? $encoded : 'Unable to encode POP API debug context';
}

/**
 * Build an explicit email subject for POP API failures.
 *
 * @param mixed  $response
 * @param string $url
 * @param mixed  $provider
 * @param mixed  $objID
 *
 * @return string
 */
function buildPopApiFailureEmailSubject($response, string $url, $provider, $objID): string
{
    $label = 'POP Api error';

    if (is_wp_error($response)) {
        $message = strtolower((string)$response->get_error_message());
        if (false !== strpos($message, 'curl error 28') || false !== strpos($message, 'timed out')) {
            $label = 'POP Api timeout';
        } else {
            $label = 'POP Api WP_Error';
        }
    }

    $path = (string)wp_parse_url($url, PHP_URL_PATH);
    $path = trim($path, '/');
    $path = '' !== $path ? $path : 'unknown-endpoint';
    $provider = ! empty($provider) ? (string)$provider : 'unknown-provider';
    $objID = ! empty($objID) ? (string)$objID : 'n/a';

    return sprintf('%s [%s] order:%s endpoint:%s', $label, $provider, $objID, $path);
}

/**
 * Check if an API error payload represents a quota exceeded response.
 *
 * @param int        $httpCode
 * @param array|null $payload
 *
 * @return bool
 */
function isPopApiQuotaExceededError(int $httpCode, ?array $payload): bool
{
    if (! is_array($payload)) {
        return false;
    }

    $apiCode    = isset($payload['code']) ? (string)$payload['code'] : '';
    $innerStatus = isset($payload['data']['status']) ? (int)$payload['data']['status'] : 0;

    return 'api_quota_exceeded' === $apiCode || 429 === $httpCode || 429 === $innerStatus;
}

/**
 * Return the most relevant payload data node for POP API errors.
 *
 * @param array|null $payload
 *
 * @return array
 */
function getPopApiErrorData(?array $payload): array
{
    if (! is_array($payload)) {
        return [];
    }

    if (isset($payload['data']) && is_array($payload['data'])) {
        return $payload['data'];
    }

    return $payload;
}

/**
 * Return a nested array value using dot notation.
 *
 * @param array  $data
 * @param string $path
 * @param mixed  $default
 *
 * @return mixed
 */
function getPopApiNestedValue(array $data, string $path, $default = null)
{
    $segments = explode('.', $path);
    $value    = $data;

    foreach ($segments as $segment) {
        if (! is_array($value) || ! array_key_exists($segment, $value)) {
            return $default;
        }

        $value = $value[$segment];
    }

    return $value;
}

/**
 * Check if a POP API error payload represents an overquota block.
 *
 * @param int        $httpCode
 * @param array|null $payload
 *
 * @return bool
 */
function isPopApiOverquotaError(int $httpCode, ?array $payload): bool
{
    if (! is_array($payload)) {
        return false;
    }

    $data       = getPopApiErrorData($payload);
    $apiCode    = isset($payload['code']) ? (string)$payload['code'] : ((isset($data['code']) ? (string)$data['code'] : ''));
    $status     = isset($data['status']) ? (int)$data['status'] : (isset($payload['status']) ? (int)$payload['status'] : $httpCode);
    $reasonCode = isset($data['reason_code']) ? (string)$data['reason_code'] : (isset($payload['reason_code']) ? (string)$payload['reason_code'] : '');
    $hasQuota   = isset($data['quota']) && is_array($data['quota']);
    $hasDetails = array_key_exists('overquota_enabled', $data) ||
        array_key_exists('monthly_cap_eur', $data) ||
        array_key_exists('overquota_spent_eur', $data) ||
        array_key_exists('projected_overquota_eur', $data) ||
        array_key_exists('overquota_cycle_end_date', $data) ||
        array_key_exists('next_charge_date', $data);
    $hasGuardrailDebug = is_array(getPopApiNestedValue($data, 'debug.guardrail', []));

    $knownReasonCodes = [
        'quota_limit_reached',
        'overquota_disabled',
        'overquota_cap_reached',
        'overquota_no_payment_method',
        'overquota_unpaid_older_than_30d',
        'overquota_guardrail_unavailable',
    ];

    if (in_array($apiCode, ['api_quota_exceeded', 'api_overquota_guardrail_unavailable'], true)) {
        return true;
    }

    if (in_array($reasonCode, $knownReasonCodes, true)) {
        return true;
    }

    if (429 === $httpCode || 429 === $status) {
        return $hasQuota || $hasDetails || '' !== $reasonCode;
    }

    if (503 === $httpCode || 503 === $status) {
        return 'overquota_guardrail_unavailable' === $reasonCode || $hasGuardrailDebug || $hasDetails;
    }

    return false;
}

/**
 * Extract quota usage details from payload.
 *
 * @param array|null $payload
 *
 * @return array{user_id:int|null,quota_used:int|null,quota_limit:int|null}
 */
function extractPopApiQuotaDetails(?array $payload): array
{
    $data       = getPopApiErrorData($payload);
    $quota      = isset($data['quota']) && is_array($data['quota']) ? $data['quota'] : [];
    $userId     = isset($data['user_id']) ? (int)$data['user_id'] : null;
    $quotaUsed  = isset($quota['quota_used']) ? (int)$quota['quota_used'] : null;
    $quotaLimit = isset($quota['quota_limit']) ? (int)$quota['quota_limit'] : null;

    return [
        'user_id'     => $userId,
        'quota_used'  => $quotaUsed,
        'quota_limit' => $quotaLimit,
    ];
}

/**
 * Build a readable quota exceeded message for browser and admin email.
 *
 * @param array{user_id:int|null,quota_used:int|null,quota_limit:int|null} $details
 *
 * @return string
 */
function buildPopApiQuotaExceededMessage(array $details = []): string
{
    $used  = isset($details['quota_used']) && null !== $details['quota_used'] ? (int)$details['quota_used'] : null;
    $limit = isset($details['quota_limit']) && null !== $details['quota_limit'] ? (int)$details['quota_limit'] : null;

    $line1 = __('Quota exceeded for your free plan. You reached your plan limit.', WC_EL_INV_TEXTDOMAIN);
    if (null !== $used && null !== $limit) {
        $line1 = sprintf(
            __('Quota exceeded for your free plan (%1$d/%2$d). You reached your plan limit.', WC_EL_INV_TEXTDOMAIN),
            $used,
            $limit
        );
    }

    $line2 = __('How can I increase the quota? Upgrade your plan from POP > My Account or POP > Pricing.', WC_EL_INV_TEXTDOMAIN);
    $line3 = __('Already upgraded and still seeing this message? In WordPress go to POP and click "Refresh license info".', WC_EL_INV_TEXTDOMAIN);

    return implode("\n", [
        $line1,
        '- ' . $line2,
        '- ' . $line3,
    ]);
}

/**
 * Build HTML message for wp_die quota exceeded output.
 *
 * @param array{user_id:int|null,quota_used:int|null,quota_limit:int|null} $details
 *
 * @return string
 */
function buildPopApiQuotaExceededMessageHtml(array $details = []): string
{
    $used  = isset($details['quota_used']) && null !== $details['quota_used'] ? (int)$details['quota_used'] : null;
    $limit = isset($details['quota_limit']) && null !== $details['quota_limit'] ? (int)$details['quota_limit'] : null;
    $title = __('You reached your plan limit.', WC_EL_INV_TEXTDOMAIN);

    $paragraph = __('Quota exceeded for your free plan. You reached your plan limit.', WC_EL_INV_TEXTDOMAIN);
    if (null !== $used && null !== $limit) {
        $paragraph = sprintf(
            __('Quota exceeded for your free plan (%1$d/%2$d). You reached your plan limit.', WC_EL_INV_TEXTDOMAIN),
            $used,
            $limit
        );
    }

    $item1 = __('How can I increase the quota? Upgrade your plan from POP > My Account or POP > Pricing.', WC_EL_INV_TEXTDOMAIN);
    $item2 = __('Already upgraded and still seeing this message? In WordPress go to POP and click "Refresh license info".', WC_EL_INV_TEXTDOMAIN);

    return '<p><strong>' . esc_html($title) . '</strong></p>'
        . '<p>' . esc_html($paragraph) . '</p>'
        . '<ul>'
        . '<li>' . esc_html($item1) . '</li>'
        . '<li>' . esc_html($item2) . '</li>'
        . '</ul>';
}

/**
 * Extract normalized overquota-relevant details from the API payload.
 *
 * @param array|null $payload
 *
 * @return array
 */
function extractPopApiOverquotaDetails(?array $payload): array
{
    $data      = getPopApiErrorData($payload);
    $quota     = isset($data['quota']) && is_array($data['quota']) ? $data['quota'] : [];
    $debug     = isset($data['debug']) && is_array($data['debug']) ? $data['debug'] : [];
    $indicators = getPopApiNestedValue($debug, 'guardrail.indicators', []);

    return [
        'overquota_enabled'          => isset($data['overquota_enabled']) ? (bool)$data['overquota_enabled'] : null,
        'monthly_cap_eur'            => isset($data['monthly_cap_eur']) ? (string)$data['monthly_cap_eur'] : null,
        'overquota_spent_eur'        => isset($data['overquota_spent_eur']) ? (string)$data['overquota_spent_eur'] : null,
        'projected_overquota_eur'    => isset($data['projected_overquota_eur']) ? (string)$data['projected_overquota_eur'] : null,
        'overquota_cycle_end_date'   => isset($data['overquota_cycle_end_date']) ? (string)$data['overquota_cycle_end_date'] : null,
        'next_charge_date'           => isset($data['next_charge_date']) ? (string)$data['next_charge_date'] : null,
        'quota_used'                 => isset($quota['quota_used']) ? (int)$quota['quota_used'] : null,
        'quota_limit'                => isset($quota['quota_limit']) ? (int)$quota['quota_limit'] : null,
        'debug'                      => [
            'stage'                         => isset($debug['stage']) ? (string)$debug['stage'] : null,
            'reason_code'                   => isset($debug['reason_code']) ? (string)$debug['reason_code'] : null,
            'guardrail_status'              => (string)getPopApiNestedValue($debug, 'guardrail.status', ''),
            'guardrail_error_code'          => (string)getPopApiNestedValue($debug, 'guardrail.error_code', ''),
            'guardrail_reason_code'         => (string)getPopApiNestedValue($debug, 'guardrail.reason_code', ''),
            'guardrail_technical_reason_code' => (string)getPopApiNestedValue($debug, 'guardrail.technical_reason_code', ''),
            'guardrail_indicators'          => is_array($indicators) ? $indicators : [],
        ],
    ];
}

/**
 * Fallback CTA action from reason code.
 *
 * @param string $reasonCode
 *
 * @return string
 */
function getPopApiOverquotaSuggestedActionFallback(string $reasonCode): string
{
    $map = [
        'quota_limit_reached'             => 'upgrade_plan',
        'overquota_disabled'              => 'enable_overquota',
        'overquota_cap_reached'           => 'increase_cap',
        'overquota_no_payment_method'     => 'update_payment_method',
        'overquota_unpaid_older_than_30d' => 'settle_now',
        'overquota_guardrail_unavailable' => 'retry_later',
    ];

    return isset($map[$reasonCode]) ? $map[$reasonCode] : 'open_billing';
}

/**
 * Build user-facing copy for an overquota block.
 *
 * @param array $normalized
 *
 * @return array{title:string,message:string}
 */
function buildPopApiOverquotaUserMessage(array $normalized): array
{
    $reasonCode      = isset($normalized['reason_code']) ? (string)$normalized['reason_code'] : '';
    $suggestedAction = isset($normalized['suggested_action']) ? (string)$normalized['suggested_action'] : '';

    if ('overquota_disabled' === $reasonCode && 'settle_now' === $suggestedAction) {
        return [
            'title'   => __('Pending billing settlement required', WC_EL_INV_TEXTDOMAIN),
            'message' => __('The action could not be completed because a billing settlement is required before overquota can be used again. Check your account on popapi.io and settle the pending amount to unblock the request.', WC_EL_INV_TEXTDOMAIN),
        ];
    }

    $map = [
        'quota_limit_reached' => [
            'title'   => __('Plan limit reached', WC_EL_INV_TEXTDOMAIN),
            'message' => __('The action could not be completed because the plan credit limit has been reached and overquota is not currently available. Check your account status on popapi.io to continue.', WC_EL_INV_TEXTDOMAIN),
        ],
        'overquota_disabled' => [
            'title'   => __('Overquota disabled', WC_EL_INV_TEXTDOMAIN),
            'message' => __('The action could not be completed because overquota is disabled for this account. Enable overquota on popapi.io to continue.', WC_EL_INV_TEXTDOMAIN),
        ],
        'overquota_cap_reached' => [
            'title'   => __('Monthly overquota cap reached', WC_EL_INV_TEXTDOMAIN),
            'message' => __('The action could not be completed because the overquota cap has been reached. Increase the cap on popapi.io to continue.', WC_EL_INV_TEXTDOMAIN),
        ],
        'overquota_no_payment_method' => [
            'title'   => __('Payment method required', WC_EL_INV_TEXTDOMAIN),
            'message' => __('The action could not be completed because no valid payment method is available for overquota. Update the payment method on popapi.io to continue.', WC_EL_INV_TEXTDOMAIN),
        ],
        'overquota_unpaid_older_than_30d' => [
            'title'   => __('Unpaid overquota charges', WC_EL_INV_TEXTDOMAIN),
            'message' => __('The action could not be completed because there are overdue overquota charges older than 30 days. Settle the pending amount on popapi.io to continue.', WC_EL_INV_TEXTDOMAIN),
        ],
        'overquota_guardrail_unavailable' => [
            'title'   => __('Billing verification unavailable', WC_EL_INV_TEXTDOMAIN),
            'message' => __('The action could not be completed because billing verification is temporarily unavailable. Try again later or contact support if the problem persists.', WC_EL_INV_TEXTDOMAIN),
        ],
    ];

    return isset($map[$reasonCode]) ? $map[$reasonCode] : [
        'title'   => __('POP API limit reached', WC_EL_INV_TEXTDOMAIN),
        'message' => __('The action could not be completed because a POP API billing limit blocked the request. Check your account status on popapi.io to continue.', WC_EL_INV_TEXTDOMAIN),
    ];
}

/**
 * Build CTA metadata for an overquota block.
 *
 * @param string $suggestedAction
 *
 * @return array{action:string,label:string,url:string,breadcrumb:string,step_label:string}
 */
function buildPopApiOverquotaCta(string $suggestedAction): array
{
    $map = [
        'upgrade_plan' => [
            'breadcrumb' => 'popapi.io > prezzi',
        ],
        'enable_overquota' => [
            'breadcrumb' => 'popapi.io > account > piano',
        ],
        'increase_cap' => [
            'breadcrumb' => 'popapi.io > account > piano',
        ],
        'update_payment_method' => [
            'breadcrumb' => 'popapi.io > account > pagamento',
        ],
        'settle_now' => [
            'breadcrumb' => 'popapi.io > account > piano',
        ],
        'open_billing' => [
            'breadcrumb' => 'popapi.io > account > piano',
        ],
        'retry_later' => [
            'label'      => __('Contact support', WC_EL_INV_TEXTDOMAIN),
            'breadcrumb' => 'popapi.io',
        ],
    ];

    $config = isset($map[$suggestedAction]) ? $map[$suggestedAction] : [
        'breadcrumb' => 'popapi.io',
    ];

    return [
        'action'     => $suggestedAction,
        'label'      => isset($config['label']) ? $config['label'] : __('Open POP', WC_EL_INV_TEXTDOMAIN),
        'url'        => 'https://popapi.io/',
        'breadcrumb' => $config['breadcrumb'],
        'step_label' => __('Next step', WC_EL_INV_TEXTDOMAIN),
    ];
}

/**
 * Normalize a POP API overquota error payload to a stable consumer shape.
 *
 * @param int        $httpCode
 * @param array|null $payload
 *
 * @return array|null
 */
function normalizePopApiOverquotaError(int $httpCode, ?array $payload): ?array
{
    if (! isPopApiOverquotaError($httpCode, $payload)) {
        return null;
    }

    $data            = getPopApiErrorData($payload);
    $apiCode         = isset($payload['code']) ? (string)$payload['code'] : ((isset($data['code']) ? (string)$data['code'] : ''));
    $status          = isset($data['status']) ? (int)$data['status'] : (isset($payload['status']) ? (int)$payload['status'] : $httpCode);
    $legacyQuota     = extractPopApiQuotaDetails($payload);
    $details         = extractPopApiOverquotaDetails($payload);
    $reasonCode      = isset($data['reason_code']) ? (string)$data['reason_code'] : (isset($payload['reason_code']) ? (string)$payload['reason_code'] : '');
    $suggestedAction = isset($data['suggested_action']) ? (string)$data['suggested_action'] : '';

    if ('' === $reasonCode && null !== $legacyQuota['quota_limit']) {
        $reasonCode = 'quota_limit_reached';
    }

    if ('' === $suggestedAction) {
        $suggestedAction = getPopApiOverquotaSuggestedActionFallback($reasonCode);
    }

    if ('' === $apiCode) {
        $apiCode = 503 === $status ? 'api_overquota_guardrail_unavailable' : 'api_quota_exceeded';
    }

    $copy       = buildPopApiOverquotaUserMessage([
        'reason_code'      => $reasonCode,
        'suggested_action' => $suggestedAction,
    ]);
    $isTechnical = 503 === $status || 'api_overquota_guardrail_unavailable' === $apiCode || 'overquota_guardrail_unavailable' === $reasonCode;

    return [
        'type'             => 'pop_api_overquota',
        'status'           => $status,
        'code'             => $apiCode,
        'reason_code'      => $reasonCode,
        'suggested_action' => $suggestedAction,
        'retryable'        => $isTechnical,
        'title'            => $copy['title'],
        'message'          => $copy['message'],
        'cta'              => buildPopApiOverquotaCta($suggestedAction),
        'details'          => $details,
        'recorded_at'      => gmdate('c'),
    ];
}

/**
 * Build the consumer-side overquota payload envelope.
 *
 * @param array $normalized
 *
 * @return array
 */
function buildPopApiOverquotaUiPayload(array $normalized): array
{
    return $normalized;
}

/**
 * Extract a normalized overquota payload from mixed raw consumer responses.
 *
 * @param mixed $raw
 *
 * @return array|null
 */
function extractPopApiOverquotaUiPayload($raw): ?array
{
    if (is_string($raw)) {
        $decoded = json_decode($raw, true);
        if (! is_array($decoded)) {
            return null;
        }

        return extractPopApiOverquotaUiPayload($decoded);
    }

    if (is_object($raw)) {
        $raw = json_decode(json_encode($raw), true);
    }

    if (! is_array($raw)) {
        return null;
    }

    if (
        isset($raw['type']) &&
        'pop_api_overquota' === $raw['type']
    ) {
        return $raw;
    }

    if (
        isset($raw['data']) &&
        is_array($raw['data']) &&
        isset($raw['data']['type']) &&
        'pop_api_overquota' === $raw['data']['type']
    ) {
        return $raw['data'];
    }

    if (
        isset($raw['errors']) &&
        is_array($raw['errors']) &&
        isset($raw['error_data']) &&
        is_array($raw['error_data'])
    ) {
        $errorCode = '';
        foreach (array_keys($raw['errors']) as $candidateCode) {
            if (isset($raw['error_data'][$candidateCode]) && is_array($raw['error_data'][$candidateCode])) {
                $errorCode = (string)$candidateCode;
                break;
            }
        }

        if ('' !== $errorCode) {
            $errorData = $raw['error_data'][$errorCode];
            $status    = isset($errorData['status']) ? (int)$errorData['status'] : 0;
            $normalized = normalizePopApiOverquotaError($status, [
                'code' => $errorCode,
                'data' => $errorData,
            ]);
            if (is_array($normalized)) {
                return $normalized;
            }
        }
    }

    return null;
}

/**
 * Build a technical message for logs and admin notifications.
 *
 * @param array $payload
 *
 * @return string
 */
function buildPopApiOverquotaTechnicalMessage(array $payload): string
{
    $lines = [
        'POP API overquota block',
        'status: ' . (string)($payload['status'] ?? ''),
        'code: ' . (string)($payload['code'] ?? ''),
        'reason_code: ' . (string)($payload['reason_code'] ?? ''),
        'suggested_action: ' . (string)($payload['suggested_action'] ?? ''),
        'message: ' . (string)($payload['message'] ?? ''),
    ];

    $technicalReason = getPopApiNestedValue(isset($payload['details']) && is_array($payload['details']) ? $payload['details'] : [], 'debug.guardrail_technical_reason_code', '');
    if ('' !== $technicalReason) {
        $lines[] = 'technical_reason_code: ' . $technicalReason;
    }

    return implode("\n", $lines);
}

/**
 * Build an HTML fallback page for document endpoints blocked by overquota.
 *
 * @param array $payload
 *
 * @return string
 */
function buildPopApiOverquotaHtmlFallback(array $payload): string
{
    $title      = isset($payload['title']) ? (string)$payload['title'] : 'POP API limit reached';
    $message    = isset($payload['message']) ? (string)$payload['message'] : 'The request could not be completed.';
    $ctaLabel   = isset($payload['cta']['label']) ? (string)$payload['cta']['label'] : 'Open POP';
    $ctaUrl     = isset($payload['cta']['url']) ? (string)$payload['cta']['url'] : 'https://popapi.io/';
    $breadcrumb = isset($payload['cta']['breadcrumb']) ? (string)$payload['cta']['breadcrumb'] : 'popapi.io';
    $logoUrl    = defined('WC_EL_INV_URL') ? WC_EL_INV_URL . 'assets/images/pop.svg' : '';
    $closeLabel = __('Close tab', WC_EL_INV_TEXTDOMAIN);
    $closeHelp  = __('If the tab does not close automatically, you can close it manually.', WC_EL_INV_TEXTDOMAIN);

    // @codingStandardsIgnoreLine
    return '<!DOCTYPE html><html><head><meta charset="utf-8"><title>' . esc_html($title) . '</title>
            <style>
            body {
                background: #f6f7f7;
                margin: 0;
                padding: 24px;
            }
            .overquota_html_wrap {
                max-width: 760px;
                margin: 40px auto;
                padding: 30px 30px 55px;
                font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif;
                line-height: 1.5;
                color: #1d2327;
                border: 1px solid #ccc;
                border-radius: 5px;
            }
            .overquota_html_logo {
                display:block;
                width:120px;
                max-width:100%;
                margin:0 0 22px 0;
            }
            .overquota_html_wrap h1 {
                margin:0 0 12px 0;
                font-size:28px;
            }
            .overquota_html_wrap p {
                margin:0 0 16px 0;
                font-size:16px;
            }
            .overquota_html_wrap p.breadcrumb {
                font-size:14px;
            }
            .overquota_html_button {
                background:    #ff5f5e;
                color:         #fff;
                border-radius: 10px 0 10px 10px;
                box-shadow:    4px 4px 0 0 #a8dadc;
                border:        0;
                font-size:     18px;
                padding:       12px 26px 12px 26px;
                line-height:   18px;
                transition:    all 0.2s ease;
                text-decoration: none !important;
                margin-top: 10px;
                display: inline-block;
            }
            .overquota_html_button:hover,
            .overquota_html_button:focus,
            .overquota_html_button:active {
                background: #a8dadc !important;
                box-shadow: none !important;
            }
            .overquota_html_button.secondary {
                background: #fff;
                color: #1d2327;
                border: 1px solid #dcdcde;
                box-shadow: none;
                margin-left: 10px;
            }
            .overquota_html_close_help {
                display:none;
                margin-top:12px;
                font-size:13px;
                color:#50575e;
            }
            </style>
            </head><body>'
           . '<div class="overquota_html_wrap">'
           . ('' !== $logoUrl ? '<img class="overquota_html_logo" src="' . esc_url($logoUrl) . '" alt="POP">' : '')
           . '<h1>' . esc_html($title) . '</h1>'
           . '<p>' . esc_html($message) . '</p>'
           . '<p><strong>' . esc_html__('Next step', WC_EL_INV_TEXTDOMAIN) . ':</strong> ' . esc_html($breadcrumb) . '</p>'
           . '<p><a class="overquota_html_button" href="' . esc_url($ctaUrl) . '" target="_blank" rel="noopener noreferrer">' . esc_html($ctaLabel) . '</a>'
           . '<button type="button" id="pop-close-tab" class="overquota_html_button secondary">' . esc_html($closeLabel) . '</button></p>'
           . '<p id="pop-close-help" class="overquota_html_close_help">' . esc_html($closeHelp) . '</p>'
           . '</div><script>
                (function () {
                    var closeButton = document.getElementById("pop-close-tab");
                    var closeHelp = document.getElementById("pop-close-help");
                    if (!closeButton) {
                        return;
                    }
                    closeButton.addEventListener("click", function () {
                        window.close();
                        window.setTimeout(function () {
                            if (closeHelp) {
                                closeHelp.style.display = "block";
                            }
                        }, 300);
                    });
                })();
            </script></body></html>';
}

/**
 * Extract a generic document-endpoint business error suitable for HTML fallback pages.
 *
 * @param mixed $raw
 *
 * @return array|null
 */
function extractPopApiDocumentErrorHtmlPayload($raw): ?array
{
    if (is_string($raw)) {
        $decoded = json_decode($raw, true);
        if (! is_array($decoded)) {
            return null;
        }

        return extractPopApiDocumentErrorHtmlPayload($decoded);
    }

    if (is_object($raw)) {
        $raw = json_decode(json_encode($raw), true);
    }

    if (! is_array($raw)) {
        return null;
    }

    if (is_array(extractPopApiOverquotaUiPayload($raw))) {
        return null;
    }

    $success = $raw['success'] ?? null;
    $message = isset($raw['message']) ? trim((string)$raw['message']) : '';
    $code    = isset($raw['code']) ? (string)$raw['code'] : '';

    if (false !== $success || '' === $message) {
        return null;
    }

    return [
        'title'   => __('Document generation blocked', WC_EL_INV_TEXTDOMAIN),
        'message' => $message,
        'code'    => $code,
    ];
}

/**
 * Build an HTML fallback page for non-overquota business errors returned by document endpoints.
 *
 * @param array $payload
 *
 * @return string
 */
function buildPopApiDocumentErrorHtmlFallback(array $payload): string
{
    $title   = isset($payload['title']) ? (string)$payload['title'] : __('Document generation blocked', WC_EL_INV_TEXTDOMAIN);
    $message = isset($payload['message']) ? (string)$payload['message'] : __('The request could not be completed.', WC_EL_INV_TEXTDOMAIN);
    $code    = isset($payload['code']) ? (string)$payload['code'] : '';
    $logoUrl = defined('WC_EL_INV_URL') ? WC_EL_INV_URL . 'assets/images/pop.svg' : '';
    $closeLabel = __('Close tab', WC_EL_INV_TEXTDOMAIN);
    $closeHelp  = __('If the tab does not close automatically, you can close it manually.', WC_EL_INV_TEXTDOMAIN);

    return '<!DOCTYPE html><html><head><meta charset="utf-8"><title>' . esc_html($title) . '</title>
            <style>
            body {
                background: #f6f7f7;
                margin: 0;
                padding: 24px;
            }
            .document_error_html_wrap {
                max-width: 760px;
                margin: 40px auto;
                padding: 30px 30px 40px;
                font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif;
                line-height: 1.5;
                color: #1d2327;
                border: 1px solid #ccc;
                border-radius: 5px;
            }
            .document_error_html_logo {
                display:block;
                width:120px;
                max-width:100%;
                margin:0 0 22px 0;
            }
            .document_error_html_wrap h1 {
                margin:0 0 12px 0;
                font-size:28px;
            }
            .document_error_html_wrap p {
                margin:0 0 16px 0;
                font-size:16px;
            }
            .document_error_html_wrap .meta {
                font-size:13px;
                color:#50575e;
            }
            .document_error_html_button {
                background: #fff;
                color: #1d2327;
                border: 1px solid #dcdcde;
                border-radius: 10px 0 10px 10px;
                font-size: 16px;
                padding: 12px 26px;
                line-height: 18px;
                cursor: pointer;
            }
            .document_error_html_close_help {
                display:none;
                margin-top:12px;
                font-size:13px;
                color:#50575e;
            }
            </style>
            </head><body>'
           . '<div class="document_error_html_wrap">'
           . ('' !== $logoUrl ? '<img class="document_error_html_logo" src="' . esc_url($logoUrl) . '" alt="POP">' : '')
           . '<h1>' . esc_html($title) . '</h1>'
           . '<p>' . esc_html($message) . '</p>'
           . ('' !== $code ? '<p class="meta"><strong>' . esc_html__('Code', WC_EL_INV_TEXTDOMAIN) . ':</strong> ' . esc_html($code) . '</p>' : '')
           . '<p><button type="button" id="pop-close-tab" class="document_error_html_button">' . esc_html($closeLabel) . '</button></p>'
           . '<p id="pop-close-help" class="document_error_html_close_help">' . esc_html($closeHelp) . '</p>'
           . '</div><script>
                (function () {
                    var closeButton = document.getElementById("pop-close-tab");
                    var closeHelp = document.getElementById("pop-close-help");
                    if (!closeButton) {
                        return;
                    }
                    closeButton.addEventListener("click", function () {
                        window.close();
                        window.setTimeout(function () {
                            if (closeHelp) {
                                closeHelp.style.display = "block";
                            }
                        }, 300);
                    });
                })();
            </script></body></html>';
}

/**
 * Return true when the current context should receive JSON instead of HTML fallback.
 *
 * @param string $return
 *
 * @return bool
 */
function shouldReturnPopApiOverquotaJson(string $return): bool
{
    if (defined('DOING_AJAX') && DOING_AJAX) {
        return true;
    }

    if (defined('DOING_CRON') && DOING_CRON) {
        return true;
    }

    return 'json' === $return || 'string' === $return;
}

/**
 * Return the order meta key used to persist overquota state.
 *
 * @return string
 */
function getPopApiOverquotaOrderMetaKey(): string
{
    return '_wc_el_inv_pop_api_overquota_state';
}

/**
 * Return the option key used to persist the global overquota state.
 *
 * @return string
 */
function getPopApiOverquotaOptionKey(): string
{
    return 'wc_el_inv_pop_api_overquota_state';
}

/**
 * Return the current global overquota state, if any.
 *
 * @return array|null
 */
function getPopApiOverquotaState(): ?array
{
    $state = get_option(getPopApiOverquotaOptionKey(), null);
    if (! is_array($state) || ! isset($state['type']) || 'pop_api_overquota' !== $state['type']) {
        return null;
    }

    return $state;
}

/**
 * Persist the latest overquota state on the order.
 *
 * @param mixed $order
 * @param array $payload
 * @param array $context
 *
 * @return void
 */
function persistPopApiOverquotaState($order, array $payload, array $context = []): void
{
    $state = $payload;

    if (is_object($order) && method_exists($order, 'get_id')) {
        $state['last_order_id'] = (int)$order->get_id();
    }

    foreach (['provider', 'action', 'method', 'return'] as $contextKey) {
        if (isset($context[$contextKey]) && '' !== (string)$context[$contextKey]) {
            $state['last_' . $contextKey] = (string)$context[$contextKey];
        }
    }

    update_option(getPopApiOverquotaOptionKey(), $state, false);
}

/**
 * Clear persisted overquota state after a successful flow.
 *
 * @param mixed $order
 *
 * @return void
 */
function clearPopApiOverquotaState($order): void
{
    if (function_exists('delete_option')) {
        delete_option(getPopApiOverquotaOptionKey());
    }

    if (is_object($order) && method_exists($order, 'delete_meta')) {
        $order->delete_meta(getPopApiOverquotaOrderMetaKey());
        if (method_exists($order, 'save')) {
            $order->save();
        }
    }
}

/**
 * Build persistent admin notice HTML from the global overquota state.
 *
 * @param array $payload
 *
 * @return string
 */
function buildPopApiOverquotaAdminNoticeHtml(array $payload): string
{
    $title      = isset($payload['title']) ? (string)$payload['title'] : __('POP API limit reached', WC_EL_INV_TEXTDOMAIN);
    $message    = isset($payload['message']) ? (string)$payload['message'] : '';
    $cta        = isset($payload['cta']) && is_array($payload['cta']) ? $payload['cta'] : [];
    $stepLabel  = isset($cta['step_label']) ? (string)$cta['step_label'] : __('Next step', WC_EL_INV_TEXTDOMAIN);
    $breadcrumb = isset($cta['breadcrumb']) ? (string)$cta['breadcrumb'] : '';
    $ctaLabel   = isset($cta['label']) ? (string)$cta['label'] : __('Open POP', WC_EL_INV_TEXTDOMAIN);
    $ctaUrl     = isset($cta['url']) ? (string)$cta['url'] : 'https://popapi.io/';
    $noticeType = ! empty($payload['retryable']) ? 'notice-warning' : 'notice-error';

    $html = '<div class="pop-notice notice ' . esc_attr($noticeType) . ' is-dismissible">';
    $html .= '<p><strong>' . esc_html($title) . '</strong><br>' . esc_html($message) . '</p>';

    if ('' !== $breadcrumb) {
        $html .= '<p><strong>' . esc_html($stepLabel) . ':</strong><br>' . esc_html($breadcrumb) . '</p>';
    }

    $html .= '<p><a class="button button-secondary" href="' . esc_url($ctaUrl) . '" target="_blank" rel="noopener noreferrer">' . esc_html($ctaLabel) . '</a></p>';
    $html .= '</div>';

    return $html;
}

/**
 * Resolve local XML storage bucket by shop country.
 *
 * @param string $shopCountry
 *
 * @return string
 */
function resolvePopXmlStorageBucket(string $shopCountry): string
{
    $shopCountry = strtoupper(trim($shopCountry));

    if ('PL' === $shopCountry) {
        return 'ksef';
    }

    if (in_array($shopCountry, ['IT', 'SM'], true)) {
        return 'xml';
    }

    if (in_array($shopCountry, CreateXml::$allowedUBLZone, true)) {
        return 'ubl';
    }

    return '';
}

/**
 * Returns true when stylized XML local persistence is supported for the bucket.
 *
 * @param string $storageBucket
 *
 * @return bool
 */
function shouldPersistPopStylizedXml(string $storageBucket): bool
{
    return 'xml' === trim($storageBucket);
}

/**
 * Return the normalized ZUGFeRD bundle node from a POP API success payload.
 *
 * @param array|null $payload
 *
 * @return array|null
 */
function getPopApiZugferdBundlePayload(?array $payload): ?array
{
    if (! is_array($payload)) {
        return null;
    }

    $candidate = $payload;
    if (isset($payload['data']) && is_array($payload['data'])) {
        $candidate = $payload['data'];
    }

    if (! isset($candidate['attachments']) || ! is_array($candidate['attachments'])) {
        return null;
    }

    if (! isset($candidate['attachments']['pdf'], $candidate['attachments']['xml'], $candidate['attachments']['hybrid_pdf'])) {
        return null;
    }

    return $candidate;
}

/**
 * Extract base64 file contents from a ZUGFeRD bundle attachment node.
 *
 * @param mixed $node
 *
 * @return string|null
 */
function decodePopApiZugferdAttachment($node): ?string
{
    if (! is_array($node)) {
        return null;
    }

    $base64 = isset($node['content_base64']) ? (string)$node['content_base64'] : '';
    if ('' === $base64) {
        return null;
    }

    $decoded = base64_decode($base64, true);

    return false === $decoded ? null : $decoded;
}

/**
 * Get the current environment setting (live or sandbox).
 *
 * @return string
 */
function getPopApiEnvironment(): string
{
    $env = get_option('wc_el_inv_environment', 'live');
    return in_array($env, ['live', 'sandbox'], true) ? $env : 'live';
}

/**
 * @param array  $params
 * @param string $return
 *
 * @return mixed|string|null
 */
function popApi($url, array $args = array(), array $params = array(), string $return = 'string')
{
    if (! empty($params)) {
        $params['environment'] = getPopApiEnvironment();

        $paramArgs = \WcElectronInvoice\Functions\filterInput($_GET, 'param_args', FILTER_UNSAFE_RAW);

        /**
         * @since 6.0.0
         * Having the user id, means the wizard has been completed.
         *
         * Some time in the future we can get rid of the user id and just check for a license_key.
         * This is just a temporary solution to avoid breaking changes, since the license_key was there even before 6.0.0.
         */
        $uid = (int)get_option('pop_account_user_id', false);
        if (empty($uid)) {
            $subject   = esc_html__('Complete Your Account Setup', WC_EL_INV_TEXTDOMAIN);
            $emailBody = sprintf(esc_html__('To start generating electronic invoices (XML and PDF) using our plugin, you need to create an account on %s.
				We’ve integrated a handy setup wizard directly into the plugin to guide you through the account creation process:
				1. Log in to your WordPress site.
				2. Go to the “General” section of the plugin and click on “Configure Account”, or navigate to the “Wizard Setup” section.
				3. Follow the wizard to complete the registration.
				Once the setup is complete, you’ll be able to generate electronic invoices without issues!
				For support inquiries, feel free to contact us at support@popapi.io.
				Thank you for choosing POP,
				The POP Team
				', WC_EL_INV_TEXTDOMAIN), 'https://popapi.io');

            sendEmailToAdminForError($subject, $emailBody);

            if (empty($params['license_key'])) {
                exit;
            }
        }

        /**
         * Filter params before json_encode and api request
         */
        $params = apply_filters('wc_el_inv-params_filter_before_api_request', $params);

        $json         = json_encode($params);
        $args['body'] = $json;

        // Returns the json of the API request
        if ('true' === $paramArgs) {
            $params['url_request'] = $url;
            wp_send_json($params, 200);
            exit;
        }
    }

    if (empty($args['body'])) {
        $data = array(
            'msg'  => __('Error: Empty body data', WC_EL_INV_TEXTDOMAIN),
            'code' => 500,
        );
        wp_send_json($data, 200, JSON_PRETTY_PRINT);
        exit;
    }

    $fileName       = $params['data']['filename'];
    $objID          = $params['data']['id'];
    $provider       = $params['data']['order_provider'];
    $integrationUse = ! empty($params['integration']);

    /**
     * Filter args before api request
     */
    $args = apply_filters('wc_el_inv-args_filter_before_api_request', $args);

    // Initialize response data
    $result   = null;
    $response = null;
    $code     = 0;
    $body     = null;

    // Request args
    $args = wp_parse_args($args, array(
        'headers'     => array('Content-Type' => 'application/json; charset=UTF-8'),
        'timeout'     => 15,
        'redirection' => 5,
        'sslverify'   => true,
        'compress'    => true,
        'cookies'     => array(),
    ));

    if (! empty($params['license_key'])) {
        $args['headers']['X-API-Key'] = $params['license_key'];
    }

    $attempt     = 1;
    $maxAttempts = 1;

    if ($integrationUse) {
        $args['timeout'] = 20;
        $response        = wp_remote_request($url, $args);
        $code            = is_wp_error($response) ? 0 : (int)wp_remote_retrieve_response_code($response);
        $body            = is_wp_error($response) ? '' : wp_remote_retrieve_body($response);
    } else {
        // Initialize retry data
        $maxAttempts = 3;
        $attempt     = 0;
        $delay       = 1;

        while ($attempt < $maxAttempts) {
            $attempt++;
            $response = wp_remote_request($url, $args);

            if (! is_wp_error($response)) {
                $code = (int)wp_remote_retrieve_response_code($response);
                $body = wp_remote_retrieve_body($response);
                if ($code === 404 || ($code >= 200 && $code < 300)) {
                    break;
                }
            }

            sleep($delay);
            $delay += 1;
        }
    }

    // API Error
    if (is_wp_error($response) || ($code < 200 || $code >= 300)) {
        $decodedBody = parsePopApiJsonBody($body);
        $payload     = $decodedBody['data'];
        $overquotaPayload = normalizePopApiOverquotaError($code, $payload);
        if (is_array($overquotaPayload)) {
            $uiPayload        = buildPopApiOverquotaUiPayload($overquotaPayload);
            $technicalMessage = buildPopApiOverquotaTechnicalMessage($uiPayload);
            $orderContext     = null;
            if ($objID && $provider) {
                $orderContext = OrderQuery::instance()->getProviderOrder($objID, $provider);
            }
            $action = \WcElectronInvoice\Functions\filterInput($_POST, 'action') ?? null;
            $method = \WcElectronInvoice\Functions\filterInput($_POST, 'method') ?? null;
            persistPopApiOverquotaState($orderContext, $uiPayload, [
                'provider' => $provider,
                'action'   => $action,
                'method'   => $method,
                'return'   => $return,
            ]);

            error_log("POP Api overquota: " . $technicalMessage);

            $subject = esc_html__('POP API overquota block', WC_EL_INV_TEXTDOMAIN);
            sendEmailToAdminForError($subject, $technicalMessage . "\n\n");

            if (shouldReturnPopApiOverquotaJson($return)) {
                $body = json_encode([
                    'success' => false,
                    'message' => $uiPayload['message'],
                    'code'    => $uiPayload['code'],
                    'data'    => $uiPayload,
                ]);
            } else {
                @status_header((int)$uiPayload['status']);
                @header('Content-Type: text/html; charset=' . get_option('blog_charset'));
                echo buildPopApiOverquotaHtmlFallback($uiPayload);
                die();
            }
        }

        $documentErrorPayload = extractPopApiDocumentErrorHtmlPayload($payload);
        if (is_array($documentErrorPayload) && ! shouldReturnPopApiOverquotaJson($return)) {
            @status_header($code > 0 ? $code : 422);
            @header('Content-Type: text/html; charset=' . get_option('blog_charset'));
            echo buildPopApiDocumentErrorHtmlFallback($documentErrorPayload);
            die();
        }

        if (! $decodedBody['ok']) {
            $body = json_encode([
                'error' => is_wp_error($response) ? $response->get_error_message() : esc_html__('POP Api error',
                    WC_EL_INV_TEXTDOMAIN),
                'status_code' => $code,
                'decode_error' => $decodedBody['error'],
                'raw' => is_string($body) ? $body : '',
            ]);
        }

        $debugContext = buildPopApiFailureDebugContext(
            $url,
            $args,
            $response,
            $code,
            $body,
            $attempt,
            $maxAttempts,
            $objID,
            $provider,
            $integrationUse
        );

        error_log("POP Api error context: " . $debugContext);
        error_log("POP Api error: " . $body);
        // Unauthorized error
        // @see allowedUserAction "pop-cloud-api" or WP_Error from api
        // POP api error
        $subject = buildPopApiFailureEmailSubject($response, $url, $provider, $objID);
        sendEmailToAdminForError($subject, "Context:\n" . $debugContext . "\n\nPayload:\n" . $body . "\n\n");

        @header('Content-Type: application/json; charset=' . get_option('blog_charset'));
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
        echo $body;
        die();
    }

    // Initialize SdI/PEPPOL via pop response
    $responseIntegrationViaPop = null;

    if ($objID && $provider) {
        $orderContext = OrderQuery::instance()->getProviderOrder($objID, $provider);
        clearPopApiOverquotaState($orderContext);
    } else {
        clearPopApiOverquotaState(null);
    }

    // Render response
    if ($body) {
        $zugferdPayload = null;
        if ('zugferd' === $return) {
            $decodedZugferd = parsePopApiJsonBody($body);
            if ($decodedZugferd['ok']) {
                $zugferdPayload = getPopApiZugferdBundlePayload($decodedZugferd['data']);
            }

            if (is_array($zugferdPayload)) {
                $body = wp_json_encode($zugferdPayload);
            } else {
                @header('Content-Type: application/json; charset=' . get_option('blog_charset'));
                return $body;
            }
        }

        // DOING_CRON > SdI/PEPPOL via POP set xml_string to body for saving XML file
        if ('xml' === $return && (defined('DOING_CRON') && DOING_CRON || defined('DOING_AJAX') && DOING_AJAX)) {
            $action = \WcElectronInvoice\Functions\filterInput($_POST, 'action') ?? null;
            $method = \WcElectronInvoice\Functions\filterInput($_POST, 'method') ?? null;

            if (
                (
                    'createAndSendInvoice' === $action
                    || 'createAndSendUblInvoice' === $action
                    || 'createAndSendZohoInvoice' === $action
                ) &&
                \WcElectronInvoice\Functions\isBackgroundExecutionMode($method, ['cronjob', 'ajax'])
            ) {
                $response = json_decode($body);
                if (
                    is_object($response) &&
                    isset($response->xml_string) &&
                    is_string($response->xml_string) &&
                    '' !== trim($response->xml_string)
                ) {
                    $responseIntegrationViaPop = $response;
                    // XML string for saving
                    $body = $responseIntegrationViaPop->xml_string;
                    \WcElectronInvoice\Functions\log("{$action} > Set xml string before saving file" . "\n");
                } else {
                    \WcElectronInvoice\Functions\log(
                        "{$action} > Invalid integration JSON shape, fallback to raw body\n" .
                        print_r($body, true) . "\n"
                    );
                }
            }
        }

        // Save xml file
        if (isset($params['data']['save']) && true === $params['data']['save'] && false === $params['data']['save_bulk']) {
            // Set header for open dialog and save xml file.
            @header("Content-Disposition: attachment;filename={$fileName}.xml");
        }
        if ('xml' === $return) {
            if (! defined('IS_POP_XML_RESPONSE')) {
                define('IS_POP_XML_RESPONSE', true);
            }
            @header('Content-Type: application/xml; charset=' . get_option('blog_charset'));
            $result = $body;
        } elseif ('string' === $return) {
            @header('Content-Type: text/plain; charset=' . get_option('blog_charset'));
            $result = $body;
        } elseif ('pdf' === $return) {
            if ('on' !== $params['data']['pdf']['invoice_html']) {
                @header('Content-Type: application/pdf');
                @header("Content-Disposition: attachment;filename={$fileName}.pdf");
            } else {
                @header('Content-Type: text/html; charset=' . get_option('blog_charset'));
            }
            $result = $body;
        } elseif ('json' === $return) {
            @header('Content-Type: application/json; charset=' . get_option('blog_charset'));
            $result = json_decode($body);
            if (JSON_ERROR_NONE !== json_last_error()) {
                \WcElectronInvoice\Functions\log(
                    "popApi > Invalid JSON response for return=json\n" .
                    "json_error: " . json_last_error_msg() . "\n"
                );
            }
        } elseif ('zugferd' === $return) {
            $hybridPdf = decodePopApiZugferdAttachment($zugferdPayload['attachments']['hybrid_pdf']);
            @header('Content-Type: application/pdf');
            @header("Content-Disposition: attachment;filename={$fileName}-zugferd.pdf");
            $result = is_string($hybridPdf) ? $hybridPdf : '';
        }

        /**
         * Store file
         */
        // Get the base directory of uploads
        $upload_dir = wp_upload_dir();
        $year       = date('Y');
        $month      = date('m');

        // Get order obj
        $order = OrderQuery::instance()->getProviderOrder($objID, $provider);
        $type  = $order->get_type();
        if (is_subclass_of($order, '\WcElectronInvoice\Providers\OrderRefund')) {
            $order = OrderQuery::instance()->getProviderOrder($order->get_parent_id(), $provider);
        }

        // Save directory for XML and PDF
        $ubl_dir  = trailingslashit($upload_dir['basedir']) . "pop-ubl/{$year}/{$month}/";
        $xml_dir  = trailingslashit($upload_dir['basedir']) . "pop-xml/{$year}/{$month}/";
        $ksef_dir = trailingslashit($upload_dir['basedir']) . "pop-ksef/{$year}/{$month}/";
        $pdf_dir  = trailingslashit($upload_dir['basedir']) . "pop-pdf/{$year}/{$month}/";
        $zugferd_dir = trailingslashit($upload_dir['basedir']) . "pop-zugferd/{$year}/{$month}/";

        // File paths based on the requested name
        $wp_content_path   = trailingslashit(WP_CONTENT_DIR);
        $relative_ubl_dir  = str_replace($wp_content_path, 'wp-content/', $ubl_dir);
        $relative_xml_dir  = str_replace($wp_content_path, 'wp-content/', $xml_dir);
        $relative_ksef_dir = str_replace($wp_content_path, 'wp-content/', $ksef_dir);
        $relative_pdf_dir  = str_replace($wp_content_path, 'wp-content/', $pdf_dir);
        $relative_zugferd_dir = str_replace($wp_content_path, 'wp-content/', $zugferd_dir);

        $xml_file_path = $relative_xml_dir . $fileName . '.xml';
        if ('xml' === $return && true === boolval($params['data']['view'])) {
            $xml_file_path = $relative_xml_dir . $fileName . '_stylized.xml';
        }
        $pdf_file_path = $relative_pdf_dir . $fileName . '.pdf';
        $ubl_file_path = $relative_ubl_dir . $fileName . '.xml';
        $ksef_file_path = $relative_ksef_dir . $fileName . '.xml';
        $zugferd_hybrid_pdf_file_path = $relative_zugferd_dir . $fileName . '-zugferd.pdf';

        // Creating directories if they do not exist
        if (! file_exists($xml_dir)) {
            wp_mkdir_p($xml_dir);
        }
        if (! file_exists($ubl_dir)) {
            wp_mkdir_p($ubl_dir);
        }
        if (! file_exists($ksef_dir)) {
            wp_mkdir_p($ksef_dir);
        }
        if (! file_exists($pdf_dir)) {
            wp_mkdir_p($pdf_dir);
        }
        if (! file_exists($zugferd_dir)) {
            wp_mkdir_p($zugferd_dir);
        }

        if (is_subclass_of($order, '\WcElectronInvoice\Providers\Order') ||
            is_subclass_of($order, '\WcElectronInvoice\Providers\OrderRefund')
        ) {
            if ('zugferd' === $return && is_array($zugferdPayload)) {
                $xmlContent = decodePopApiZugferdAttachment($zugferdPayload['attachments']['xml']);
                $pdfContent = decodePopApiZugferdAttachment($zugferdPayload['attachments']['pdf']);
                $hybridPdfContent = decodePopApiZugferdAttachment($zugferdPayload['attachments']['hybrid_pdf']);

                if (is_string($xmlContent) && '' !== $xmlContent) {
                    popSaveFileSafely($xmlContent, $fileName . '.xml', $xml_dir);
                    if ('shop_order_refund' === $type) {
                        $order->update_meta('pop_credit_note_file_xml', $xml_file_path);
                    } else {
                        $order->update_meta('pop_invoice_file_xml', $xml_file_path);
                    }
                }

                if (is_string($pdfContent) && 0 === strpos($pdfContent, '%PDF-')) {
                    popSaveFileSafely($pdfContent, $fileName . '.pdf', $pdf_dir);
                    if ('shop_order_refund' === $type) {
                        $order->update_meta('pop_credit_note_file_pdf', $pdf_file_path);
                    } else {
                        $order->update_meta('pop_invoice_file_pdf', $pdf_file_path);
                    }
                }

                if (is_string($hybridPdfContent) && 0 === strpos($hybridPdfContent, '%PDF-')) {
                    popSaveFileSafely($hybridPdfContent, $fileName . '-zugferd.pdf', $zugferd_dir);
                    if ('shop_order_refund' === $type) {
                        $order->update_meta('pop_credit_note_file_zugferd_hybrid_pdf', $zugferd_hybrid_pdf_file_path);
                    } else {
                        $order->update_meta('pop_invoice_file_zugferd_hybrid_pdf', $zugferd_hybrid_pdf_file_path);
                    }
                }

                $order->save();
            } elseif ('xml' === $return && false === boolval($params['data']['view'])) {
                $check = isXml($body);
                if ($check->valid) {
                    $shopCountry   = \WcElectronInvoice\WooCommerce\Fields\GeneralFields::getGeneralInvoiceOptionCountryState();
                    $storageBucket = resolvePopXmlStorageBucket($shopCountry);
                    if ('xml' === $storageBucket && $result = popSaveFileSafely($body, $fileName . '.xml', $xml_dir)) {
                        // Store file and save pop_credit_note_file_xml or pop_invoice_file_xml meta
                        if ('shop_order_refund' === $type) {
                            $order->update_meta('pop_credit_note_file_xml', $xml_file_path);
                        } else {
                            $order->update_meta('pop_invoice_file_xml', $xml_file_path);
                        }
                        $order->save();
                    } elseif ('ubl' === $storageBucket) {
                        if ($result = popSaveFileSafely($body, $fileName . '.xml', $ubl_dir)) {
                            // Store file and save pop_credit_note_file_xml or pop_invoice_file_xml meta
                            if ('shop_order_refund' === $type) {
                                $order->update_meta('pop_credit_note_file_xml', $ubl_file_path);
                            } else {
                                $order->update_meta('pop_invoice_file_xml', $ubl_file_path);
                            }
                            $order->save();
                        }
                    } elseif ('ksef' === $storageBucket && $result = popSaveFileSafely($body, $fileName . '.xml',
                            $ksef_dir)) {
                        if ('shop_order_refund' === $type) {
                            $order->update_meta('pop_credit_note_file_xml', $ksef_file_path);
                        } else {
                            $order->update_meta('pop_invoice_file_xml', $ksef_file_path);
                        }
                        $order->save();
                    }
                }
            } elseif ('xml' === $return && true === boolval($params['data']['view'])) {
                $check = isXml($body);
                if ($check->valid) {
                    $shopCountry      = \WcElectronInvoice\WooCommerce\Fields\GeneralFields::getGeneralInvoiceOptionCountryState();
                    $storageBucket    = resolvePopXmlStorageBucket($shopCountry);
                    if (! shouldPersistPopStylizedXml($storageBucket)) {
                        return $result;
                    }

                    if ($result = popSaveFileSafely($body, $fileName . '_stylized.xml', $xml_dir)) {
                        // Store file and save pop_credit_note_file_xml or pop_invoice_file_xmlmeta
                        if ('shop_order_refund' === $type) {
                            $order->update_meta('pop_credit_note_file_xml_stylized', $xml_file_path);
                        } else {
                            $order->update_meta('pop_invoice_file_xml_stylized', $xml_file_path);
                        }
                        $order->save();
                    }
                }
            } elseif ('pdf' === $return && 'on' !== $params['data']['pdf']['invoice_html']) {
                if (is_string($body) && substr($body, 0, 5) === '%PDF-') {
                    if ($result = popSaveFileSafely($body, $fileName . '.pdf', $pdf_dir)) {
                        // Store file and save pop_credit_note_file_pdf or pop_invoice_file_pdf meta
                        if ('shop_order_refund' === $type) {
                            $order->update_meta('pop_credit_note_file_pdf', $pdf_file_path);
                        } else {
                            $order->update_meta('pop_invoice_file_pdf', $pdf_file_path);
                        }
                        $order->save();
                    }
                }
            }
        }
    }

    // DOING_CRON > SdI/PEPPOL via POP response after create xml
    if ($responseIntegrationViaPop && 'xml' === $return && (defined('DOING_CRON') && DOING_CRON || defined('DOING_AJAX') && DOING_AJAX)) {
        $action = \WcElectronInvoice\Functions\filterInput($_POST, 'action') ?? null;
        $method = \WcElectronInvoice\Functions\filterInput($_POST, 'method') ?? null;
        if (
            (
                'createAndSendInvoice' === $action
                || 'createAndSendUblInvoice' === $action
                || 'createAndSendZohoInvoice' === $action
            ) &&
            \WcElectronInvoice\Functions\isBackgroundExecutionMode($method, ['cronjob', 'ajax'])
        ) {
            return json_encode($responseIntegrationViaPop);
        }
    }

    return $result;
}

/**
 * sendEmailToAdminForError
 *
 * @param $emailBody
 *
 * @return void
 */
function sendEmailToAdminForError($subject, $emailBody)
{
    $adminEmail = get_bloginfo('admin_email');
    if (function_exists('wp_mail')) {
        $headers   = array('Content-Type: text/html; charset=UTF-8');
        $emailBody = nl2br($emailBody);
        wp_mail($adminEmail, $subject, $emailBody, $headers);
    }
}

/**
 * popSaveFileSafely
 *
 * @param $content
 * @param $filename
 * @param $target_dir
 *
 * @return bool
 */
function popSaveFileSafely($content, $filename, $target_dir)
{
    remove_all_filters('wp_delete_file');
    remove_all_filters('wp_delete_file_resized');

    // Disabilita la cancellazione automatica dei file temporanei
    add_filter('wp_delete_file', '__return_false');

    // Assicurati che la cartella di destinazione esista e sia scrivibile
    if (! file_exists($target_dir)) {
        wp_mkdir_p($target_dir); // Crea la cartella se non esiste
    }

    if (! is_writable($target_dir)) {
        chmod($target_dir, 0755); // Assicurati che la cartella sia scrivibile
    }

    ensureHtaccessProtection($target_dir);

    // Scriviamo direttamente il contenuto nel file nella cartella di destinazione
    $final_path = trailingslashit($target_dir) . $filename;

    // Scrivi il contenuto nel file finale
    if ($put = file_put_contents($final_path, $content)) {
        error_log('file put ' . $put);

        return $content;
    } else {
        error_log("Error saving file.");

        return false;
    }
}

/**
 * popDeleteSavedFiles
 *
 * @return void
 */
function popDeleteSavedFiles()
{
    if (! function_exists('WP_Filesystem')) {
        require_once ABSPATH . 'wp-admin/includes/file.php';
    }

    WP_Filesystem();
    global $wp_filesystem;

    $upload_dir = wp_upload_dir();

    $dirs = [
        trailingslashit($upload_dir['basedir']) . 'pop-ubl/',
        trailingslashit($upload_dir['basedir']) . 'pop-ksef/',
        trailingslashit($upload_dir['basedir']) . 'pop-xml/',
        trailingslashit($upload_dir['basedir']) . 'pop-pdf/',
        trailingslashit($upload_dir['basedir']) . 'pop-zugferd/',
        trailingslashit($upload_dir['basedir']) . 'pop-csv/',
    ];

    foreach ($dirs as $dir) {
        if ($wp_filesystem->is_dir($dir)) {
            $wp_filesystem->rmdir($dir, true); // Recursive delete
        }
    }
}

/**
 * ensureHtaccessProtection
 *
 * @param $dir_path
 *
 * @return void
 */
function ensureHtaccessProtection($dir_path)
{
    $htaccess_path = trailingslashit($dir_path) . '.htaccess';

    // Se la directory non esiste, la crea
    if (! file_exists($dir_path)) {
        wp_mkdir_p($dir_path);
    }

    // Se il file .htaccess non esiste, lo crea
    if (! file_exists($htaccess_path)) {
        $htaccess_content = <<<HTA
<IfModule mod_authz_core.c>
    Require all denied
</IfModule>

<IfModule !mod_authz_core.c>
    Deny from all
</IfModule>
HTA;

        file_put_contents($htaccess_path, $htaccess_content);
    }
}
