<?php
namespace Bot\Services;

use Bot\Config;
use Bot\Database;
use PDO;

class ReferralService
{
    private PDO $pdo;
    private BalanceService $balances;
    private SettingService $settings;

    public function __construct()
    {
        $this->pdo = Database::connection();
        $this->balances = new BalanceService();
        $this->settings = new SettingService();
    }

    public function generateCode(): string
    {
        do {
            $code = strtoupper(bin2hex(random_bytes(3)));
            $exists = $this->pdo->prepare('SELECT 1 FROM users WHERE referral_code = ?');
            $exists->execute([$code]);
        } while ($exists->fetch());
        return $code;
    }

    public function linkReferrer(int $userId, string $code): ?int
    {
        $stmt = $this->pdo->prepare('SELECT id FROM users WHERE referral_code = ?');
        $stmt->execute([strtoupper($code)]);
        $referrer = $stmt->fetchColumn();
        if (!$referrer || (int) $referrer === $userId) {
            return null;
        }
        $update = $this->pdo->prepare('UPDATE users SET referrer_id = ? WHERE id = ? AND referrer_id IS NULL');
        $update->execute([(int) $referrer, $userId]);
        return $update->rowCount() > 0 ? (int) $referrer : null;
    }

    public function awardReferralBonus(int $referrerId, int $referredUserId): void
    {
        // legacy method kept for compatibility; credits cash bonus if configured
        $bonus = (float) Config::get()->getValue('referral_bonus_amount', 0);
        if ($bonus <= 0) {
            return;
        }
        $this->balances->credit($referrerId, 'USDT', (string) $bonus, 'referral_cash_bonus', (string) $referredUserId);
    }

    public function handleKycVerified(int $userId): void
    {
        $stmt = $this->pdo->prepare('SELECT referrer_id FROM users WHERE id = ?');
        $stmt->execute([$userId]);
        $referrerId = $stmt->fetchColumn();
        if (!$referrerId) {
            return;
        }
        if ($this->hasReward($referrerId, $userId, 'kyc_bonus')) {
            return;
        }
        $points = $this->getSetting('referral_kyc_points');
        if ($points <= 0) {
            return;
        }
        $this->grantPoints((int) $referrerId, $points, 'kyc_bonus', $userId);
        $update = $this->pdo->prepare(
            'UPDATE users SET referral_verified_count = referral_verified_count + 1 WHERE id = ?'
        );
        $update->execute([(int) $referrerId]);
        $this->recalculateLevel((int) $referrerId);
    }

    public function handleOrderCompleted(int $orderId, array $order): void
    {
        $stmt = $this->pdo->prepare('SELECT referrer_id FROM users WHERE id = ?');
        $stmt->execute([(int) $order['user_id']]);
        $referrerId = $stmt->fetchColumn();
        if (!$referrerId) {
            return;
        }
        if ($this->hasReward($referrerId, $order['user_id'], 'trade_bonus')) {
            // give only once per referred user
            return;
        }
        $points = $this->getSetting('referral_trade_points');
        if ($points <= 0) {
            return;
        }
        $this->grantPoints((int) $referrerId, $points, 'trade_bonus', (int) $order['user_id']);
        $volumeUpdate = $this->pdo->prepare(
            'UPDATE users SET referral_trade_volume = referral_trade_volume + ? WHERE id = ?'
        );
        $volumeUpdate->execute([$order['amount'], (int) $referrerId]);
        $this->recalculateLevel((int) $referrerId);
    }

    public function convertPoints(int $userId, float $points): float
    {
        if ($points <= 0) {
            throw new \InvalidArgumentException('Points must be positive.');
        }
        $pointsFormatted = number_format($points, 4, '.', '');
        $balance = $this->balances->getBalance($userId, 'POINT');
        if ($points > (float) $balance['available']) {
            throw new \RuntimeException('Insufficient points.');
        }
        $rate = $this->getPointRate();
        if ($rate <= 0) {
            throw new \RuntimeException('Conversion rate not configured.');
        }
        $usd = $points * $rate;
        $usdFormatted = number_format($usd, 4, '.', '');
        $this->balances->debit($userId, 'POINT', $pointsFormatted, 'points_convert', null);
        $this->balances->credit($userId, 'USDT', $usdFormatted, 'points_convert', null);
        $this->logReward($userId, null, 'conversion', $pointsFormatted, $usdFormatted);
        return (float) $usdFormatted;
    }

    public function recalculateLevel(int $userId): void
    {
        $stmt = $this->pdo->prepare('SELECT referral_verified_count, referral_trade_volume FROM users WHERE id = ?');
        $stmt->execute([$userId]);
        $stats = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$stats) {
            return;
        }
        $levels = $this->pdo->query('SELECT * FROM referral_levels ORDER BY min_verified DESC, min_volume DESC')->fetchAll(PDO::FETCH_ASSOC);
        $current = 'Bronze';
        foreach ($levels as $level) {
            if ((int) $stats['referral_verified_count'] >= (int) $level['min_verified']
                && (float) $stats['referral_trade_volume'] >= (float) $level['min_volume']) {
                $current = $level['level_name'];
                break;
            }
        }
        $update = $this->pdo->prepare('UPDATE users SET referral_level = ? WHERE id = ?');
        $update->execute([$current, $userId]);
    }

    public function getPointRate(): float
    {
        $value = $this->settings->get('referral_point_rate');
        if ($value === null) {
            $defaults = Config::get()->getValue('referral_defaults', []);
            return (float) ($defaults['point_rate'] ?? 0.01);
        }
        return (float) $value;
    }

    /**
     * @return array<int,array<string,mixed>>
     */
    public function listLevels(): array
    {
        $stmt = $this->pdo->query('SELECT * FROM referral_levels ORDER BY min_verified ASC, min_volume ASC');
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    public function upsertLevel(string $name, int $minVerified, float $minVolume, float $multiplier, float $bonusPoints = 0): void
    {
        $stmt = $this->pdo->prepare(
            'INSERT INTO referral_levels (level_name, min_verified, min_volume, multiplier, bonus_points, created_at)
             VALUES (:name, :verified, :volume, :multiplier, :bonus, NOW())
             ON DUPLICATE KEY UPDATE min_verified = VALUES(min_verified), min_volume = VALUES(min_volume),
             multiplier = VALUES(multiplier), bonus_points = VALUES(bonus_points)'
        );
        $stmt->execute([
            ':name'      => $name,
            ':verified'  => $minVerified,
            ':volume'    => $minVolume,
            ':multiplier'=> $multiplier,
            ':bonus'     => $bonusPoints,
        ]);
    }

    public function deleteLevel(string $name): void
    {
        $stmt = $this->pdo->prepare('DELETE FROM referral_levels WHERE level_name = ?');
        $stmt->execute([$name]);
    }

    private function grantPoints(int $referrerId, float $points, string $event, ?int $referredUserId = null): void
    {
        $pointsFormatted = number_format($points, 4, '.', '');
        $level = $this->getUserLevel($referrerId);
        $multiplier = $this->getLevelMultiplier($level);
        $finalPoints = $pointsFormatted * $multiplier;
        $finalPoints = number_format($finalPoints, 4, '.', '');
        $this->balances->credit($referrerId, 'POINT', $finalPoints, 'referral_' . $event, (string) $referredUserId);
        $this->logReward($referrerId, $referredUserId, $event, $finalPoints, 0.0, $level);
    }

    private function logReward(int $referrerId, ?int $referredUserId, string $event, string $points, float $usd, ?string $level = null): void
    {
        $stmt = $this->pdo->prepare(
            'INSERT INTO referral_rewards (referrer_id, referred_user_id, event, points, usd_amount, level_snapshot, created_at)
             VALUES (?, ?, ?, ?, ?, ?, NOW())'
        );
        $stmt->execute([
            $referrerId,
            $referredUserId,
            $event,
            $points,
            $usd,
            $level,
        ]);
    }

    private function hasReward(int $referrerId, int $referredId, string $event): bool
    {
        $stmt = $this->pdo->prepare(
            'SELECT 1 FROM referral_rewards WHERE referrer_id = ? AND referred_user_id = ? AND event = ? LIMIT 1'
        );
        $stmt->execute([$referrerId, $referredId, $event]);
        return (bool) $stmt->fetchColumn();
    }

    private function getLevelMultiplier(string $levelName): float
    {
        $stmt = $this->pdo->prepare('SELECT multiplier FROM referral_levels WHERE level_name = ?');
        $stmt->execute([$levelName]);
        $value = $stmt->fetchColumn();
        return $value !== false ? (float) $value : 1.0;
    }

    private function getUserLevel(int $userId): string
    {
        $stmt = $this->pdo->prepare('SELECT referral_level FROM users WHERE id = ?');
        $stmt->execute([$userId]);
        $level = $stmt->fetchColumn();
        return $level ?: 'Bronze';
    }

    private function getSetting(string $key): float
    {
        $value = $this->settings->get($key);
        if ($value === null) {
            $defaults = Config::get()->getValue('referral_defaults', []);
            $map = [
                'referral_point_rate'   => 'point_rate',
                'referral_kyc_points'   => 'kyc_points',
                'referral_trade_points' => 'trade_points',
            ];
            $mapped = $map[$key] ?? null;
            return $mapped ? (float) ($defaults[$mapped] ?? 0) : 0.0;
        }
        return (float) $value;
    }
}
