<?php

namespace App\Jobs;

use App\Models\Contract;
use App\Models\Package;
use App\Models\PackageItem;
use App\Models\SmsProfile;
use App\Models\SmsSender;
use App\Models\SmsTemplate;
use App\Services\Sms\SmsService;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Redis;

class PackageItemSmsJob implements ShouldQueue
{
    use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(public int $packageItemId)
    {
        $this->onQueue('sms');
    }

    public function handle(SmsService $sms): void
    {
        /** @var PackageItem|null $item */
        $item = PackageItem::query()->find($this->packageItemId);
        if (! $item) {
            return;
        }

        /** @var Package $package */
        $package = $item->package;
        if (! $package || $package->status === Package::STATUS_CANCELED) {
            return; // canceled or missing
        }

        // Skip if already finalized to avoid double counting on retries
        if (in_array($item->status, ['sent', 'failed', 'canceled', 'skipped'], true)) {
            return;
        }
        // Mark processing on first entry
        if ($item->status === 'queued') {
            $item->status = 'processing';
            $item->save();
            $package->increment('processing_count');
        }

        $payload = (array) $item->payload_json;
        $target = (array) $item->target_json;

        $profileId = $payload['profile_id'] ?? null;
        $senderId = $payload['sender_id'] ?? null;
        $templateId = $payload['template_id'] ?? null;
        $deliveryReport = (bool) ($payload['delivery_report'] ?? false);
        $variables = (array) ($payload['variables'] ?? []);
        // Enrich variables with contract/account context when available (contracts-based packages)
        if (! empty($target['contract_id'])) {
            $contract = Contract::query()->with('account.type')->find($target['contract_id']);
            if ($contract) {
                $variables['contract'] = [
                    'id' => $contract->id,
                    'uuid' => $contract->uuid,
                    'reference' => $contract->reference,
                    'start_date' => (string) ($contract->start_date ?? ''),
                    'end_date' => (string) ($contract->end_date ?? ''),
                ];
                // Include contract.meta as flattened key-value pairs for template access
                if (is_array($contract->meta) && ! empty($contract->meta)) {
                    $variables['contract']['meta'] = $this->flattenMeta($contract->meta);
                }
                if ($contract->account) {
                    // Preserve raw values and provide EU-formatted versions for SMS rendering
                    $initialRaw = (string) $contract->account->initial_amount;
                    $balanceRaw = (string) $contract->account->balance_amount;
                    $variables['account'] = [
                        'id' => $contract->account->id,
                        'reference' => $contract->account->reference,
                        // Override placeholders with EU formatted values for SMS
                        'initial_amount' => $sms->formatAmountEu($initialRaw),
                        'balance_amount' => $sms->formatAmountEu($balanceRaw),
                        // Expose raw values too in case templates need them explicitly
                        'initial_amount_raw' => $initialRaw,
                        'balance_amount_raw' => $balanceRaw,
                        'type' => $contract->account->type?->name,
                    ];
                }
            }
        }
        $bodyOverride = isset($payload['body']) ? trim((string) $payload['body']) : null;
        if ($bodyOverride === '') {
            $bodyOverride = null;
        }

        /** @var SmsProfile|null $profile */
        $profile = $profileId ? SmsProfile::find($profileId) : null;
        /** @var SmsSender|null $sender */
        $sender = $senderId ? SmsSender::find($senderId) : null;
        /** @var SmsTemplate|null $template */
        $template = $templateId ? SmsTemplate::with(['action', 'decision'])->find($templateId) : null;

        $to = $target['number'] ?? null;
        if (! is_string($to) || $to === '') {
            $item->status = 'failed';
            $item->last_error = 'Missing recipient number.';
            $item->save();

            return;
        }

        // Compute throttle key
        $scope = config('services.sms.throttle.scope', 'global');
        $provider = config('services.sms.throttle.provider_key', 'smsapi_si');
        $allow = (int) config('services.sms.throttle.allow', 30);
        $every = (int) config('services.sms.throttle.every', 60);
        $jitter = (int) config('services.sms.throttle.jitter_seconds', 2);
        $key = $scope === 'per_profile' && $profile ? "sms:{$provider}:{$profile->id}" : "sms:{$provider}";

        // Throttle
        $sendClosure = function () use ($sms, $item, $package, $profile, $sender, $template, $to, $variables, $deliveryReport, $bodyOverride, $target) {
            // Idempotency key (optional external use)
            if (empty($item->idempotency_key)) {
                $hash = sha1(implode('|', [
                    'sms', (string) ($profile?->id ?? ''), (string) ($sender?->id ?? ''), (string) ($template?->id ?? ''), $to, (string) ($bodyOverride ?? ''), json_encode($variables),
                ]));
                $item->idempotency_key = "pkgitem:{$item->id}:{$hash}";
                $item->save();
            }

            // Decide whether to use template or raw content
            $useTemplate = false;
            if ($template) {
                if ($bodyOverride) {
                    // If custom body is provided but template does not allow it, force template
                    $useTemplate = ! (bool) ($template->allow_custom_body ?? false);
                } else {
                    // No custom body provided -> use template
                    $useTemplate = true;
                }
            }

            if ($useTemplate) {
                $log = $sms->sendFromTemplate(
                    template: $template,
                    to: $to,
                    variables: $variables,
                    profile: $profile,
                    sender: $sender,
                    countryCode: null,
                    deliveryReport: $deliveryReport,
                    clientReference: "pkg:{$item->package_id}:item:{$item->id}",
                );
            } else {
                // Either explicit body override or no template
                $effectiveBody = (string) ($bodyOverride ?? '');
                if ($effectiveBody === '') {
                    // Avoid provider error for empty body
                    throw new \RuntimeException('Empty SMS body and no template provided.');
                }
                if (! $profile && $template) {
                    $profile = $template->defaultProfile;
                }
                if (! $profile) {
                    throw new \RuntimeException('Missing SMS profile for raw send.');
                }
                $log = $sms->sendRaw(
                    profile: $profile,
                    to: $to,
                    content: $effectiveBody,
                    sender: $sender,
                    countryCode: null,
                    deliveryReport: $deliveryReport,
                    clientReference: "pkg:{$item->package_id}:item:{$item->id}",
                );
            }

            $newStatus = $log->status === 'sent' ? 'sent' : 'failed';
            $item->status = $newStatus;
            $item->provider_message_id = $log->provider_message_id;
            $item->cost = $log->cost;
            $item->currency = $log->currency;
            // Persist useful result info including final rendered message for auditing
            $result = $log->meta ?? [];
            $result['message'] = $log->message ?? (($useTemplate && isset($template)) ? $sms->renderContent($template->content, $variables) : ($bodyOverride ?? null));
            $result['template_id'] = $template?->id;
            $result['render_source'] = $useTemplate ? 'template' : 'body';
            $item->result_json = $result;
            $item->last_error = $log->status === 'sent' ? null : ($log->meta['error_message'] ?? 'Failed');
            $item->save();

            // Create activity if template has action_id and decision_id configured and SMS was sent successfully
            if ($newStatus === 'sent' && $template && ($template->action_id || $template->decision_id)) {
                if (! empty($target['contract_id'])) {
                    $contract = Contract::query()->with('clientCase')->find($target['contract_id']);

                    if ($contract && $contract->client_case_id) {
                        \App\Models\Activity::create(array_filter([
                            'client_case_id' => $contract->client_case_id,
                            'contract_id' => $contract->id,
                            'action_id' => $template->action_id,
                            'decision_id' => $template->decision_id,
                            'note' => "SMS poslan na {$to}: {$result['message']}",
                            'created_at' => now(),
                            'updated_at' => now(),
                        ]));
                    }
                }
            }

            // Update package counters atomically
            if ($newStatus === 'sent') {
                $package->increment('sent_count');
            } else {
                $package->increment('failed_count');
            }

            // If all items processed, finalize package
            $package->refresh();
            if (($package->sent_count + $package->failed_count) >= $package->total_items) {
                $finalStatus = $package->failed_count > 0 ? Package::STATUS_FAILED : Package::STATUS_COMPLETED;
                $package->status = $finalStatus;
                $package->finished_at = now();
                $package->save();
            }
        };

        try {
            Redis::throttle($key)->allow($allow)->every($every)->then($sendClosure, function () use ($jitter) {
                return $this->release(max(1, rand(1, $jitter)));
            });
        } catch (\Throwable $e) {
            // Fallback to direct send when Redis unavailable (e.g., test environment)
            $sendClosure();
        }
    }

    /**
     * Flatten nested meta structure into dot-notation key-value pairs.
     * Extracts 'value' from objects with {title, value, type} structure.
     * Also creates direct access aliases for nested fields (skipping numeric keys).
     */
    private function flattenMeta(array $meta, string $prefix = ''): array
    {
        $result = [];
        foreach ($meta as $key => $value) {
            $newKey = $prefix === '' ? $key : "{$prefix}.{$key}";

            if (is_array($value)) {
                // Check if it's a structured meta entry with 'value' field
                if (isset($value['value'])) {
                    $result[$newKey] = $value['value'];
                    // If parent key is numeric, also create direct alias without the number
                    if ($prefix !== '' && is_numeric($key)) {
                        $result[$key] = $value['value'];
                    }
                } else {
                    // Recursively flatten nested arrays
                    $nested = $this->flattenMeta($value, $newKey);
                    $result = array_merge($result, $nested);

                    // If current key is numeric, also flatten without it for easier access
                    if (is_numeric($key)) {
                        $directNested = $this->flattenMeta($value, $prefix);
                        foreach ($directNested as $dk => $dv) {
                            // Only add if not already set (prefer first occurrence)
                            if (! isset($result[$dk])) {
                                $result[$dk] = $dv;
                            }
                        }
                    }
                }
            } else {
                $result[$newKey] = $value;
            }
        }

        return $result;
    }
}
