<?php
declare(strict_types=1);

namespace App\Modules\Automation;

use App\Modules\Accounting\ReceiptRepository;
use App\Modules\Email\EmailSendingService;
use App\Modules\Orders\OrdersRepository;
use App\Modules\Settings\CompanySettingsRepository;
use App\Modules\Settings\ReceiptConfigRepository;
use App\Modules\Shipments\DeliveryStatus;
use App\Modules\Shipments\ShipmentPackageRepository;
use Throwable;

final class AutomationService
{
    private const CHAIN_STATE_KEY = '__automation_chain';
    private const MAX_CHAIN_DEPTH = 8;
    private const MAX_CHAIN_EXECUTIONS = 200;

    private const SHIPMENT_STATUS_OPTION_MAP = [
        'registered' => ['created', 'confirmed'],
        'ready_for_pickup' => ['ready_for_pickup'],
        'dropped_at_point' => ['confirmed', 'in_transit'],
        'picked_up' => ['delivered'],
        'cancelled' => ['cancelled'],
        'unclaimed' => ['problem'],
        'picked_up_return' => ['returned'],
    ];

    public function __construct(
        private readonly AutomationRepository $repository,
        private readonly AutomationExecutionLogRepository $executionLogs,
        private readonly EmailSendingService $emailService,
        private readonly OrdersRepository $orders,
        private readonly CompanySettingsRepository $companySettings,
        private readonly ReceiptRepository $receipts,
        private readonly ReceiptConfigRepository $receiptConfigs,
        private readonly ShipmentPackageRepository $shipmentPackages
    ) {
    }

    /**
     * @param array<string, mixed> $context
     */
    public function trigger(string $eventType, int $orderId, array $context = []): void
    {
        $chainState = $this->getChainState($context);
        if ($chainState['depth'] > self::MAX_CHAIN_DEPTH) {
            return;
        }
        $context = $this->withChainState($context, $chainState);

        $rules = $this->repository->findActiveByEvent($eventType);
        if ($rules === []) {
            return;
        }

        $details = $this->orders->findDetails($orderId);
        if ($details === null) {
            return;
        }

        $order = is_array($details['order'] ?? null) ? $details['order'] : [];

        foreach ($rules as $rule) {
            try {
                $ruleId = (int) ($rule['id'] ?? 0);
                $executionKey = $this->buildExecutionKey($eventType, $ruleId);
                if ($this->isExecutionAlreadyProcessed($chainState, $executionKey)) {
                    continue;
                }

                $conditions = is_array($rule['conditions'] ?? null) ? $rule['conditions'] : [];
                $actions = is_array($rule['actions'] ?? null) ? $rule['actions'] : [];
                $ruleName = (string) ($rule['name'] ?? '');
                $ruleContext = $this->withExecution($context, $executionKey);
                $ruleMatched = $this->evaluateConditions($conditions, $order, $ruleContext);

                if ($ruleMatched) {
                    $this->executeActions($actions, $orderId, $ruleName, $ruleContext);
                    $this->logExecution($eventType, $ruleId, $ruleName, $orderId, 'success', 'Wykonano akcje automatyzacji', $ruleContext);
                }
            } catch (Throwable $exception) {
                $this->logExecution(
                    $eventType,
                    (int) ($rule['id'] ?? 0),
                    (string) ($rule['name'] ?? ''),
                    $orderId,
                    'failed',
                    $exception->getMessage(),
                    $context
                );
                // Blad jednej reguly nie blokuje kolejnych
            }
        }
    }

    /**
     * @param list<array<string, mixed>> $conditions
     * @param array<string, mixed> $order
     * @param array<string, mixed> $context
     */
    private function evaluateConditions(array $conditions, array $order, array $context): bool
    {
        foreach ($conditions as $condition) {
            $type = (string) ($condition['condition_type'] ?? '');
            $value = is_array($condition['condition_value'] ?? null) ? $condition['condition_value'] : [];

            if (!$this->evaluateSingleCondition($type, $value, $order, $context)) {
                return false;
            }
        }

        return true;
    }

    /**
     * @param array<string, mixed> $value
     * @param array<string, mixed> $order
     * @param array<string, mixed> $context
     */
    private function evaluateSingleCondition(string $type, array $value, array $order, array $context): bool
    {
        if ($type === 'integration') {
            return $this->evaluateIntegrationCondition($value, $order);
        }
        if ($type === 'shipment_status') {
            return $this->evaluateShipmentStatusCondition($value, $context);
        }

        return false;
    }

    /**
     * @param array<string, mixed> $value
     * @param array<string, mixed> $order
     */
    private function evaluateIntegrationCondition(array $value, array $order): bool
    {
        $allowedIds = is_array($value['integration_ids'] ?? null) ? $value['integration_ids'] : [];
        if ($allowedIds === []) {
            return false;
        }

        $orderIntegrationId = (int) ($order['integration_id'] ?? 0);
        if ($orderIntegrationId === 0) {
            return false;
        }

        return in_array($orderIntegrationId, array_map('intval', $allowedIds), true);
    }

    /**
     * @param array<string, mixed> $value
     * @param array<string, mixed> $context
     */
    private function evaluateShipmentStatusCondition(array $value, array $context): bool
    {
        $statusKeys = is_array($value['status_keys'] ?? null) ? $value['status_keys'] : [];
        if ($statusKeys === []) {
            return false;
        }

        $deliveryStatus = trim((string) ($context['delivery_status'] ?? ''));
        if ($deliveryStatus === '') {
            return false;
        }

        $allowedStatuses = [];
        foreach ($statusKeys as $statusKeyRaw) {
            $statusKey = trim((string) $statusKeyRaw);
            if ($statusKey === '' || !isset(self::SHIPMENT_STATUS_OPTION_MAP[$statusKey])) {
                continue;
            }
            foreach (self::SHIPMENT_STATUS_OPTION_MAP[$statusKey] as $mappedStatus) {
                $allowedStatuses[$mappedStatus] = true;
            }
        }

        if ($allowedStatuses === []) {
            return false;
        }

        return isset($allowedStatuses[$deliveryStatus]);
    }

    /**
     * @param list<array<string, mixed>> $actions
     * @param array<string, mixed> $context
     */
    private function executeActions(array $actions, int $orderId, string $ruleName, array $context): void
    {
        foreach ($actions as $action) {
            $type = (string) ($action['action_type'] ?? '');
            $config = is_array($action['action_config'] ?? null) ? $action['action_config'] : [];

            if ($type === 'send_email') {
                $this->handleSendEmail($config, $orderId, $ruleName);
                continue;
            }

            if ($type === 'issue_receipt') {
                $this->handleIssueReceipt($config, $orderId, $ruleName, $context);
                continue;
            }

            if ($type === 'update_shipment_status') {
                $this->handleUpdateShipmentStatus($config, $orderId, $ruleName, $context);
                continue;
            }

            if ($type === 'update_order_status') {
                $this->handleUpdateOrderStatus($config, $orderId, $ruleName);
            }
        }
    }

    /**
     * @param array<string, mixed> $config
     */
    private function handleSendEmail(array $config, int $orderId, string $ruleName): void
    {
        $templateId = (int) ($config['template_id'] ?? 0);
        if ($templateId <= 0) {
            return;
        }

        $recipient = (string) ($config['recipient'] ?? 'client');
        $actorName = 'Automatyzacja: ' . $ruleName;

        if ($recipient === 'client' || $recipient === 'client_and_company') {
            $this->emailService->send($orderId, $templateId, null, $actorName);
        }

        if ($recipient === 'company' || $recipient === 'client_and_company') {
            $this->sendToCompany($orderId, $templateId, $actorName);
        }
    }

    private function sendToCompany(int $orderId, int $templateId, string $actorName): void
    {
        $settings = $this->companySettings->getSettings();
        $companyEmail = trim((string) ($settings['email'] ?? ''));

        if ($companyEmail === '' || filter_var($companyEmail, FILTER_VALIDATE_EMAIL) === false) {
            $this->orders->recordActivity(
                $orderId,
                'automation_email_failed',
                $actorName . ' — brak adresu e-mail firmy w ustawieniach',
                ['reason' => 'missing_company_email'],
                'system',
                $actorName
            );
            return;
        }

        $companyName = trim((string) ($settings['company_name'] ?? ''));

        $this->emailService->send(
            $orderId,
            $templateId,
            null,
            $actorName,
            $companyEmail,
            $companyName
        );
    }

    /**
     * @param array<string, mixed> $config
     * @param array<string, mixed> $context
     */
    private function handleIssueReceipt(array $config, int $orderId, string $ruleName, array $context): void
    {
        $actorName = 'Automatyzacja: ' . $ruleName;

        $configId = (int) ($config['receipt_config_id'] ?? 0);
        if ($configId <= 0) {
            return;
        }
        $issueDateMode = (string) ($config['issue_date_mode'] ?? 'today');
        $duplicatePolicy = (string) ($config['duplicate_policy'] ?? 'skip_if_exists');

        $receiptConfig = $this->receiptConfigs->findById($configId);
        if ($receiptConfig === null || (int) ($receiptConfig['is_active'] ?? 0) !== 1) {
            $this->orders->recordActivity(
                $orderId,
                'automation_receipt_failed',
                $actorName . ' - nieprawidlowa lub nieaktywna konfiguracja paragonu',
                ['receipt_config_id' => $configId],
                'system',
                $actorName
            );
            return;
        }

        $existingReceipts = $this->receipts->findByOrderId($orderId);
        if ($duplicatePolicy === 'skip_if_exists' && $existingReceipts !== []) {
            $this->orders->recordActivity(
                $orderId,
                'automation_receipt_skipped',
                $actorName . ' - pomieto, paragon juz istnieje dla zamowienia',
                ['duplicate_policy' => $duplicatePolicy, 'existing_count' => count($existingReceipts)],
                'system',
                $actorName
            );
            return;
        }

        $details = $this->orders->findDetails($orderId);
        if ($details === null) {
            return;
        }
        $order = is_array($details['order'] ?? null) ? $details['order'] : [];
        $items = is_array($details['items'] ?? null) ? $details['items'] : [];
        $addresses = is_array($details['addresses'] ?? null) ? $details['addresses'] : [];
        $payments = is_array($details['payments'] ?? null) ? $details['payments'] : [];

        $issueDate = $this->resolveIssueDate($issueDateMode, $order, $payments);
        $saleDate = $this->resolveSaleDate($receiptConfig, $order, $payments, $issueDate);
        $orderReference = $this->resolveOrderReference($receiptConfig, $order);
        $sellerSnapshot = $this->buildSellerSnapshot();
        $buyerSnapshot = $this->buildBuyerSnapshot($addresses);
        ['items' => $itemsSnapshot, 'total_gross' => $totalGross] = $this->buildItemsSnapshot($items);

        try {
            $receiptNumber = $this->receipts->getNextNumber(
                $configId,
                (string) ($receiptConfig['number_format'] ?? 'PAR/%N/%M/%Y'),
                (string) ($receiptConfig['numbering_type'] ?? 'monthly')
            );
            $this->receipts->create([
                'order_id' => $orderId,
                'config_id' => $configId,
                'receipt_number' => $receiptNumber,
                'issue_date' => $issueDate,
                'sale_date' => $saleDate,
                'seller_data_json' => json_encode($sellerSnapshot, JSON_UNESCAPED_UNICODE),
                'buyer_data_json' => $buyerSnapshot !== null ? json_encode($buyerSnapshot, JSON_UNESCAPED_UNICODE) : null,
                'items_json' => json_encode($itemsSnapshot, JSON_UNESCAPED_UNICODE),
                'total_net' => number_format($totalGross, 2, '.', ''),
                'total_gross' => number_format($totalGross, 2, '.', ''),
                'order_reference_value' => $orderReference,
                'created_by' => null,
            ]);

            $this->orders->recordActivity(
                $orderId,
                'receipt_issued',
                'Wystawiono paragon: ' . $receiptNumber,
                ['receipt_number' => $receiptNumber, 'config_id' => $configId, 'total_gross' => number_format($totalGross, 2, '.', '')],
                'system',
                $actorName
            );

            // Chain automation: issuing receipt from one rule should trigger
            // rules listening on receipt.created. Uses generic chain context
            // with depth + rule deduplication to prevent loops.
            $this->emitEvent(
                'receipt.created',
                $orderId,
                $context,
                [
                    'automation_source' => 'issue_receipt',
                    'automation_rule' => $ruleName,
                    'receipt_number' => $receiptNumber,
                    'receipt_config_id' => $configId,
                ]
            );
        } catch (Throwable $exception) {
            $this->orders->recordActivity(
                $orderId,
                'automation_receipt_failed',
                $actorName . ' - blad wystawiania paragonu',
                ['error' => $exception->getMessage(), 'receipt_config_id' => $configId],
                'system',
                $actorName
            );
        }
    }

    /**
     * @param array<string, mixed> $config
     * @param array<string, mixed> $context
     */
    private function handleUpdateShipmentStatus(array $config, int $orderId, string $ruleName, array $context): void
    {
        $statusKey = trim((string) ($config['status_key'] ?? ''));
        $targetStatus = $this->resolveStatusFromActionKey($statusKey);
        if ($targetStatus === null) {
            return;
        }

        $targetPackage = $this->resolveTargetPackage($orderId, $context);
        if ($targetPackage === null) {
            return;
        }

        $packageId = (int) ($targetPackage['id'] ?? 0);
        if ($packageId <= 0) {
            return;
        }

        $previousStatus = trim((string) ($targetPackage['delivery_status'] ?? DeliveryStatus::UNKNOWN));
        if ($previousStatus === '') {
            $previousStatus = DeliveryStatus::UNKNOWN;
        }
        if ($previousStatus === $targetStatus) {
            return;
        }

        $this->shipmentPackages->updateDeliveryStatus($packageId, $targetStatus, null);

        $actorName = 'Automatyzacja: ' . $ruleName;
        $this->orders->recordActivity(
            $orderId,
            'automation_shipment_status_updated',
            $actorName . ' - zaktualizowano status przesylki',
            [
                'package_id' => $packageId,
                'previous_status' => $previousStatus,
                'delivery_status' => $targetStatus,
                'status_key' => $statusKey,
            ],
            'system',
            $actorName
        );

        $this->emitEvent(
            'shipment.status_changed',
            $orderId,
            $context,
            [
                'package_id' => $packageId,
                'provider' => (string) ($targetPackage['provider'] ?? ''),
                'delivery_status' => $targetStatus,
                'delivery_status_raw' => '',
                'previous_status' => $previousStatus,
                'previous_status_raw' => '',
                'automation_source' => 'update_shipment_status',
                'automation_rule' => $ruleName,
            ]
        );
    }

    /**
     * @param array<string, mixed> $config
     */
    private function handleUpdateOrderStatus(array $config, int $orderId, string $ruleName): void
    {
        $statusCode = trim((string) ($config['status_code'] ?? ''));
        if ($statusCode === '') {
            return;
        }

        $actorName = 'Automatyzacja: ' . $ruleName;
        $updated = $this->orders->updateOrderStatus($orderId, $statusCode, 'system', $actorName);
        if ($updated) {
            return;
        }

        $this->orders->recordActivity(
            $orderId,
            'automation_order_status_failed',
            $actorName . ' - nie udalo sie zmienic statusu zamowienia',
            ['target_status_code' => $statusCode],
            'system',
            $actorName
        );
    }

    private function resolveStatusFromActionKey(string $statusKey): ?string
    {
        if ($statusKey === '' || !isset(self::SHIPMENT_STATUS_OPTION_MAP[$statusKey])) {
            return null;
        }

        $mappedStatuses = self::SHIPMENT_STATUS_OPTION_MAP[$statusKey];
        if ($mappedStatuses === []) {
            return null;
        }

        $candidate = trim((string) $mappedStatuses[0]);
        if ($candidate === '' || !in_array($candidate, DeliveryStatus::ALL_STATUSES, true)) {
            return null;
        }

        return $candidate;
    }

    /**
     * @param array<string, mixed> $context
     * @return array<string, mixed>|null
     */
    private function resolveTargetPackage(int $orderId, array $context): ?array
    {
        $packageId = (int) ($context['package_id'] ?? 0);
        if ($packageId > 0) {
            $package = $this->shipmentPackages->findById($packageId);
            if (is_array($package) && (int) ($package['order_id'] ?? 0) === $orderId) {
                return $package;
            }
        }

        return $this->shipmentPackages->findLatestByOrderId($orderId);
    }

    /**
     * @param array<string, mixed> $order
     * @param list<array<string, mixed>> $payments
     */
    private function resolveIssueDate(string $mode, array $order, array $payments): string
    {
        if ($mode === 'order_date') {
            $orderedAt = trim((string) ($order['ordered_at'] ?? ''));
            if ($orderedAt !== '') {
                $timestamp = strtotime($orderedAt);
                if ($timestamp !== false) {
                    return date('Y-m-d', $timestamp);
                }
            }
        }

        if ($mode === 'payment_date') {
            $firstPayment = $payments[0] ?? [];
            $paymentDate = trim((string) ($firstPayment['payment_date'] ?? ''));
            if ($paymentDate !== '') {
                $timestamp = strtotime($paymentDate);
                if ($timestamp !== false) {
                    return date('Y-m-d', $timestamp);
                }
            }
        }

        return date('Y-m-d');
    }

    /**
     * @param array<string, mixed> $receiptConfig
     * @param array<string, mixed> $order
     * @param list<array<string, mixed>> $payments
     */
    private function resolveSaleDate(array $receiptConfig, array $order, array $payments, string $issueDate): string
    {
        $source = (string) ($receiptConfig['sale_date_source'] ?? 'issue_date');

        if ($source === 'order_date') {
            $ordered = (string) ($order['ordered_at'] ?? '');
            if ($ordered !== '') {
                $ts = strtotime($ordered);
                return $ts !== false ? date('Y-m-d', $ts) : $issueDate;
            }
        }

        if ($source === 'payment_date' && $payments !== []) {
            $lastPayment = $payments[0] ?? [];
            $payDate = (string) ($lastPayment['payment_date'] ?? '');
            if ($payDate !== '') {
                $ts = strtotime($payDate);
                return $ts !== false ? date('Y-m-d', $ts) : $issueDate;
            }
        }

        return $issueDate;
    }

    /**
     * @param array<string, mixed> $receiptConfig
     * @param array<string, mixed> $order
     */
    private function resolveOrderReference(array $receiptConfig, array $order): ?string
    {
        $ref = (string) ($receiptConfig['order_reference'] ?? 'none');

        if ($ref === 'orderpro') {
            return (string) ($order['internal_order_number'] ?? '');
        }

        if ($ref === 'integration') {
            return (string) ($order['external_order_id'] ?? '');
        }

        return null;
    }

    /**
     * @return array<string, mixed>
     */
    private function buildSellerSnapshot(): array
    {
        $seller = $this->companySettings->getSettings();

        return [
            'company_name' => $seller['company_name'] ?? '',
            'tax_number' => $seller['tax_number'] ?? '',
            'street' => $seller['street'] ?? '',
            'city' => $seller['city'] ?? '',
            'postal_code' => $seller['postal_code'] ?? '',
            'phone' => $seller['phone'] ?? '',
            'email' => $seller['email'] ?? '',
            'bank_account' => $seller['bank_account'] ?? '',
            'bdo_number' => $seller['bdo_number'] ?? '',
            'regon' => $seller['regon'] ?? '',
            'court_register' => $seller['court_register'] ?? '',
        ];
    }

    /**
     * @param list<array<string, mixed>> $addresses
     * @return array<string, mixed>|null
     */
    private function buildBuyerSnapshot(array $addresses): ?array
    {
        $buyerAddress = $this->resolveBuyerAddress($addresses);
        if ($buyerAddress === null) {
            return null;
        }

        return [
            'name' => $buyerAddress['name'] ?? '',
            'company_name' => $buyerAddress['company_name'] ?? '',
            'tax_number' => $buyerAddress['company_tax_number'] ?? '',
            'street' => trim((string) (($buyerAddress['street_name'] ?? '') . ' ' . ($buyerAddress['street_number'] ?? ''))),
            'city' => $buyerAddress['city'] ?? '',
            'postal_code' => $buyerAddress['zip_code'] ?? '',
            'phone' => $buyerAddress['phone'] ?? '',
            'email' => $buyerAddress['email'] ?? '',
        ];
    }

    /**
     * @param list<array<string, mixed>> $addresses
     * @return array<string, mixed>|null
     */
    private function resolveBuyerAddress(array $addresses): ?array
    {
        $byType = [];
        foreach ($addresses as $address) {
            $type = (string) ($address['address_type'] ?? '');
            if ($type !== '' && !isset($byType[$type])) {
                $byType[$type] = $address;
            }
        }

        return $byType['invoice'] ?? $byType['customer'] ?? null;
    }

    /**
     * @param list<array<string, mixed>> $items
     * @return array{items:list<array<string,mixed>>,total_gross:float}
     */
    private function buildItemsSnapshot(array $items): array
    {
        $itemsSnapshot = [];
        $totalGross = 0.0;
        foreach ($items as $item) {
            $qty = (float) ($item['quantity'] ?? 0);
            $price = $item['original_price_with_tax'] !== null ? (float) $item['original_price_with_tax'] : 0.0;
            $lineTotal = $qty * $price;
            $totalGross += $lineTotal;
            $itemsSnapshot[] = [
                'name' => $item['original_name'] ?? '',
                'quantity' => $qty,
                'price' => $price,
                'total' => $lineTotal,
                'sku' => $item['sku'] ?? '',
                'ean' => $item['ean'] ?? '',
            ];
        }

        return [
            'items' => $itemsSnapshot,
            'total_gross' => $totalGross,
        ];
    }

    /**
     * @param array<string, mixed> $context
     * @return array{chain_id:string,depth:int,executions:list<string>}
     */
    private function getChainState(array $context): array
    {
        $raw = $context[self::CHAIN_STATE_KEY] ?? null;
        $rawState = is_array($raw) ? $raw : [];

        $chainId = trim((string) ($rawState['chain_id'] ?? ''));
        if ($chainId === '') {
            $chainId = $this->generateChainId();
        }

        $depth = max(0, (int) ($rawState['depth'] ?? 0));

        $executionsRaw = $rawState['executions'] ?? [];
        $executions = [];
        if (is_array($executionsRaw)) {
            foreach ($executionsRaw as $executionRaw) {
                $execution = trim((string) $executionRaw);
                if ($execution === '') {
                    continue;
                }
                $executions[] = $execution;
            }
        }

        return [
            'chain_id' => $chainId,
            'depth' => $depth,
            'executions' => array_values(array_unique($executions)),
        ];
    }

    /**
     * @param array<string, mixed> $context
     * @param array{chain_id:string,depth:int,executions:list<string>} $state
     * @return array<string, mixed>
     */
    private function withChainState(array $context, array $state): array
    {
        $context[self::CHAIN_STATE_KEY] = [
            'chain_id' => $state['chain_id'],
            'depth' => $state['depth'],
            'executions' => $state['executions'],
        ];

        return $context;
    }

    /**
     * @param array<string, mixed> $context
     * @return array<string, mixed>
     */
    private function withExecution(array $context, string $executionKey): array
    {
        $state = $this->getChainState($context);
        $state['executions'][] = $executionKey;
        if (count($state['executions']) > self::MAX_CHAIN_EXECUTIONS) {
            $state['executions'] = array_slice($state['executions'], -self::MAX_CHAIN_EXECUTIONS);
        }

        return $this->withChainState($context, $state);
    }

    private function buildExecutionKey(string $eventType, int $ruleId): string
    {
        if ($ruleId <= 0) {
            return $eventType . '#unknown';
        }

        return $eventType . '#' . $ruleId;
    }

    /**
     * @param array{chain_id:string,depth:int,executions:list<string>} $state
     */
    private function isExecutionAlreadyProcessed(array $state, string $executionKey): bool
    {
        return in_array($executionKey, $state['executions'], true);
    }

    /**
     * @param array<string, mixed> $parentContext
     * @param array<string, mixed> $eventContext
     */
    private function emitEvent(string $eventType, int $orderId, array $parentContext, array $eventContext): void
    {
        $parentState = $this->getChainState($parentContext);
        if ($parentState['depth'] >= self::MAX_CHAIN_DEPTH) {
            return;
        }

        $childState = $parentState;
        $childState['depth'] = $parentState['depth'] + 1;

        $childContext = $this->withChainState($eventContext, $childState);
        $this->trigger($eventType, $orderId, $childContext);
    }

    private function generateChainId(): string
    {
        try {
            return bin2hex(random_bytes(8));
        } catch (Throwable) {
            return uniqid('chain_', true);
        }
    }

    /**
     * @param array<string, mixed> $context
     */
    private function logExecution(
        string $eventType,
        int $ruleId,
        string $ruleName,
        int $orderId,
        string $status,
        string $message,
        array $context
    ): void {
        if ($ruleId <= 0 || $orderId <= 0 || $ruleName === '') {
            return;
        }

        try {
            $this->executionLogs->create([
                'event_type' => $eventType,
                'rule_id' => $ruleId,
                'rule_name' => $ruleName,
                'order_id' => $orderId,
                'execution_status' => $status,
                'result_message' => mb_substr(trim($message), 0, 500),
                'context' => $this->sanitizeContext($context),
                'executed_at' => date('Y-m-d H:i:s'),
            ]);
        } catch (Throwable) {
            // Historia automatyzacji nie moze blokowac glownego flow.
        }
    }

    /**
     * @param array<string, mixed> $context
     * @return array<string, mixed>
     */
    private function sanitizeContext(array $context): array
    {
        $sanitized = [];
        foreach ($context as $key => $value) {
            if (is_scalar($value) || $value === null) {
                $sanitized[(string) $key] = $value;
                continue;
            }

            if (!is_array($value)) {
                continue;
            }

            $sanitized[(string) $key] = $this->sanitizeArray($value, 2);
        }

        return $sanitized;
    }

    /**
     * @param array<mixed> $value
     * @return array<mixed>
     */
    private function sanitizeArray(array $value, int $depth): array
    {
        if ($depth <= 0) {
            return [];
        }

        $sanitized = [];
        foreach ($value as $key => $item) {
            if (is_scalar($item) || $item === null) {
                $sanitized[$key] = $item;
                continue;
            }

            if (is_array($item)) {
                $sanitized[$key] = $this->sanitizeArray($item, $depth - 1);
            }
        }

        return $sanitized;
    }
}
