<?php

namespace App\Jobs;

use App\Models\Contract;
use App\Models\Email;
use App\Models\EmailLog;
use App\Models\EmailLogStatus;
use App\Models\EmailTemplate;
use App\Models\MailProfile;
use App\Models\Package;
use App\Models\PackageItem;
use App\Services\EmailSender;
use App\Services\EmailTemplateRenderer;
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\Str;

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

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

    public function handle(EmailTemplateRenderer $renderer, EmailSender $sender): 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;
        }

        if (in_array($item->status, ['sent', 'failed', 'canceled', 'skipped'], true)) {
            return;
        }

        if ($item->status === 'queued') {
            $item->status = 'processing';
            $item->save();
            $package->increment('processing_count');
        }

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

        $to = $target['email'] ?? null;
        if (! is_string($to) || ! filter_var($to, FILTER_VALIDATE_EMAIL)) {
            $item->status = 'failed';
            $item->last_error = 'Missing or invalid recipient email.';
            $item->save();
            $this->updatePackageCounters($item, $package);

            return;
        }

        $templateId = $payload['template_id'] ?? null;
        $mailProfileId = $payload['mail_profile_id'] ?? null;
        $variables = (array) ($payload['variables'] ?? []);
        $subjectOverride = isset($payload['subject']) ? trim((string) $payload['subject']) : null;
        if ($subjectOverride === '') {
            $subjectOverride = null;
        }
        $bodyText = isset($payload['body_text']) ? (string) $payload['body_text'] : '';

        // Enrich variables with contract/account context when available
        $contract = null;
        if (! empty($target['contract_id'])) {
            $contract = Contract::query()->with(['clientCase.person', '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 ?? ''),
                ];
                if (is_array($contract->meta) && ! empty($contract->meta)) {
                    $variables['contract']['meta'] = $this->flattenMeta($contract->meta);
                }
                if ($contract->account) {
                    $initialRaw = (string) $contract->account->initial_amount;
                    $balanceRaw = (string) $contract->account->balance_amount;
                    $variables['account'] = [
                        'id' => $contract->account->id,
                        'reference' => $contract->account->reference,
                        'initial_amount' => $this->formatAmountEu($initialRaw),
                        'balance_amount' => $this->formatAmountEu($balanceRaw),
                        'initial_amount_raw' => $initialRaw,
                        'balance_amount_raw' => $balanceRaw,
                        'type' => $contract->account->type?->name,
                    ];
                }
                if ($contract->clientCase?->person) {
                    $person = $contract->clientCase->person;
                    $variables['person'] = [
                        'full_name' => $person->full_name,
                        'first_name' => $person->first_name,
                        'last_name' => $person->last_name,
                    ];
                }
            }
        }

        /** @var EmailTemplate|null $template */
        $template = $templateId ? EmailTemplate::with(['action', 'decision'])->find((int) $templateId) : null;

        /** @var MailProfile|null $mailProfile */
        $mailProfile = $mailProfileId
            ? MailProfile::find((int) $mailProfileId)
            : MailProfile::query()->where('active', true)->orderBy('priority')->orderBy('id')->first();

        try {
            if (! $template && ! $subjectOverride) {
                throw new \RuntimeException('No email template or subject provided.');
            }

            $rendered = $template
                ? $renderer->render([
                    'subject' => $subjectOverride ?? (string) $template->subject_template,
                    'html' => (string) $template->html_template,
                    'text' => (string) $template->text_template,
                ], array_filter([
                    'contract' => $contract,
                    'person' => $contract?->clientCase?->person,
                    'client' => $contract?->clientCase?->client,
                    'client_case' => $contract?->clientCase,
                    'mail_profile' => $mailProfile,
                    'extra' => $variables,
                    'body_text' => $bodyText !== '' ? $bodyText : null,
                ]))
                : [
                    'subject' => $subjectOverride ?? '',
                    'html' => null,
                    'text' => null,
                ];

            $log = new EmailLog;
            $log->fill([
                'uuid' => (string) Str::uuid(),
                'template_id' => $template?->id,
                'mail_profile_id' => $mailProfile?->id,
                'to_email' => $to,
                'to_recipients' => [$to],
                'subject' => $rendered['subject'],
                'body_html_hash' => isset($rendered['html']) ? hash('sha256', (string) $rendered['html']) : null,
                'body_text_preview' => isset($rendered['text']) ? mb_strimwidth((string) $rendered['text'], 0, 4096) : null,
                'embed_mode' => 'base64',
                'status' => EmailLogStatus::Queued,
                'queued_at' => now(),
                'contract_id' => $contract?->id,
                'client_id' => $contract?->clientCase?->client?->id,
                'client_case_id' => $contract?->clientCase?->id,
                'extra_context' => ['package_id' => $item->package_id, 'package_item_id' => $item->id],
            ]);
            $log->save();

            $log->body()->create([
                'body_html' => (string) ($rendered['html'] ?? ''),
                'body_text' => (string) ($rendered['text'] ?? ''),
                'inline_css' => true,
            ]);

            // Send directly (synchronous within job context)
            $start = microtime(true);
            $log->status = EmailLogStatus::Sending;
            $log->started_at = now();
            $log->attempt = 1;
            $log->save();

            $sender->sendFromLog($log);

            $log->status = EmailLogStatus::Sent;
            $log->sent_at = now();
            $log->duration_ms = (int) round((microtime(true) - $start) * 1000);
            $log->save();

            $item->status = 'sent';
            $item->result_json = ['email_log_id' => $log->id, 'subject' => $rendered['subject']];
            $item->last_error = null;
            $item->save();

            // Clear failed flag on successful delivery
            Email::query()->where('value', $to)->where('failed', true)->update(['failed' => false]);

            // Create activity if the template has action/decision configured
            if ($template && ($template->action_id || $template->decision_id) && $contract && $contract->client_case_id) {
                $activity = \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' => 'Poslano: '.$to.', Uspešno'.($bodyText !== '' ? ' | Vsebina: '.mb_strimwidth($bodyText, 0, 500, '…') : ''),
                ]));
                $activity->emailLogs()->attach($log->id);
            }
        } catch (\Throwable $e) {
            $item->status = 'failed';
            $item->last_error = $e->getMessage();
            $item->save();

            // Create activity for failed send if the template has action/decision configured
            if ($template && ($template->action_id || $template->decision_id) && isset($contract) && $contract && $contract->client_case_id) {
                $shortError = mb_strimwidth($e->getMessage(), 0, 120, '…');
                $activity = \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' => 'Poslano: '.$to.', Napaka pri pošiljanju: '.$shortError.($bodyText !== '' ? ' | Vsebina: '.mb_strimwidth($bodyText, 0, 500, '…') : ''),
                ]));
                if (isset($log) && $log->exists) {
                    $activity->emailLogs()->attach($log->id);
                }
            }

            // Mark the email address as failed in the DB.
            if (isset($to)) {
                Email::query()
                    ->where('value', $to)
                    ->update(['failed' => true]);
            }

            // Permanent SMTP rejection (550 user unknown, 551 not local, 553 invalid address)
            // means the address definitively does not exist — also mark it invalid.
            if ($e instanceof \Symfony\Component\Mailer\Exception\TransportExceptionInterface
                && preg_match('/\b55[013]\b/', $e->getMessage())
                && isset($to)) {
                Email::query()
                    ->where('value', $to)
                    ->update(['valid' => false]);
            }
        }

        $this->updatePackageCounters($item, $package);
    }

    private function updatePackageCounters(PackageItem $item, Package $package): void
    {
        if ($item->status === 'sent') {
            $package->increment('sent_count');
        } else {
            $package->increment('failed_count');
        }

        $package->decrement('processing_count');

        $package->refresh();
        $done = $package->sent_count + $package->failed_count;
        if ($done >= $package->total_items) {
            $package->status = $package->failed_count > 0 ? Package::STATUS_FAILED : Package::STATUS_COMPLETED;
            $package->finished_at = now();
            $package->save();
        }
    }

    private function flattenMeta(array $meta, string $prefix = ''): array
    {
        $result = [];
        foreach ($meta as $key => $value) {
            $newKey = $prefix === '' ? $key : "{$prefix}.{$key}";
            if (is_array($value)) {
                if (isset($value['value'])) {
                    $result[$newKey] = $value['value'];
                } else {
                    $nested = $this->flattenMeta($value, $newKey);
                    $result = array_merge($result, $nested);
                }
            } else {
                $result[$newKey] = $value;
            }
        }

        return $result;
    }

    private function formatAmountEu(string $raw): string
    {
        $numeric = preg_replace('/[^0-9.]/', '', $raw);
        $float = (float) $numeric;

        return number_format($float, 2, ',', '.');
    }
}
