<?php

namespace App\Services\Coupons;

use App\Enums\CouponStatus;
use App\Enums\CouponType;
use App\Models\Coupon;
use App\Models\CouponRedemption;
use App\Models\Package;
use App\Models\Tenant;
use App\Services\Paddle\Exceptions\PaddleException;
use App\Services\Paddle\PaddleDiscountService;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class CouponService
{
    public function __construct(private readonly PaddleDiscountService $paddleDiscounts) {}

    /**
     * @return array{coupon: Coupon, pricing: array<string, mixed>, source: string}
     */
    public function preview(string $code, Package $package, ?Tenant $tenant = null): array
    {
        $coupon = $this->findCouponForCode($code);

        $this->ensureCouponCanBeApplied($coupon, $package, $tenant);

        $pricing = $this->buildPricingBreakdown($coupon, $package, $tenant);

        return [
            'coupon' => $coupon,
            'pricing' => $pricing['pricing'],
            'source' => $pricing['source'],
        ];
    }

    public function ensureCouponCanBeApplied(Coupon $coupon, Package $package, ?Tenant $tenant = null): void
    {
        if (! $coupon->paddle_discount_id) {
            throw ValidationException::withMessages([
                'code' => __('marketing.coupon.errors.not_synced'),
            ]);
        }

        if (! $coupon->enabled_for_checkout) {
            throw ValidationException::withMessages([
                'code' => __('marketing.coupon.errors.disabled'),
            ]);
        }

        if (! $coupon->isCurrentlyActive()) {
            throw ValidationException::withMessages([
                'code' => __('marketing.coupon.errors.inactive'),
            ]);
        }

        if (! $coupon->appliesToPackage($package)) {
            throw ValidationException::withMessages([
                'code' => __('marketing.coupon.errors.not_applicable'),
            ]);
        }

        if ($tenant) {
            $usage = $this->usageForTenant($coupon, $tenant);
            $remaining = $coupon->remainingUsages($usage);

            if ($remaining !== null && $remaining <= 0) {
                throw ValidationException::withMessages([
                    'code' => __('marketing.coupon.errors.limit_reached'),
                ]);
            }
        } elseif ($coupon->per_customer_limit !== null) {
            throw ValidationException::withMessages([
                'code' => __('marketing.coupon.errors.login_required'),
            ]);
        }
    }

    protected function findCouponForCode(string $code): Coupon
    {
        $normalized = Str::upper(trim($code));

        if ($normalized === '') {
            throw ValidationException::withMessages([
                'code' => __('marketing.coupon.errors.required'),
            ]);
        }

        $coupon = Coupon::query()
            ->where('code', $normalized)
            ->first();

        if (! $coupon) {
            throw ValidationException::withMessages([
                'code' => __('marketing.coupon.errors.not_found'),
            ]);
        }

        if (in_array($coupon->status, [CouponStatus::PAUSED, CouponStatus::ARCHIVED, CouponStatus::DRAFT], true)) {
            throw ValidationException::withMessages([
                'code' => __('marketing.coupon.errors.inactive'),
            ]);
        }

        return $coupon;
    }

    protected function usageForTenant(Coupon $coupon, Tenant $tenant): int
    {
        return $coupon->redemptions()
            ->where('tenant_id', $tenant->id)
            ->whereNot('status', CouponRedemption::STATUS_FAILED)
            ->count();
    }

    /**
     * @return array{pricing: array<string, mixed>, source: string}
     */
    protected function buildPricingBreakdown(Coupon $coupon, Package $package, ?Tenant $tenant = null): array
    {
        $currency = Str::upper($package->currency ?? 'EUR');
        $subtotal = (float) $package->price;

        if ($package->paddle_price_id) {
            try {
                $preview = $this->paddleDiscounts->previewDiscount(
                    $coupon,
                    [
                        [
                            'price_id' => $package->paddle_price_id,
                            'quantity' => 1,
                        ],
                    ],
                    array_filter([
                        'currency' => $currency,
                        'customer_id' => $tenant?->paddle_customer_id,
                    ])
                );

                $mapped = $this->mapPaddlePreview($preview, $currency, $subtotal);

                return [
                    'pricing' => $mapped,
                    'source' => 'paddle',
                ];
            } catch (PaddleException $exception) {
                Log::warning('Paddle preview failed, falling back to manual pricing', [
                    'coupon_id' => $coupon->id,
                    'package_id' => $package->id,
                    'message' => $exception->getMessage(),
                ]);
            }
        }

        return [
            'pricing' => $this->manualPricing($coupon, $currency, $subtotal),
            'source' => 'manual',
        ];
    }

    protected function mapPaddlePreview(array $preview, string $currency, float $fallbackSubtotal): array
    {
        $totals = $this->extractTotals($preview);

        $subtotal = $totals['subtotal'] ?? $fallbackSubtotal;
        $discount = $totals['discount'] ?? 0.0;
        $tax = $totals['tax'] ?? 0.0;
        $total = $totals['total'] ?? max($subtotal - $discount + $tax, 0);

        return $this->formatPricing($currency, $subtotal, $discount, $tax, $total, [
            'raw' => $preview,
            'breakdown' => $totals['breakdown'] ?? [],
        ]);
    }

    protected function manualPricing(Coupon $coupon, string $currency, float $subtotal): array
    {
        $discount = match ($coupon->type) {
            CouponType::PERCENTAGE => round($subtotal * ((float) $coupon->amount) / 100, 2),
            default => (float) $coupon->amount,
        };

        if ($coupon->type !== CouponType::PERCENTAGE && $coupon->currency && Str::upper($coupon->currency) !== $currency) {
            throw ValidationException::withMessages([
                'code' => __('marketing.coupon.errors.currency_mismatch'),
            ]);
        }

        $discount = min($discount, $subtotal);
        $total = max($subtotal - $discount, 0);

        return $this->formatPricing($currency, $subtotal, $discount, 0, $total, [
            'breakdown' => [
                ['type' => 'coupon', 'amount' => $discount],
            ],
        ]);
    }

    protected function extractTotals(array $preview): array
    {
        $totals = Arr::get($preview, 'totals', Arr::get($preview, 'details.totals', []));

        $subtotal = $this->convertMinorAmount($totals['subtotal'] ?? ($totals['subtotal']['amount'] ?? null));
        $discount = $this->convertMinorAmount($totals['discount'] ?? ($totals['discount']['amount'] ?? null));
        $tax = $this->convertMinorAmount($totals['tax'] ?? ($totals['tax']['amount'] ?? null));
        $total = $this->convertMinorAmount($totals['total'] ?? ($totals['total']['amount'] ?? null));

        return array_filter([
            'currency' => $totals['currency_code'] ?? Arr::get($preview, 'currency_code'),
            'subtotal' => $subtotal,
            'discount' => $discount,
            'tax' => $tax,
            'total' => $total,
            'breakdown' => Arr::get($preview, 'discounts', []),
        ], static fn ($value) => $value !== null && $value !== '');
    }

    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);
    }

    protected function formatPricing(string $currency, float $subtotal, float $discount, float $tax, float $total, array $extra = []): array
    {
        $locale = $this->mapLocale(app()->getLocale());
        $formatter = class_exists(\NumberFormatter::class)
            ? new \NumberFormatter($locale, \NumberFormatter::CURRENCY)
            : null;

        $format = function (float $amount, bool $allowNegative = false) use ($currency, $formatter): string {
            $value = $allowNegative ? $amount : max($amount, 0);

            if ($formatter) {
                $formatted = $formatter->formatCurrency($value, $currency);
                if ($formatted !== false) {
                    return $formatted;
                }
            }

            $symbol = match ($currency) {
                'EUR' => '€',
                'USD' => '$',
                default => $currency.' ',
            };

            return $symbol.number_format($value, 2, ',', '.');
        };

        return array_merge([
            'currency' => $currency,
            'subtotal' => round($subtotal, 2),
            'discount' => round($discount, 2),
            'tax' => round($tax, 2),
            'total' => round($total, 2),
            'formatted' => [
                'subtotal' => $format($subtotal),
                'discount' => $format(-1 * abs($discount), true),
                'tax' => $format($tax),
                'total' => $format($total),
            ],
        ], $extra);
    }

    protected function mapLocale(?string $locale): string
    {
        return match ($locale) {
            'de', 'de_DE' => 'de_DE',
            'en', 'en_GB', 'en_US' => 'en_US',
            default => 'en_US',
        };
    }
}
