<?php

namespace App\Services\Coupons;

use App\Models\CheckoutSession;
use App\Models\CouponRedemption;
use App\Services\GiftVouchers\GiftVoucherService;
use Illuminate\Support\Arr;

class CouponRedemptionService
{
    private int $fraudWindowHours;

    private int $fraudMediumFailed;

    private int $fraudHighFailed;

    private float $fraudHighFailedRatio;

    public function __construct(private readonly GiftVoucherService $giftVouchers)
    {
        $this->fraudWindowHours = (int) config('checkout.fraud.window_hours', 24);
        $this->fraudMediumFailed = (int) config('checkout.fraud.medium_failed', 2);
        $this->fraudHighFailed = (int) config('checkout.fraud.high_failed', 5);
        $this->fraudHighFailedRatio = (float) config('checkout.fraud.high_failed_ratio', 0.5);
    }

    public function recordSuccess(CheckoutSession $session, array $payload = []): void
    {
        if (! $session->coupon_id) {
            return;
        }

        $transactionId = Arr::get($payload, 'id') ?? $session->paddle_transaction_id;

        $context = $this->resolveRequestContext($session);
        $fraudSnapshot = $this->buildFraudSnapshot($context);

        $values = array_merge($context, [
            'tenant_id' => $session->tenant_id,
            'user_id' => $session->user_id,
            'package_id' => $session->package_id,
            'paddle_transaction_id' => $transactionId,
            'status' => CouponRedemption::STATUS_SUCCESS,
            'failure_reason' => null,
            'amount_discounted' => $session->amount_discount,
            'currency' => $session->currency ?? 'EUR',
            'metadata' => array_filter([
                'session_snapshot' => $session->coupon_snapshot,
                'payload' => $payload,
                'fraud' => $fraudSnapshot,
            ]),
            'redeemed_at' => now(),
        ]);

        CouponRedemption::query()->updateOrCreate(
            [
                'coupon_id' => $session->coupon_id,
                'checkout_session_id' => $session->id,
            ],
            $values,
        );

        $session->coupon?->increment('redemptions_count');

        $this->giftVouchers->markRedeemed($session->coupon, $transactionId);
    }

    public function recordFailure(CheckoutSession $session, string $reason): void
    {
        if (! $session->coupon_id) {
            return;
        }

        $context = $this->resolveRequestContext($session);
        $fraudSnapshot = $this->buildFraudSnapshot($context);

        CouponRedemption::query()->updateOrCreate(
            [
                'coupon_id' => $session->coupon_id,
                'checkout_session_id' => $session->id,
            ],
            array_merge($context, [
                'tenant_id' => $session->tenant_id,
                'user_id' => $session->user_id,
                'package_id' => $session->package_id,
                'paddle_transaction_id' => $session->paddle_transaction_id,
                'status' => CouponRedemption::STATUS_FAILED,
                'failure_reason' => $reason,
                'amount_discounted' => $session->amount_discount,
                'currency' => $session->currency ?? 'EUR',
                'metadata' => array_filter([
                    'session_snapshot' => $session->coupon_snapshot,
                    'fraud' => $fraudSnapshot,
                ]),
            ]),
        );
    }

    /**
     * @return array{ip_address?: string|null, device_id?: string|null, user_agent?: string|null}
     */
    private function resolveRequestContext(CheckoutSession $session): array
    {
        return array_filter([
            'ip_address' => $session->ip_address,
            'device_id' => $session->device_id,
            'user_agent' => $session->user_agent,
        ], static fn ($value) => $value !== null && $value !== '');
    }

    /**
     * @param  array{ip_address?: string|null, device_id?: string|null, user_agent?: string|null}  $context
     * @return array<string, mixed>|null
     */
    private function buildFraudSnapshot(array $context): ?array
    {
        if ($context === []) {
            return null;
        }

        return array_filter([
            'window_hours' => $this->fraudWindowHours,
            'ip' => $this->buildReputation('ip_address', $context['ip_address'] ?? null),
            'device' => $this->buildReputation('device_id', $context['device_id'] ?? null),
        ], static fn ($value) => $value !== null);
    }

    /**
     * @return array{value: string, recent_success: int, recent_failed: int, recent_total: int, risk: string, last_seen_at: string|null}|null
     */
    private function buildReputation(string $column, ?string $value): ?array
    {
        if (! $value) {
            return null;
        }

        $since = now()->subHours($this->fraudWindowHours);

        $baseQuery = CouponRedemption::query()
            ->where($column, $value)
            ->where('created_at', '>=', $since);

        $success = (clone $baseQuery)->where('status', CouponRedemption::STATUS_SUCCESS)->count();
        $failed = (clone $baseQuery)->where('status', CouponRedemption::STATUS_FAILED)->count();
        $lastSeen = (clone $baseQuery)->latest('created_at')->first()?->created_at;

        return [
            'value' => $value,
            'recent_success' => $success,
            'recent_failed' => $failed,
            'recent_total' => $success + $failed,
            'risk' => $this->resolveRiskLevel($success, $failed),
            'last_seen_at' => $lastSeen?->toIso8601String(),
        ];
    }

    private function resolveRiskLevel(int $success, int $failed): string
    {
        $total = $success + $failed;
        if ($total === 0) {
            return 'unknown';
        }

        $failedRatio = $failed / $total;

        if ($failed >= $this->fraudHighFailed || $failedRatio >= $this->fraudHighFailedRatio) {
            return 'high';
        }

        if ($failed >= $this->fraudMediumFailed) {
            return 'medium';
        }

        return 'low';
    }
}
