<?php
namespace Bot\Services;

use Bot\Config;
use Bot\Database;
use Bot\Services\BalanceService;
use PDO;

class OrderService
{
    private PDO $pdo;
    private RateService $rateService;
    private BalanceService $balanceService;
    private int $paymentTimeoutMinutes;

    public function __construct(?RateService $rateService = null)
    {
        $this->pdo = Database::connection();
        $this->rateService = $rateService ?? new RateService();
        $this->balanceService = new BalanceService();
        $this->paymentTimeoutMinutes = (int) Config::get()->getValue('payment_timeout_minutes', 15);
    }

    /**
     * Validate user limits and return remaining allowance per period.
     *
     * @return array{daily:float,weekly:float,monthly:float}
     */
    public function enforceLimits(int $userId, float $requestedAmount): array
    {
        $stmt = $this->pdo->prepare('SELECT limits FROM users WHERE id = ?');
        $stmt->execute([$userId]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        $defaults = Config::get()->getValue('limits_defaults', ['daily' => 1000, 'weekly' => 5000, 'monthly' => 20000]);
        $limits = $row && $row['limits'] ? array_merge($defaults, json_decode($row['limits'], true) ?? []) : $defaults;

        $periods = [
            'daily'   => '1 DAY',
            'weekly'  => '7 DAY',
            'monthly' => '30 DAY',
        ];
        $remaining = [];
        foreach ($periods as $key => $interval) {
            $stmt = $this->pdo->prepare(
                "SELECT COALESCE(SUM(amount),0) AS total FROM orders
                 WHERE user_id = :user AND created_at >= DATE_SUB(NOW(), INTERVAL {$interval})
                 AND status IN ('open','pending','completed')"
            );
            $stmt->execute([':user' => $userId]);
            $total = (float) $stmt->fetchColumn();
            $limit = (float) ($limits[$key] ?? 0);
            $remaining[$key] = max($limit - $total, 0);
            if ($requestedAmount > $remaining[$key]) {
                throw new \RuntimeException(
                    sprintf('Limit exceeded: %s remaining %.2f', ucfirst($key), $remaining[$key])
                );
            }
        }
        return $remaining;
    }

    /**
     * Create an order row with calculated fees, totals, and payment instructions.
     *
     * @param array<string,mixed>|null $paymentMethod
     *
     * @return array{id:int,status:string,quote_total:float,price:float,service_fee:float,network_fee:float}
     */
    public function createOrder(
        int $userId,
        string $type,
        string $currency,
        float $amount,
        ?array $paymentMethod = null,
        array $options = []
    ): array {
        $currency = strtoupper($currency);
        $type = strtolower($type) === 'sell' ? 'sell' : 'buy';

        if ($amount <= 0) {
            throw new \InvalidArgumentException('Amount must be greater than zero.');
        }

        $rates = $this->rateService->getRates($currency);
        if (!$rates) {
            throw new \RuntimeException("No pricing available for {$currency}. Please contact support.");
        }
        $price = $type === 'buy' ? $rates['rate_buy'] : $rates['rate_sell'];
        $fees = $this->rateService->getFees($currency);
        $serviceFee = $amount * ($fees['service_fee_percent'] / 100);
        $networkFee = $fees['network_fee_flat'];
        $quoteTotal = $type === 'buy'
            ? ($amount * $price) + $serviceFee + $networkFee
            : ($amount * $price) - $serviceFee - $networkFee;

        $deadline = $paymentMethod
            ? (new \DateTime("+{$this->paymentTimeoutMinutes} minutes"))->format('Y-m-d H:i:s')
            : null;
        $instructions = $paymentMethod ? $this->formatPaymentInstructions($paymentMethod, $quoteTotal) : null;
        $contact = $paymentMethod['contact_details'] ?? '';
        $snapshot = $paymentMethod ? trim($paymentMethod['name'] . ($contact !== '' ? " ({$contact})" : '')) : null;
        $initialStatus = $options['initial_payment_status']
            ?? ($paymentMethod ? 'waiting_proof' : 'idle');
        $payoutDetails = $options['payout_details'] ?? null;
        $cryptoNetwork = $options['crypto_network'] ?? null;
        $cryptoAddress = $options['crypto_deposit_address'] ?? null;
        $txid = $options['txid'] ?? null;
        $cryptoStatus = $options['crypto_status'] ?? 'pending';

        $stmt = $this->pdo->prepare(
            "INSERT INTO orders (
                user_id, type, currency, amount, price, service_fee, network_fee, quote_total,
                payment_method_id, payment_method_snapshot, payment_instructions, payment_deadline,
                payment_status, status, payout_details, crypto_network, crypto_deposit_address, txid, crypto_status, created_at
            ) VALUES (
                :user_id, :type, :currency, :amount, :price, :service_fee, :network_fee, :quote_total,
                :payment_method_id, :payment_method_snapshot, :payment_instructions, :payment_deadline,
                :payment_status, 'open', :payout_details, :crypto_network, :crypto_deposit_address, :txid, :crypto_status, NOW()
            )"
        );
        $stmt->execute([
            ':user_id'                => $userId,
            ':type'                   => $type,
            ':currency'               => $currency,
            ':amount'                 => $amount,
            ':price'                  => $price,
            ':service_fee'            => $serviceFee,
            ':network_fee'            => $networkFee,
            ':quote_total'            => $quoteTotal,
            ':payment_method_id'      => $paymentMethod['id'] ?? null,
            ':payment_method_snapshot'=> $snapshot,
            ':payment_instructions'   => $instructions,
            ':payment_deadline'       => $deadline,
            ':payment_status'         => $initialStatus,
            ':payout_details'         => $payoutDetails,
            ':crypto_network'         => $cryptoNetwork,
            ':crypto_deposit_address' => $cryptoAddress,
            ':txid'                   => $txid,
            ':crypto_status'          => $cryptoStatus,
        ]);

        return [
            'id'                    => (int) $this->pdo->lastInsertId(),
            'status'                => 'open',
            'type'                  => $type,
            'currency'              => $currency,
            'amount'                => $amount,
            'quote_total'           => $quoteTotal,
            'price'                 => $price,
            'service_fee'           => $serviceFee,
            'network_fee'           => $networkFee,
            'payment_deadline'      => $deadline,
            'payment_instructions'  => $instructions,
            'payment_status'        => $initialStatus,
            'payout_details'        => $payoutDetails,
            'crypto_network'        => $cryptoNetwork,
            'crypto_deposit_address'=> $cryptoAddress,
            'txid'                  => $txid,
        ];
    }

    /**
     * @return array<int,array<string,mixed>>
     */
    public function listRecentOrders(int $userId, int $limit = 10): array
    {
        $stmt = $this->pdo->prepare(
            'SELECT id, type, currency, amount, price, status, service_fee, network_fee, quote_total,
                    payment_status, created_at
             FROM orders WHERE user_id = ? ORDER BY id DESC LIMIT ?'
        );
        $stmt->bindValue(1, $userId, PDO::PARAM_INT);
        $stmt->bindValue(2, $limit, PDO::PARAM_INT);
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    public function getOrderById(int $orderId): ?array
    {
        $stmt = $this->pdo->prepare('SELECT * FROM orders WHERE id = ?');
        $stmt->execute([$orderId]);
        $order = $stmt->fetch(PDO::FETCH_ASSOC);
        return $order ?: null;
    }

    public function getLatestAwaitingProofOrder(int $userId): ?array
    {
        $stmt = $this->pdo->prepare(
            "SELECT * FROM orders
             WHERE user_id = ? AND payment_status IN ('waiting_proof', 'under_review')
             ORDER BY id DESC LIMIT 1"
        );
        $stmt->execute([$userId]);
        $order = $stmt->fetch(PDO::FETCH_ASSOC);
        return $order ?: null;
    }

    public function submitPaymentProof(int $orderId, string $fileId, string $type, ?string $caption): void
    {
        $stmt = $this->pdo->prepare(
            "UPDATE orders
             SET payment_proof_file_id = :file,
                 payment_proof_type = :type,
                 payment_proof_caption = :caption,
                 payment_proof_submitted_at = NOW(),
                 payment_status = 'under_review',
                 status = 'pending'
             WHERE id = :id"
        );
        $stmt->execute([
            ':file'    => $fileId,
            ':type'    => $type,
            ':caption' => $caption,
            ':id'      => $orderId,
        ]);
    }

    public function recordTxid(int $orderId, string $txid): void
    {
        $stmt = $this->pdo->prepare(
            "UPDATE orders
             SET txid = :txid,
                 payment_status = 'awaiting_crypto_confirm',
                 crypto_status = 'awaiting_confirm',
                 status = 'pending',
                 updated_at = NOW()
             WHERE id = :id"
        );
        $stmt->execute([
            ':txid' => $txid,
            ':id'   => $orderId,
        ]);
    }

    public function updatePaymentStatus(int $orderId, string $paymentStatus, ?string $note = null): void
    {
        $order = $this->getOrderById($orderId);
        if (!$order) {
            return;
        }
        $alreadyCompleted = $order['status'] === 'completed';
        $status = $paymentStatus === 'approved' ? 'completed' : ($paymentStatus === 'expired' ? 'cancelled' : 'open');
        $stmt = $this->pdo->prepare(
            "UPDATE orders SET payment_status = :payment_status, status = :status, notes = :note, updated_at = NOW()
             WHERE id = :id"
        );
        $stmt->execute([
            ':payment_status' => $paymentStatus,
            ':status'         => $status,
            ':note'           => $note,
            ':id'             => $orderId,
        ]);
        if ($paymentStatus === 'approved' && $status === 'completed' && !$alreadyCompleted) {
            if ($order['type'] === 'buy') {
                $creditAmount = max((float) $order['amount'], 0);
                $this->balanceService->credit(
                    (int) $order['user_id'],
                    $order['currency'],
                    number_format($creditAmount, 8, '.', ''),
                    'buy_order_complete',
                    (string) $orderId
                );
            }
            $referralService = new ReferralService();
            $referralService->handleOrderCompleted($orderId, $order);
        }
    }

    /**
     * @return array<int,array<string,mixed>>
     */
    public function listOrdersByPaymentStatus(string $paymentStatus): array
    {
        $stmt = $this->pdo->prepare(
            'SELECT o.*, u.telegram_id, u.language
             FROM orders o
             JOIN users u ON u.id = o.user_id
             WHERE o.payment_status = ?
             ORDER BY o.id ASC'
        );
        $stmt->execute([$paymentStatus]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    /**
     * Auto-cancel orders whose payment deadlines have passed.
     *
     * @return array<int,array{id:int,telegram_id:string,language:string}>
     */
    public function expireStaleOrders(): array
    {
        $stmt = $this->pdo->query(
            "SELECT o.id, o.payment_deadline, u.telegram_id, u.language
             FROM orders o
             JOIN users u ON u.id = o.user_id
             WHERE o.status = 'open'
               AND o.payment_deadline IS NOT NULL
               AND o.payment_deadline < NOW()"
        );
        $expired = $stmt->fetchAll(PDO::FETCH_ASSOC);
        if (!$expired) {
            return [];
        }
        $ids = array_column($expired, 'id');
        $placeholders = implode(',', array_fill(0, count($ids), '?'));
        $update = $this->pdo->prepare(
            "UPDATE orders SET status = 'cancelled', payment_status = 'expired', updated_at = NOW()
             WHERE id IN ($placeholders)"
        );
        $update->execute($ids);
        return $expired;
    }

    /**
     * Create a human-friendly instruction block using admin-provided template.
     */
    private function formatPaymentInstructions(array $paymentMethod, float $quoteTotal): string
    {
        $template = (string) ($paymentMethod['instructions'] ?? '');
        $contact = trim((string) ($paymentMethod['contact_details'] ?? ''));
        $replacements = [
            '{{amount}}' => number_format($quoteTotal, 2),
            '{{method}}' => (string) $paymentMethod['name'],
            '{{contact}}' => $contact !== '' ? $contact : 'N/A',
        ];
        return str_replace(array_keys($replacements), array_values($replacements), $template);
    }
}
