<?php

namespace App\Services\Billing;

use App\Models\WalletModel;
use App\Models\WalletTransactionModel;
use Config\Database;

class WalletService
{
    private WalletModel $wallets;
    private WalletTransactionModel $transactions;

    public function __construct()
    {
        $this->wallets      = new WalletModel();
        $this->transactions = new WalletTransactionModel();
    }

    public function balanceFor(int $agencyId): float
    {
        return (float) $this->wallets->ensureExists($agencyId)['balance'];
    }

    public function deposit(int $agencyId, float $amount, string $referenceType, ?int $referenceId, ?string $note = null): int
    {
        return $this->adjust($agencyId, $amount, 'deposit', $referenceType, $referenceId, $note);
    }

    public function debit(int $agencyId, float $amount, string $referenceType, ?int $referenceId, ?string $note = null): int
    {
        $db = Database::connect();
        $wallet = $this->wallets->ensureExists($agencyId);

        $locked = $db->query('SELECT balance FROM wallets WHERE agency_id = ? FOR UPDATE', [$agencyId])->getRow();
        if ((float) $locked->balance < $amount) {
            throw new \App\Exceptions\InsufficientFundsException(
                "Insufficient wallet balance: " . number_format((float) $locked->balance, 2) . ", required " . number_format($amount, 2)
            );
        }

        return $this->adjust($agencyId, -$amount, 'withdrawal', $referenceType, $referenceId, $note);
    }

    public function creditPurchaseDebit(int $agencyId, float $amount, int $creditTransactionId): int
    {
        return $this->debit($agencyId, $amount, 'credit_transactions', $creditTransactionId, 'Credit purchase via wallet');
    }

    private function adjust(int $agencyId, float $delta, string $type, string $referenceType, ?int $referenceId, ?string $note): int
    {
        $db = Database::connect();
        $db->transStart();

        $this->wallets->ensureExists($agencyId);
        $locked = $db->query('SELECT balance FROM wallets WHERE agency_id = ? FOR UPDATE', [$agencyId])->getRow();
        $newBalance = round((float) $locked->balance + $delta, 2);

        $db->table('wallets')->where('agency_id', $agencyId)->update([
            'balance'    => $newBalance,
            'updated_at' => date('Y-m-d H:i:s'),
        ]);

        $wallet = $this->wallets->forAgency($agencyId);

        $txId = $this->transactions->insert([
            'wallet_id'      => $wallet['id'],
            'type'           => $type,
            'amount'         => $delta,
            'balance_after'  => $newBalance,
            'reference_type' => $referenceType,
            'reference_id'   => $referenceId,
            'note'           => $note,
            'created_at'     => date('Y-m-d H:i:s'),
        ], true);

        $db->transComplete();

        return $txId;
    }

    public function ledgerFor(int $agencyId, int $page = 1, int $perPage = 20): array
    {
        $wallet = $this->wallets->ensureExists($agencyId);
        return $this->transactions->historyFor($wallet['id'], $page, $perPage);
    }
}
