<?php

namespace App\Services\Checkout;

use App\Mail\PurchaseConfirmation;
use App\Models\AbandonedCheckout;
use App\Models\CheckoutSession;
use App\Models\Package;
use App\Models\PackagePurchase;
use App\Models\Tenant;
use App\Models\TenantPackage;
use App\Models\User;
use App\Notifications\Ops\PurchaseCreated;
use Illuminate\Auth\Events\Registered;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Str;

class CheckoutAssignmentService
{
    /**
     * Persist the purchase artefacts for a completed checkout session.
     *
     * @param  array{provider_reference?: string, payload?: array}  $options
     */
    public function finalise(CheckoutSession $session, array $options = []): void
    {
        DB::transaction(function () use ($session, $options) {
            $tenant = $session->tenant;
            $user = $session->user;

            if (! $tenant && $user) {
                $tenant = $this->ensureTenant($user, $session);
            }

            if (! $tenant) {
                Log::warning('Checkout assignment skipped: missing tenant', ['session' => $session->id]);

                return;
            }

            $package = $session->package;
            if (! $package) {
                Log::warning('Checkout assignment skipped: missing package', ['session' => $session->id]);

                return;
            }

            $metadata = $session->provider_metadata ?? [];
            $consents = [
                'accepted_terms_at' => optional($session->accepted_terms_at)->toIso8601String(),
                'accepted_privacy_at' => optional($session->accepted_privacy_at)->toIso8601String(),
                'accepted_withdrawal_notice_at' => optional($session->accepted_withdrawal_notice_at)->toIso8601String(),
                'digital_content_waiver_at' => optional($session->digital_content_waiver_at)->toIso8601String(),
                'legal_version' => $session->legal_version,
            ];
            $consents = array_filter($consents);

            $providerReference = $options['provider_reference']
                ?? $metadata['paddle_transaction_id'] ?? null
                ?? $metadata['paddle_checkout_id'] ?? null
                ?? CheckoutSession::PROVIDER_FREE;

            $providerName = $options['provider']
                ?? $session->provider
                ?? ($metadata['paddle_transaction_id'] ?? $metadata['paddle_checkout_id'] ? CheckoutSession::PROVIDER_PADDLE : null)
                ?? CheckoutSession::PROVIDER_FREE;

            $totals = $this->resolvePaddleTotals($session, $options['payload'] ?? []);
            $currency = $totals['currency'] ?? $session->currency ?? $package->currency ?? 'EUR';
            $price = array_key_exists('total', $totals) ? $totals['total'] : (float) $session->amount_total;

            $purchase = PackagePurchase::updateOrCreate(
                [
                    'tenant_id' => $tenant->id,
                    'package_id' => $package->id,
                    'provider_id' => $providerReference,
                ],
                [
                    'provider' => $providerName,
                    'price' => round($price, 2),
                    'type' => $package->type === 'reseller' ? 'reseller_subscription' : 'endcustomer_event',
                    'purchased_at' => now(),
                    'metadata' => array_filter([
                        'payload' => $options['payload'] ?? null,
                        'checkout_session_id' => $session->id,
                        'consents' => $consents ?: null,
                        'paddle_totals' => $totals !== [] ? $totals : null,
                        'currency' => $currency,
                    ], static fn ($value) => $value !== null && $value !== ''),
                ]
            );

            if ($package->type === 'reseller') {
                $tenantPackage = null;

                if ($purchase->wasRecentlyCreated) {
                    $tenantPackage = TenantPackage::create([
                        'tenant_id' => $tenant->id,
                        'package_id' => $package->id,
                        'price' => round($price, 2),
                        'active' => true,
                        'purchased_at' => now(),
                        'expires_at' => null,
                        'used_events' => 0,
                    ]);
                }
            } else {
                $tenantPackage = TenantPackage::updateOrCreate(
                    [
                        'tenant_id' => $tenant->id,
                        'package_id' => $package->id,
                    ],
                    [
                        'price' => round($price, 2),
                        'active' => true,
                        'purchased_at' => now(),
                        'expires_at' => $this->resolveExpiry($package, $tenant),
                    ]
                );
            }

            if ($package->type !== 'reseller') {
                $tenant->forceFill([
                    'subscription_status' => 'active',
                    'subscription_expires_at' => $tenantPackage->expires_at,
                ])->save();
            }

            if ($user && $user->pending_purchase) {
                $this->activateUser($user);
            }

            if ($user) {
                $mailLocale = $user->preferred_locale ?? app()->getLocale();

                if ($purchase->wasRecentlyCreated) {
                    Mail::to($user)
                        ->locale($mailLocale)
                        ->queue(new PurchaseConfirmation($purchase));

                    $opsEmail = config('mail.ops_address');
                    if ($opsEmail) {
                        Notification::route('mail', $opsEmail)->notify(new PurchaseCreated($purchase));
                    }
                }

                AbandonedCheckout::query()
                    ->where('user_id', $user->id)
                    ->where('package_id', $package->id)
                    ->where('converted', false)
                    ->update([
                        'converted' => true,
                        'reminder_stage' => 'converted',
                    ]);
            }

            Log::info('Checkout session assigned', [
                'session' => $session->id,
                'tenant' => $tenant->id,
                'package' => $package->id,
                'purchase' => $purchase->id,
            ]);
        });
    }

    protected function ensureTenant(User $user, CheckoutSession $session): ?Tenant
    {
        if ($user->tenant) {
            if (! $user->tenant_id) {
                $user->forceFill(['tenant_id' => $user->tenant->getKey()])->save();
            }

            return $user->tenant;
        }

        $tenant = Tenant::create([
            'user_id' => $user->id,
            'name' => $session->package_snapshot['name'] ?? $user->name,
            'slug' => Str::slug(($user->name ?: $user->email).' '.now()->timestamp),
            'email' => $user->email,
            'contact_email' => $user->email,
            'is_active' => true,
            'is_suspended' => false,
            'subscription_tier' => 'free',
            'subscription_status' => 'active',
            'settings' => [
                'contact_email' => $user->email,
            ],
        ]);

        if ($user->tenant_id !== $tenant->id) {
            $user->forceFill(['tenant_id' => $tenant->id])->save();
        }

        event(new Registered($user));

        return $tenant;
    }

    protected function resolveExpiry(Package $package, Tenant $tenant)
    {
        if ($package->type === 'reseller') {
            return null;
        }

        return now()->addYear();
    }

    protected function activateUser(User $user): void
    {
        $user->forceFill([
            'email_verified_at' => $user->email_verified_at ?? now(),
            'role' => $user->role === 'user' ? 'tenant_admin' : $user->role,
            'pending_purchase' => false,
        ])->save();
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array{currency?: string, subtotal?: float, discount?: float, tax?: float, total?: float}
     */
    protected function resolvePaddleTotals(CheckoutSession $session, array $payload): array
    {
        $metadataTotals = $session->provider_metadata['paddle_totals'] ?? null;

        if (is_array($metadataTotals) && $metadataTotals !== []) {
            return $metadataTotals;
        }

        $totals = Arr::get($payload, 'details.totals', Arr::get($payload, 'totals', []));
        if (! is_array($totals) || $totals === []) {
            return [];
        }

        $currency = Arr::get($totals, 'currency_code')
            ?? Arr::get($payload, 'currency_code')
            ?? Arr::get($totals, 'currency')
            ?? Arr::get($payload, 'currency');

        $subtotal = $this->convertMinorAmount(Arr::get($totals, 'subtotal.amount', $totals['subtotal'] ?? null));
        $discount = $this->convertMinorAmount(Arr::get($totals, 'discount.amount', $totals['discount'] ?? null));
        $tax = $this->convertMinorAmount(Arr::get($totals, 'tax.amount', $totals['tax'] ?? null));
        $total = $this->convertMinorAmount(
            Arr::get(
                $totals,
                'total.amount',
                $totals['total'] ?? Arr::get($totals, 'grand_total.amount', $totals['grand_total'] ?? null)
            )
        );

        return array_filter([
            'currency' => $currency ? strtoupper((string) $currency) : null,
            'subtotal' => $subtotal,
            'discount' => $discount,
            'tax' => $tax,
            'total' => $total,
        ], static fn ($value) => $value !== null);
    }

    protected function convertMinorAmount(mixed $value): ?float
    {
        if ($value === null || $value === '') {
            return null;
        }

        if (is_array($value) && isset($value['amount'])) {
            $value = $value['amount'];
        }

        if (! is_numeric($value)) {
            return null;
        }

        return round(((float) $value) / 100, 2);
    }
}
