<?php

namespace App\Services;

use App\Helpers\NotificationHelper;
use App\Models\Form;
use App\Models\FormSubmission;
use App\Models\Franchisee;
use App\Models\Payment;
use App\Models\PaymentInvoice;
use App\Models\User;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

class PaymentNotificationService
{
    public function __construct(
        protected FormEmailService $formEmailService,
        protected FormNotificationLanguageResolver $languageResolver,
        protected RegistrationPricingHelper $pricingHelper
    ) {
    }

    /**
     * Entry point for payment webhooks and status sync.
     */
    public function notifyForPayment(
        Payment $payment,
        string $eventKey,
        ?FormSubmission $submission = null,
        ?PaymentInvoice $invoice = null,
        string $language = 'en'
    ): array {
        $dedupeKey = "payment_notification:{$payment->id}:{$eventKey}";
        if (Cache::has($dedupeKey)) {
            return ['sent' => true, 'source' => 'already_sent', 'skipped' => true];
        }

        $submission ??= FormSubmission::find($payment->registration_id);
        if (!$submission) {
            Log::warning('PaymentNotificationService: submission not found', [
                'registration_id' => $payment->registration_id,
                'event_key' => $eventKey,
            ]);

            return ['sent' => false, 'source' => 'none', 'reason' => 'submission_not_found'];
        }

        if (!$invoice && !empty($payment->payment_intent_id)) {
            $invoice = PaymentInvoice::where('payment_intent_id', $payment->payment_intent_id)->first();
        }

        $form = Form::find($submission->form_id);
        if (!$form) {
            Log::warning('PaymentNotificationService: form not found, using default payment templates', [
                'form_id' => $submission->form_id,
                'registration_id' => $payment->registration_id,
            ]);

            return $this->notifyUsingDefaultTemplatesOnly(
                $payment,
                $submission,
                $invoice,
                $eventKey,
                $language,
                $dedupeKey
            );
        }

        $originalSubmission = is_array($submission->submission_data) ? $submission->submission_data : [];
        $submissionData = $this->resolveSubmissionDataForEmail(
            $originalSubmission,
            $invoice?->class_and_customer_details
        );

        $franchiseLang = $this->languageResolver->resolveFranchisePreferredLanguage((string) $payment->franchisee_id);
        $language = $this->languageResolver->normalizeLanguage($language !== 'en' ? $language : $franchiseLang);

        $payload = $this->buildPaymentPayload(
            $submissionData,
            $payment,
            $invoice,
            $eventKey,
            $originalSubmission
        );
        $payload['language'] = $this->languageResolver->resolveParentLanguage($payload, $franchiseLang);

        $attachments = $this->buildReceiptAttachments($eventKey, $invoice);

        $result = $this->notify($form, $payment, $franchiseLang, $payload, $eventKey, $attachments);

        if (!empty($result['sent'])) {
            Cache::put($dedupeKey, true, now()->addDays(7));
        }

        return $result;
    }

    /**
     * Send built-in payment templates when the registration form record is missing or inactive.
     *
     * @return array<string, mixed>
     */
    private function notifyUsingDefaultTemplatesOnly(
        Payment $payment,
        FormSubmission $submission,
        ?PaymentInvoice $invoice,
        string $eventKey,
        string $language,
        string $dedupeKey,
    ): array {
        $originalSubmission = is_array($submission->submission_data) ? $submission->submission_data : [];
        $submissionData = $this->resolveSubmissionDataForEmail(
            $originalSubmission,
            $invoice?->class_and_customer_details
        );

        $franchiseLang = $this->languageResolver->resolveFranchisePreferredLanguage((string) $payment->franchisee_id);
        $language = $this->languageResolver->normalizeLanguage($language !== 'en' ? $language : $franchiseLang);

        $payload = $this->buildPaymentPayload(
            $submissionData,
            $payment,
            $invoice,
            $eventKey,
            $originalSubmission
        );
        $payload['language'] = $this->languageResolver->resolveParentLanguage($payload, $franchiseLang);
        $payload = $this->formEmailService->enrichPayloadWithContactFields(
            $payload,
            (string) $payment->franchisee_id
        );

        $attachments = $this->buildReceiptAttachments($eventKey, $invoice);
        $parentEmail = strtolower(trim((string) ($payload['parent_email'] ?? $payload['email'] ?? '')));
        $franchiseEmail = strtolower(trim((string) ($payload['franchise_email'] ?? '')));
        $parentLanguage = $this->languageResolver->resolveParentLanguage($payload, $franchiseLang);
        $allResults = [];

        if ($parentEmail !== '' && filter_var($parentEmail, FILTER_VALIDATE_EMAIL)) {
            $parentTemplates = $this->defaultTemplatesFor($eventKey, ['parent']);
            if ($parentTemplates !== []) {
                $allResults = array_merge(
                    $allResults,
                    $this->formEmailService->normalizeEmailResults(
                        $this->formEmailService->sendFromNotificationTemplates(
                            (string) $payment->franchisee_id,
                            $franchiseLang,
                            $payload,
                            $eventKey,
                            $parentTemplates,
                            $attachments,
                            $parentLanguage
                        )
                    )
                );
            }
        }

        if ($franchiseEmail !== '' && filter_var($franchiseEmail, FILTER_VALIDATE_EMAIL)
            && !$this->formEmailService->wasPaymentEmailSentTo($allResults, $franchiseEmail, $eventKey)) {
            $franchiseTemplates = $this->defaultTemplatesFor($eventKey, ['franchise']);
            if ($franchiseTemplates !== []) {
                $allResults = array_merge(
                    $allResults,
                    $this->formEmailService->normalizeEmailResults(
                        $this->formEmailService->sendFromNotificationTemplates(
                            (string) $payment->franchisee_id,
                            $franchiseLang,
                            $payload,
                            $eventKey,
                            $franchiseTemplates,
                            $attachments,
                            $parentLanguage
                        )
                    )
                );
            }
        }

        $sent = $this->wasAnyEmailSent($allResults);

        if ($sent) {
            Cache::put($dedupeKey, true, now()->addDays(7));
        }

        $this->maybeCreateInAppNotification($payment, $eventKey, $payload);

        return [
            'sent' => $sent,
            'source' => $sent ? 'default_templates' : 'none',
            'results' => $allResults,
        ];
    }

    /**
     * Franchise form → global form → guaranteed default emails per role.
     *
     * @param  array<string, mixed>  $submissionPayload
     * @param  array<int, array<string, mixed>>  $attachments
     * @return array<string, mixed>
     */
    public function notify(
        Form $form,
        Payment $payment,
        string $franchiseLanguage,
        array $submissionPayload,
        string $eventKey,
        array $attachments = []
    ): array {
        $franchiseeId = (string) $payment->franchisee_id;
        $submissionPayload = $this->formEmailService->enrichPayloadWithContactFields($submissionPayload, $franchiseeId);
        $parentLanguage = $this->languageResolver->resolveParentLanguage($submissionPayload, $franchiseLanguage);
        $allResults = [];

        $franchiseResults = $this->formEmailService->sendFromForm(
            $form,
            $franchiseeId,
            $franchiseLanguage,
            $submissionPayload,
            $attachments,
            $eventKey,
            false
        );
        $allResults = array_merge($allResults, $this->formEmailService->normalizeEmailResults($franchiseResults));

        $globalForm = $this->resolveGlobalForm($form);
        if ($globalForm && $globalForm->id !== $form->id) {
            $globalResults = $this->formEmailService->sendFromForm(
                $globalForm,
                $franchiseeId,
                $franchiseLanguage,
                $submissionPayload,
                $attachments,
                $eventKey,
                true
            );
            $allResults = array_merge($allResults, $this->formEmailService->normalizeEmailResults($globalResults));
        }

        $parentEmail = strtolower(trim((string) ($submissionPayload['parent_email'] ?? $submissionPayload['email'] ?? '')));
        $franchiseEmail = strtolower(trim((string) ($submissionPayload['franchise_email'] ?? '')));

        if ($parentEmail !== '' && filter_var($parentEmail, FILTER_VALIDATE_EMAIL)
            && !$this->formEmailService->wasPaymentEmailSentTo($allResults, $parentEmail, $eventKey)) {
            $parentTemplates = $this->defaultTemplatesFor($eventKey, ['parent']);
            if ($parentTemplates !== []) {
                $parentResults = $this->formEmailService->sendFromNotificationTemplates(
                    $franchiseeId,
                    $franchiseLanguage,
                    $submissionPayload,
                    $eventKey,
                    $parentTemplates,
                    $attachments,
                    $parentLanguage
                );
                $allResults = array_merge($allResults, $this->formEmailService->normalizeEmailResults($parentResults));
            }
        }

        if ($franchiseEmail !== '' && filter_var($franchiseEmail, FILTER_VALIDATE_EMAIL)
            && !$this->formEmailService->wasPaymentEmailSentTo($allResults, $franchiseEmail, $eventKey)) {
            $franchiseTemplates = $this->defaultTemplatesFor($eventKey, ['franchise']);
            if ($franchiseTemplates !== []) {
                $franchiseResults = $this->formEmailService->sendFromNotificationTemplates(
                    $franchiseeId,
                    $franchiseLanguage,
                    $submissionPayload,
                    $eventKey,
                    $franchiseTemplates,
                    $attachments,
                    $parentLanguage
                );
                $allResults = array_merge($allResults, $this->formEmailService->normalizeEmailResults($franchiseResults));
            }
        }

        $sent = $this->wasAnyEmailSent($allResults);

        Log::info('PaymentNotificationService: notification run complete', [
            'registration_id' => $payment->registration_id,
            'event_key' => $eventKey,
            'parent_email' => $parentEmail,
            'franchise_email' => $franchiseEmail,
            'parent_sent' => $parentEmail !== '' && $this->formEmailService->wasPaymentEmailSentTo($allResults, $parentEmail, $eventKey),
            'franchise_sent' => $franchiseEmail !== '' && $this->formEmailService->wasPaymentEmailSentTo($allResults, $franchiseEmail, $eventKey),
            'any_sent' => $sent,
        ]);

        if (!$sent) {
            Log::warning('PaymentNotificationService: no email templates sent', [
                'form_id' => $form->id,
                'event_key' => $eventKey,
                'registration_id' => $payment->registration_id,
            ]);
        }

        $this->maybeCreateInAppNotification($payment, $eventKey, $submissionPayload);

        return [
            'sent' => $sent,
            'source' => $sent ? 'multi' : 'none',
            'results' => $allResults,
        ];
    }

    /**
     * Merge invoice snapshot with submission; never let invoice blank out parent contact fields.
     *
     * @param  array<int|string, mixed>  $submissionData
     * @param  array<int|string, mixed>|null  $invoiceData
     * @return array<int|string, mixed>
     */
    private function resolveSubmissionDataForEmail(array $submissionData, ?array $invoiceData): array
    {
        if (!is_array($invoiceData) || $invoiceData === []) {
            return $submissionData;
        }

        if ($submissionData === []) {
            return $invoiceData;
        }

        $merged = array_replace_recursive($submissionData, $invoiceData);

        foreach (['email', 'parent_email', 'name', 'phone'] as $field) {
            $fromSubmission = $this->extractScalarFromSubmission($submissionData, $field);
            if ($fromSubmission !== null && $fromSubmission !== '') {
                $merged[$field] = $fromSubmission;
            }
        }

        return $merged;
    }

    /**
     * @param  array<int|string, mixed>  $data
     */
    private function extractScalarFromSubmission(array $data, string $field): ?string
    {
        if (isset($data[$field]) && is_scalar($data[$field]) && trim((string) $data[$field]) !== '') {
            return trim((string) $data[$field]);
        }

        foreach ($data as $index => $step) {
            if (!is_array($step)) {
                continue;
            }
            if (isset($step[$field]) && is_scalar($step[$field]) && trim((string) $step[$field]) !== '') {
                return trim((string) $step[$field]);
            }
            if (is_numeric($index) && isset($data[(string) $index.'.'.$field]) && is_scalar($data[(string) $index.'.'.$field])) {
                return trim((string) $data[(string) $index.'.'.$field]);
            }
        }

        return null;
    }

    /**
     * @param  array<int|string, mixed>  $submissionData
     * @param  array<int|string, mixed>  $rawSubmissionForDisplay
     * @return array<string, mixed>
     */
    private function buildPaymentPayload(
        array $submissionData,
        Payment $payment,
        ?PaymentInvoice $invoice,
        string $eventKey,
        array $rawSubmissionForDisplay = []
    ): array {
        $normalized = $this->normalizeSubmissionData($submissionData);
        $displayRaw = $rawSubmissionForDisplay !== [] ? $rawSubmissionForDisplay : $submissionData;

        $amount = (float) ($payment->amount ?? $invoice?->total ?? 0);
        $currency = strtoupper((string) ($payment->currency ?? $invoice?->currency ?? 'USD'));

        $programName = $this->extractField($normalized, ['program_name', 'class_details.program_name', '3.class_details.program_name'], 'your program');
        $childName = $this->extractField($normalized, ['childs_name', 'child_name', '1.childs_name'], '');
        $parentName = $this->extractField($normalized, ['name', 'parent_name', '0.name'], '');
        $paidAt = $invoice?->paid_at ?? now();
        $paidAtFormatted = $paidAt->format('Y-m-d H:i:s');
        $paidAtDisplay = $paidAt->format('M j, Y · g:i A');

        $paymentFields = [
            'registration_id' => (string) $payment->registration_id,
            'payment_id' => (string) $payment->id,
            'payment_status' => (string) ($payment->status ?? ''),
            'payment_event' => $eventKey,
            'amount' => $this->formatMoney($amount, $currency),
            'amount_due_now' => $this->formatMoney($amount, $currency),
            'amount_raw' => (string) $amount,
            'currency' => $currency,
            'invoice_no' => (string) ($invoice?->id ?? $payment->payment_intent_id ?? $payment->id ?? ''),
            'payment_method' => (string) ($invoice?->payment_method ?? $payment->payment_type ?? 'card'),
            'paid_at' => $paidAtFormatted,
            'payment_date' => $paidAtDisplay,
            'submission_date' => $paidAtFormatted,
            'program_name' => $programName,
            'group_name' => $this->extractField($normalized, ['group_name', 'class_details.group_name', '3.class_details.group_name'], ''),
            'childs_name' => $childName,
            'name' => $parentName !== '' ? $parentName : $childName,
            'franchisee_id' => (string) $payment->franchisee_id,
        ];

        $payload = array_merge(['raw' => $displayRaw], $normalized, $paymentFields);
        $payload = $this->pricingHelper->enrich($payload);

        return $this->formEmailService->enrichPayloadWithContactFields(
            $payload,
            (string) $payment->franchisee_id
        );
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function buildReceiptAttachments(string $eventKey, ?PaymentInvoice $invoice): array
    {
        if ($eventKey !== 'payment_success' || !$invoice) {
            return [];
        }

        try {
            $pdf = Pdf::loadView('pdf.invoice', ['invoice' => $invoice]);

            return [[
                '@odata.type' => '#microsoft.graph.fileAttachment',
                'name' => 'receipt-'.$invoice->id.'.pdf',
                'contentType' => 'application/pdf',
                'contentBytes' => base64_encode($pdf->output()),
            ]];
        } catch (\Throwable $e) {
            Log::warning('PaymentNotificationService: receipt PDF failed', [
                'invoice_id' => $invoice->id,
                'error' => $e->getMessage(),
            ]);

            return [];
        }
    }

    /**
     * @param  list<string>  $missingTypes  parent|franchise
     * @return array<int, array<string, mixed>>
     */
    private function defaultTemplatesFor(string $eventKey, array $missingTypes): array
    {
        $all = [
            'payment_success' => [
                'parent' => [
                    'to_email' => '{parent_email}',
                    'subject' => 'Payment received — {program_name}',
                    'message' => '{payment_confirmation_body}',
                ],
                'franchise' => [
                    'to_email' => '{franchise_email}',
                    'subject' => 'Payment succeeded — {program_name} ({invoice_no})',
                    'message' => '<p>A payment of <strong>{amount}</strong> was received for registration <strong>{registration_id}</strong>.</p>{payment_details}',
                ],
            ],
            'payment_pending' => [
                'parent' => [
                    'to_email' => '{parent_email}',
                    'subject' => 'Payment processing — {program_name}',
                    'message' => '<p>Hi {name},</p><p>Your payment of <strong>{amount}</strong> for <strong>{program_name}</strong> is being processed.</p>{payment_details}',
                ],
                'franchise' => [
                    'to_email' => '{franchise_email}',
                    'subject' => 'Payment processing — {registration_id}',
                    'message' => '<p>Payment is processing for registration <strong>{registration_id}</strong> ({amount}).</p>{payment_details}',
                ],
            ],
            'payment_failed' => [
                'parent' => [
                    'to_email' => '{parent_email}',
                    'subject' => 'Payment could not be completed — {program_name}',
                    'message' => '<p>Hi {name},</p><p>Your payment of <strong>{amount}</strong> for <strong>{program_name}</strong> could not be completed.</p>{payment_details}',
                ],
                'franchise' => [
                    'to_email' => '{franchise_email}',
                    'subject' => 'Payment failed — {registration_id}',
                    'message' => '<p>Payment failed for registration <strong>{registration_id}</strong> ({amount}).</p>{payment_details}',
                ],
            ],
        ];

        $eventTemplates = $all[$eventKey] ?? [];
        $templates = [];
        foreach ($missingTypes as $type) {
            if (isset($eventTemplates[$type])) {
                $templates[] = $eventTemplates[$type];
            }
        }

        return $templates;
    }

    private function resolveGlobalForm(Form $form): ?Form
    {
        if ($form->is_global) {
            return $form->loadMissing('notifications.translations');
        }

        if ($form->parent_form_id) {
            return Form::with('notifications.translations')->find($form->parent_form_id);
        }

        return Form::with('notifications.translations')
            ->where('is_global', true)
            ->whereNull('franchisee_id')
            ->orderBy('created_at')
            ->first();
    }

    private function wasAnyEmailSent(mixed $results): bool
    {
        if (!is_array($results)) {
            return false;
        }

        if (isset($results['sent'])) {
            return (bool) $results['sent'];
        }

        foreach ($results as $row) {
            if (is_array($row) && !empty($row['sent'])) {
                return true;
            }
        }

        return false;
    }

    /**
     * Flatten stepped registration payloads so pricing helpers can read keys like
     * `class_details.price`, `3.amount_due_now`, etc.
     *
     * Previously we only flattened strict 0..n-1 lists. After merging invoice snapshots
     * (`array_replace_recursive`), top-level keys are often mixed (0,1,2,email,…),
     * so flattening was skipped and payment emails showed $0 for class totals.
     *
     * @param  array<int|string, mixed>  $submissionData
     * @return array<string, mixed>
     */
    private function normalizeSubmissionData(array $submissionData): array
    {
        if ($submissionData === []) {
            return [];
        }

        $flat = [];

        foreach ($submissionData as $index => $step) {
            if (is_scalar($step) || $step === null) {
                $flat[is_string($index) ? $index : (string) $index] = $step;

                continue;
            }

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

            $idx = null;
            if (is_int($index)) {
                $idx = $index;
            } elseif (is_string($index) && ctype_digit($index)) {
                $idx = (int) $index;
            }

            foreach ($step as $key => $value) {
                if ($key === 'class_details' && is_array($value)) {
                    foreach ($value as $subKey => $subValue) {
                        if (is_scalar($subValue) || $subValue === null) {
                            $flat['class_details.'.$subKey] = $subValue;
                            if ($idx !== null) {
                                $flat[$idx.'.class_details.'.$subKey] = $subValue;
                            }
                        }
                    }

                    continue;
                }

                if (is_scalar($value) || $value === null) {
                    $flat[$key] = $value;
                    if ($idx !== null) {
                        $flat[$idx.'.'.$key] = $value;
                    }
                } elseif (is_array($value)) {
                    $flat[$key] = $value;
                    foreach ($value as $subKey => $subValue) {
                        if (is_scalar($subValue) || $subValue === null) {
                            $flat[$key.'.'.$subKey] = $subValue;
                            if ($idx !== null) {
                                $flat[$idx.'.'.$key.'.'.$subKey] = $subValue;
                            }
                        }
                    }
                }
            }
        }

        return $flat;
    }

    /**
     * @param  array<string, mixed>  $data
     * @param  list<string>  $paths
     */
    private function extractField(array $data, array $paths, string $fallback): string
    {
        foreach ($paths as $path) {
            $val = $this->getByPath($data, $path);
            if (is_string($val) && trim($val) !== '') {
                return strip_tags(trim($val));
            }
        }

        return $fallback;
    }

    /**
     * @param  array<string, mixed>  $data
     */
    private function getByPath(array $data, string $path): mixed
    {
        if (array_key_exists($path, $data)) {
            return $data[$path];
        }

        $current = $data;
        foreach (explode('.', $path) as $part) {
            if (!is_array($current) || !array_key_exists($part, $current)) {
                return null;
            }
            $current = $current[$part];
        }

        return $current;
    }

    private function formatMoney(float|int|string $amount, string $currency): string
    {
        $value = is_numeric($amount) ? (float) $amount : 0.0;
        $symbol = match ($currency) {
            'USD' => '$',
            'EUR' => '€',
            'GBP' => '£',
            default => $currency.' ',
        };

        $formatted = number_format($value, fmod($value, 1.0) === 0.0 ? 0 : 2);

        return $symbol === '$' || $symbol === '€' || $symbol === '£'
            ? $symbol.$formatted
            : trim($symbol.$formatted);
    }

    /**
     * @param  array<string, mixed>  $submissionPayload
     */
    private function maybeCreateInAppNotification(Payment $payment, string $eventKey, array $submissionPayload): void
    {
        if (empty($payment->franchisee_id)) {
            return;
        }

        $admin = User::where('franchisee_id', $payment->franchisee_id)
            ->whereIn('role', ['franchise_admin', 'franchisee_admin', 'admin'])
            ->first();

        if (!$admin) {
            return;
        }

        $amount = $submissionPayload['amount'] ?? '';
        $program = $submissionPayload['program_name'] ?? 'program';

        $message = match ($eventKey) {
            'payment_success' => "Payment received ({$amount}) for {$program} — registration {$payment->registration_id}",
            'payment_failed' => "Payment failed ({$amount}) for {$program} — registration {$payment->registration_id}",
            'payment_pending' => "Payment processing ({$amount}) for {$program} — registration {$payment->registration_id}",
            default => "Payment update ({$eventKey}) for registration {$payment->registration_id}",
        };

        try {
            NotificationHelper::send(
                type: $eventKey,
                message: $message,
                data: [
                    'payment_id' => $payment->id,
                    'registration_id' => $payment->registration_id,
                    'amount' => $payment->amount,
                    'currency' => $payment->currency,
                    'status' => $payment->status,
                ],
                franchiseeId: $payment->franchisee_id,
                senderId: 'stripe',
                receiverId: (string) $admin->id,
                status: 'Pending',
            );
        } catch (\Throwable $e) {
            Log::warning('PaymentNotificationService: in-app notification failed', [
                'error' => $e->getMessage(),
                'payment_id' => $payment->id,
            ]);
        }
    }
}
