<?php

namespace App\Services\GiftVouchers;

use App\Enums\CouponStatus;
use App\Enums\CouponType;
use App\Jobs\NotifyGiftVoucherReminder;
use App\Jobs\SyncCouponToPaddle;
use App\Mail\GiftVoucherIssued;
use App\Models\Coupon;
use App\Models\GiftVoucher;
use App\Models\Package;
use App\Services\Paddle\PaddleTransactionService;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class GiftVoucherService
{
    public function __construct(private readonly PaddleTransactionService $transactions) {}

    /**
     * Create a voucher from a Paddle transaction payload.
     */
    public function issueFromPaddle(array $payload): GiftVoucher
    {
        $metadata = $this->extractCustomData($payload);
        $priceId = $this->resolvePriceId($payload);
        $amount = $this->resolveAmount($payload);
        $currency = Str::upper($this->resolveCurrency($payload));
        $locale = $metadata['app_locale'] ?? app()->getLocale();
        $existing = null;

        $expiresAt = now()->addYears((int) config('gift-vouchers.default_valid_years', 5));

        if (! empty($payload['id'])) {
            $existing = GiftVoucher::query()
                ->where('paddle_transaction_id', $payload['id'])
                ->first();
        }

        $mergedMetadata = array_merge($existing?->metadata ?? [], $metadata);

        $voucher = GiftVoucher::query()->updateOrCreate(
            [
                'paddle_transaction_id' => $payload['id'] ?? null,
            ],
            [
                'code' => $metadata['gift_code'] ?? $this->generateCode(),
                'amount' => $amount,
                'currency' => $currency,
                'status' => GiftVoucher::STATUS_ISSUED,
                'purchaser_email' => $metadata['purchaser_email'] ?? Arr::get($payload, 'customer.email'),
                'recipient_email' => $metadata['recipient_email'] ?? null,
                'recipient_name' => $metadata['recipient_name'] ?? null,
                'message' => $metadata['message'] ?? null,
                'paddle_checkout_id' => $payload['checkout_id'] ?? Arr::get($payload, 'details.checkout_id'),
                'paddle_price_id' => $priceId,
                'metadata' => $mergedMetadata,
                'expires_at' => $expiresAt,
                'refunded_at' => null,
                'redeemed_at' => null,
            ]
        );

        if (! $voucher->coupon_id) {
            $coupon = $this->createCouponForVoucher($voucher);
            $voucher->forceFill(['coupon_id' => $coupon->id])->save();
            SyncCouponToPaddle::dispatch($coupon);
        }

        $notificationsSent = (bool) Arr::get($voucher->metadata ?? [], 'notifications_sent', false);

        if (! $notificationsSent) {
            $this->sendNotifications($voucher, locale: $locale);
        }

        return $voucher;
    }

    public function resend(GiftVoucher $voucher, ?string $locale = null, ?bool $recipientOnly = null): void
    {
        $this->sendNotifications($voucher, force: true, locale: $locale, recipientOnly: $recipientOnly);
    }

    public function scheduleRecipientDelivery(GiftVoucher $voucher, Carbon $when, ?string $locale = null): void
    {
        $voucher->forceFill([
            'recipient_delivery_scheduled_at' => $when,
        ])->save();

        $this->sendNotifications($voucher, force: true, when: $when, locale: $locale, recipientOnly: true);
    }

    public function markRedeemed(?Coupon $coupon, ?string $transactionId = null): void
    {
        if (! $coupon?->giftVoucher) {
            return;
        }

        $voucher = $coupon->giftVoucher;

        if ($voucher->isRedeemed()) {
            return;
        }

        $voucher->forceFill([
            'status' => GiftVoucher::STATUS_REDEEMED,
            'redeemed_at' => now(),
            'metadata' => array_merge($voucher->metadata ?? [], array_filter([
                'redeemed_transaction_id' => $transactionId,
            ])),
        ])->save();
    }

    /**
     * @return array<string, mixed>
     */
    public function refund(GiftVoucher $voucher, ?string $reason = null): array
    {
        if (! $voucher->canBeRefunded()) {
            throw ValidationException::withMessages([
                'voucher' => __('Voucher cannot be refunded after redemption or refund.'),
            ]);
        }

        if (! $voucher->paddle_transaction_id) {
            throw ValidationException::withMessages([
                'voucher' => __('Missing Paddle transaction for refund.'),
            ]);
        }

        $response = $this->transactions->refund($voucher->paddle_transaction_id, array_filter([
            'reason' => $reason,
        ]));

        $voucher->forceFill([
            'status' => GiftVoucher::STATUS_REFUNDED,
            'refunded_at' => now(),
        ])->save();

        if ($voucher->coupon) {
            $voucher->coupon->forceFill([
                'status' => CouponStatus::ARCHIVED,
                'enabled_for_checkout' => false,
            ])->save();
        }

        return $response;
    }

    protected function createCouponForVoucher(GiftVoucher $voucher): Coupon
    {
        $packages = $this->eligiblePackages();

        $coupon = Coupon::create([
            'name' => 'Gutschein '.$voucher->code,
            'code' => $voucher->code,
            'type' => CouponType::FLAT,
            'amount' => $voucher->amount,
            'currency' => $voucher->currency,
            'status' => CouponStatus::ACTIVE,
            'enabled_for_checkout' => true,
            'is_stackable' => false,
            'usage_limit' => 1,
            'per_customer_limit' => 1,
            'auto_apply' => false,
            'description' => 'Geschenkgutschein '.number_format((float) $voucher->amount, 2).' '.$voucher->currency.' für Endkunden-Pakete.',
            'starts_at' => now(),
            'ends_at' => $voucher->expires_at,
        ]);

        if ($packages->isNotEmpty()) {
            $coupon->packages()->sync($packages->pluck('id'));
        }

        return $coupon;
    }

    protected function eligiblePackages(): Collection
    {
        $types = (array) config('gift-vouchers.package_types', ['endcustomer']);

        return Package::query()
            ->whereIn('type', $types)
            ->whereNotNull('paddle_price_id')
            ->get(['id']);
    }

    protected function resolvePriceId(array $payload): ?string
    {
        $metadata = $this->extractCustomData($payload);

        if (is_array($metadata) && ! empty($metadata['paddle_price_id'])) {
            return $metadata['paddle_price_id'];
        }

        $items = Arr::get($payload, 'items', Arr::get($payload, 'details.items', []));
        if (is_array($items) && isset($items[0]['price_id'])) {
            return $items[0]['price_id'];
        }

        return $payload['price_id'] ?? null;
    }

    protected function resolveAmount(array $payload): float
    {
        $tiers = Collection::make(config('gift-vouchers.tiers', []))
            ->keyBy(fn ($tier) => $tier['paddle_price_id'] ?? null);

        $priceId = $this->resolvePriceId($payload);
        if ($priceId && $tiers->has($priceId)) {
            return (float) $tiers->get($priceId)['amount'];
        }

        $amount = Arr::get($payload, 'totals.grand_total.amount')
            ?? Arr::get($payload, 'totals.grand_total')
            ?? Arr::get($payload, 'details.totals.grand_total.amount')
            ?? Arr::get($payload, 'details.totals.grand_total')
            ?? Arr::get($payload, 'amount');

        if (is_numeric($amount)) {
            $value = (float) $amount;

            return $value >= 100 ? round($value / 100, 2) : round($value, 2);
        }

        Log::warning('[GiftVoucher] Unable to resolve amount, defaulting to 0', ['payload' => $payload]);

        return 0.0;
    }

    protected function resolveCurrency(array $payload): string
    {
        return $payload['currency_code']
            ?? Arr::get($payload, 'details.totals.currency_code')
            ?? Arr::get($payload, 'currency')
            ?? 'EUR';
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    protected function extractCustomData(array $payload): array
    {
        $customData = [];

        if (isset($payload['custom_data']) && is_array($payload['custom_data'])) {
            $customData = $payload['custom_data'];
        }

        if (isset($payload['customData']) && is_array($payload['customData'])) {
            $customData = array_merge($customData, $payload['customData']);
        }

        if (isset($payload['metadata']) && is_array($payload['metadata'])) {
            $customData = array_merge($customData, $payload['metadata']);
        }

        return $customData;
    }

    protected function generateCode(): string
    {
        return 'GIFT-'.Str::upper(Str::random(8));
    }

    protected function sendNotifications(
        GiftVoucher $voucher,
        bool $force = false,
        ?Carbon $when = null,
        ?string $locale = null,
        ?bool $recipientOnly = null
    ): void {
        $alreadySent = (bool) Arr::get($voucher->metadata ?? [], 'notifications_sent', false);

        if ($alreadySent && ! $force) {
            return;
        }

        $purchaserMail = $voucher->purchaser_email ? Mail::to($voucher->purchaser_email) : null;
        $recipientMail = $voucher->recipient_email && $voucher->recipient_email !== $voucher->purchaser_email
            ? Mail::to($voucher->recipient_email)
            : null;

        if (! $recipientOnly && $purchaserMail) {
            $mailable = (new GiftVoucherIssued($voucher, false))->locale($locale);
            $when ? $purchaserMail->later($when, $mailable) : $purchaserMail->queue($mailable);
        }

        if ($recipientMail) {
            $mailable = (new GiftVoucherIssued($voucher, true))->locale($locale);
            $when ? $recipientMail->later($when, $mailable) : $recipientMail->queue($mailable);
        }

        $metadata = $voucher->metadata ?? [];
        if (! $recipientOnly) {
            $metadata['notifications_sent'] = true;
        }
        $voucher->forceFill([
            'metadata' => $metadata,
            'recipient_delivery_sent_at' => $when ? $voucher->recipient_delivery_sent_at : ($recipientMail ? now() : $voucher->recipient_delivery_sent_at),
        ])->save();

        $this->scheduleReminders($voucher);
    }

    protected function scheduleReminders(GiftVoucher $voucher): void
    {
        if ($voucher->isRedeemed() || $voucher->isRefunded()) {
            return;
        }

        $reminderDays = (int) config('gift-vouchers.reminder_days', 7);
        $expiryReminderDays = (int) config('gift-vouchers.expiry_reminder_days', 14);

        if ($reminderDays > 0) {
            NotifyGiftVoucherReminder::dispatch($voucher)->delay(now()->addDays($reminderDays));
        }

        if ($voucher->expires_at && $expiryReminderDays > 0) {
            $when = $voucher->expires_at->copy()->subDays($expiryReminderDays);
            if ($when->isFuture()) {
                NotifyGiftVoucherReminder::dispatch($voucher, true)->delay($when);
            }
        }
    }
}
