<?php
namespace App\Services;

use App\Core\Database;
use PDO;
use RuntimeException;

final class CouponService
{
    public function normalizeCode(string $code): string
    {
        $code = strtoupper(trim($code));
        $code = preg_replace('/[^A-Z0-9_-]/', '', $code) ?? '';
        return substr($code, 0, 60);
    }

    public function validateForWalk(int $userId, int $walkId, string $code): array
    {
        $pdo = Database::connection();
        $code = $this->normalizeCode($code);
        if ($code === '') {
            throw new RuntimeException('Enter a coupon code.');
        }

        $stmt = $pdo->prepare('SELECT * FROM coupons WHERE code = ? LIMIT 1');
        $stmt->execute([$code]);
        $coupon = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$coupon) {
            throw new RuntimeException('Coupon code was not found.');
        }

        if (($coupon['status'] ?? 'inactive') !== 'active') {
            throw new RuntimeException('Coupon is not active.');
        }

        $now = time();
        if (!empty($coupon['valid_from']) && strtotime((string)$coupon['valid_from']) > $now) {
            throw new RuntimeException('Coupon is not valid yet.');
        }
        if (!empty($coupon['valid_until']) && strtotime((string)$coupon['valid_until']) < $now) {
            throw new RuntimeException('Coupon has expired.');
        }

        $walkStmt = $pdo->prepare('SELECT id, title, payment_model, payment_amount, payment_currency FROM walks WHERE id = ? LIMIT 1');
        $walkStmt->execute([$walkId]);
        $walk = $walkStmt->fetch(PDO::FETCH_ASSOC);
        if (!$walk) {
            throw new RuntimeException('Walk not found.');
        }

        $original = (float)($walk['payment_amount'] ?? 0);
        $currency = strtoupper((string)($walk['payment_currency'] ?? 'LBP'));
        if (($walk['payment_model'] ?? 'free') !== 'paid' || $original <= 0) {
            throw new RuntimeException('Coupons can only be used on paid walks.');
        }

        $minAmount = (float)($coupon['min_walk_amount'] ?? 0);
        if ($minAmount > 0 && $original < $minAmount) {
            throw new RuntimeException('Coupon requires a minimum walk amount of ' . number_format($minAmount, 2) . ' ' . $currency . '.');
        }

        $maxUses = $coupon['max_uses'] === null ? 0 : (int)$coupon['max_uses'];
        if ($maxUses > 0) {
            $usesStmt = $pdo->prepare('SELECT COUNT(*) FROM coupon_redemptions WHERE coupon_id = ?');
            $usesStmt->execute([(int)$coupon['id']]);
            if ((int)$usesStmt->fetchColumn() >= $maxUses) {
                throw new RuntimeException('Coupon usage limit has been reached.');
            }
        }

        $perUser = max(1, (int)($coupon['max_uses_per_user'] ?? 1));
        $userUsesStmt = $pdo->prepare('SELECT COUNT(*) FROM coupon_redemptions WHERE coupon_id = ? AND user_id = ?');
        $userUsesStmt->execute([(int)$coupon['id'], $userId]);
        if ((int)$userUsesStmt->fetchColumn() >= $perUser) {
            throw new RuntimeException('You already used this coupon.');
        }

        $discountType = (string)($coupon['discount_type'] ?? 'percent');
        $discountValue = (float)($coupon['discount_value'] ?? 0);
        if ($discountValue <= 0) {
            throw new RuntimeException('Coupon has no discount value.');
        }

        if ($discountType === 'fixed') {
            $couponCurrency = strtoupper((string)($coupon['currency'] ?? $currency));
            if ($couponCurrency !== $currency) {
                throw new RuntimeException('Coupon currency does not match this walk.');
            }
            $discount = min($original, $discountValue);
        } else {
            $percent = min(100.0, max(0.0, $discountValue));
            $discount = round($original * ($percent / 100), 2);
        }

        $discount = round(max(0, min($original, $discount)), 2);
        $final = round(max(0, $original - $discount), 2);

        return [
            'coupon' => [
                'id' => (int)$coupon['id'],
                'code' => (string)$coupon['code'],
                'description' => (string)($coupon['description'] ?? ''),
                'discount_type' => $discountType,
                'discount_value' => $discountValue,
                'currency' => (string)($coupon['currency'] ?? $currency),
            ],
            'walk' => [
                'id' => (int)$walk['id'],
                'title' => (string)$walk['title'],
                'payment_currency' => $currency,
            ],
            'original_amount' => $original,
            'discount_amount' => $discount,
            'final_amount' => $final,
            'currency' => $currency,
        ];
    }

    public function redeem(int $userId, int $walkId, int $registrationId, string $code): array
    {
        $pdo = Database::connection();
        $validated = $this->validateForWalk($userId, $walkId, $code);
        $couponId = (int)$validated['coupon']['id'];

        $existing = $pdo->prepare('SELECT * FROM coupon_redemptions WHERE coupon_id = ? AND user_id = ? AND walk_id = ? LIMIT 1');
        $existing->execute([$couponId, $userId, $walkId]);
        $row = $existing->fetch(PDO::FETCH_ASSOC);
        if ($row) {
            $validated['redemption'] = $row;
            return $validated;
        }

        $stmt = $pdo->prepare('INSERT INTO coupon_redemptions (coupon_id, walk_id, user_id, registration_id, original_amount, discount_amount, final_amount, currency, redeemed_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())');
        $stmt->execute([
            $couponId,
            $walkId,
            $userId,
            $registrationId,
            $validated['original_amount'],
            $validated['discount_amount'],
            $validated['final_amount'],
            $validated['currency'],
        ]);

        $validated['redemption'] = ['id' => (int)$pdo->lastInsertId()];
        return $validated;
    }
}
