<?php
/**
 * AURA FARMING — Diamantes — Parte 02
 *
 * Backend da carteira e extrato de Diamantes.
 * Esta etapa NÃO altera Like, Super Like, XP, Mercado Pago ou Perfil.
 */

if (!defined('AURA_DIAMONDS_PART02_VERSION')) {
    define('AURA_DIAMONDS_PART02_VERSION', '2.0.1');
}

function afDiamondsTableExists(PDO $pdo, string $table): bool
{
    static $cache = [];
    if (isset($cache[$table])) return $cache[$table];

    if (!preg_match('/^[a-zA-Z0-9_]+$/', $table)) {
        return $cache[$table] = false;
    }

    // HostGator e algumas hospedagens bloqueiam leitura de information_schema.
    // Por isso a verificação é feita diretamente na tabela e apenas captura o erro se ela não existir.
    try {
        $pdo->query('SELECT 1 FROM `' . $table . '` LIMIT 1');
        return $cache[$table] = true;
    } catch (Throwable $e) {
        return $cache[$table] = false;
    }
}

function afDiamondsAssertInstalled(PDO $pdo): void
{
    $required = [
        'af_diamond_packages',
        'af_user_diamond_wallets',
        'af_diamond_ledger',
        'af_diamond_orders',
        'af_paid_reactions',
    ];
    foreach ($required as $table) {
        if (!afDiamondsTableExists($pdo, $table)) {
            throw new RuntimeException('A Parte 01 dos Diamantes ainda não foi importada. Tabela ausente: ' . $table);
        }
    }
}

function afDiamondsJson(?array $data): ?string
{
    if (!$data) return null;
    return json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}

function afDiamondsIpHash(): ?string
{
    if (function_exists('afStage24IpHash')) return afStage24IpHash();
    $ip = function_exists('clientIp') ? clientIp() : (string)($_SERVER['REMOTE_ADDR'] ?? '');
    if ($ip === '') return null;
    $salt = defined('SECURITY_HASH_SALT') ? (string)SECURITY_HASH_SALT : 'aura-diamonds';
    return hash_hmac('sha256', $ip, $salt);
}

function afDiamondsUuid(): string
{
    if (function_exists('uuidV4')) return uuidV4();
    $data = random_bytes(16);
    $data[6] = chr((ord($data[6]) & 0x0f) | 0x40);
    $data[8] = chr((ord($data[8]) & 0x3f) | 0x80);
    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}

function afDiamondsAudit(PDO $pdo, string $action, string $entityType = 'diamond_wallet', int|string|null $entityId = null, array $before = [], array $after = [], array $context = []): void
{
    try {
        if (function_exists('afStage24AuditLog')) {
            $context['source'] = $context['source'] ?? 'diamonds_part02';
            afStage24AuditLog($pdo, $action, $entityType, $entityId, $before, $after, $context);
        }
    } catch (Throwable $e) {
        if (defined('APP_DEBUG') && APP_DEBUG) error_log('[AURA-DIAMONDS-AUDIT] ' . $e->getMessage());
    }
}

function afDiamondsSavepointName(): string
{
    return 'af_diamonds_' . bin2hex(random_bytes(4));
}

function afDiamondsTxBegin(PDO $pdo): array
{
    if (!$pdo->inTransaction()) {
        $pdo->beginTransaction();
        return ['own' => true, 'savepoint' => null];
    }
    $savepoint = afDiamondsSavepointName();
    try { $pdo->exec('SAVEPOINT ' . $savepoint); } catch (Throwable $e) { $savepoint = null; }
    return ['own' => false, 'savepoint' => $savepoint];
}

function afDiamondsTxCommit(PDO $pdo, array $tx): void
{
    if (!empty($tx['own'])) {
        if ($pdo->inTransaction()) $pdo->commit();
        return;
    }
    if (!empty($tx['savepoint'])) {
        try { $pdo->exec('RELEASE SAVEPOINT ' . $tx['savepoint']); } catch (Throwable $e) {}
    }
}

function afDiamondsTxRollback(PDO $pdo, array $tx): void
{
    if (!empty($tx['own'])) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        return;
    }
    if (!empty($tx['savepoint'])) {
        try { $pdo->exec('ROLLBACK TO SAVEPOINT ' . $tx['savepoint']); } catch (Throwable $e) {}
    }
}

function afDiamondsValidUserId(int $userId): void
{
    if ($userId <= 0) throw new InvalidArgumentException('Usuário inválido para carteira de Diamantes.');
}

function afDiamondsValidAmount(int $amount): void
{
    if ($amount <= 0) throw new InvalidArgumentException('A quantidade de Diamantes deve ser maior que zero.');
}

function afDiamondsNormalizeType(string $type): string
{
    $type = trim($type);
    $allowed = [
        'purchase_credit',
        'like_spend',
        'super_like_spend',
        'admin_adjustment',
        'refund_debit',
        'refund_credit',
        'system_correction',
        'migration_opening',
    ];
    if (!in_array($type, $allowed, true)) {
        throw new InvalidArgumentException('Tipo de movimentação de Diamantes inválido: ' . $type);
    }
    return $type;
}

function afDiamondsNormalizeDirection(string $direction): string
{
    $direction = trim($direction);
    if (!in_array($direction, ['credit', 'debit'], true)) {
        throw new InvalidArgumentException('Direção de movimentação de Diamantes inválida.');
    }
    return $direction;
}

function afDiamondsGetExistingLedgerByIdempotency(PDO $pdo, ?string $idempotencyKey): ?array
{
    if (!$idempotencyKey) return null;
    $st = $pdo->prepare('SELECT * FROM af_diamond_ledger WHERE idempotency_key = ? LIMIT 1');
    $st->execute([$idempotencyKey]);
    $row = $st->fetch();
    return $row ?: null;
}

function afDiamondsEnsureWallet(PDO $pdo, int $userId, bool $lock = false): array
{
    afDiamondsAssertInstalled($pdo);
    afDiamondsValidUserId($userId);

    $pdo->prepare('INSERT IGNORE INTO af_user_diamond_wallets (user_id, balance, locked_balance, lifetime_earned, lifetime_spent, status, created_at, updated_at) VALUES (?, 0, 0, 0, 0, \'active\', NOW(), NOW())')
        ->execute([$userId]);

    $sql = 'SELECT * FROM af_user_diamond_wallets WHERE user_id = ? LIMIT 1';
    if ($lock && $pdo->inTransaction()) $sql .= ' FOR UPDATE';
    $st = $pdo->prepare($sql);
    $st->execute([$userId]);
    $wallet = $st->fetch();
    if (!$wallet) throw new RuntimeException('Não foi possível criar a carteira de Diamantes.');
    return $wallet;
}

function afDiamondsGetWallet(PDO $pdo, int $userId): array
{
    return afDiamondsEnsureWallet($pdo, $userId, false);
}

function afDiamondsGetBalance(PDO $pdo, int $userId): int
{
    $wallet = afDiamondsGetWallet($pdo, $userId);
    return (int)($wallet['balance'] ?? 0);
}

function afDiamondsCanSpend(PDO $pdo, int $userId, int $amount): bool
{
    afDiamondsValidAmount($amount);
    return afDiamondsGetBalance($pdo, $userId) >= $amount;
}

function afDiamondsLedger(PDO $pdo, int $userId, int $limit = 30, int $offset = 0): array
{
    afDiamondsAssertInstalled($pdo);
    afDiamondsValidUserId($userId);
    $limit = max(1, min(200, $limit));
    $offset = max(0, $offset);
    $st = $pdo->prepare("SELECT * FROM af_diamond_ledger WHERE user_id = ? ORDER BY id DESC LIMIT {$limit} OFFSET {$offset}");
    $st->execute([$userId]);
    return $st->fetchAll() ?: [];
}

function afDiamondsMovement(PDO $pdo, int $userId, string $direction, int $amount, string $type, array $options = []): array
{
    afDiamondsAssertInstalled($pdo);
    afDiamondsValidUserId($userId);
    afDiamondsValidAmount($amount);
    $direction = afDiamondsNormalizeDirection($direction);
    $type = afDiamondsNormalizeType($type);

    $idempotencyKey = isset($options['idempotency_key']) && $options['idempotency_key'] !== ''
        ? substr(hash('sha256', (string)$options['idempotency_key']), 0, 64)
        : null;

    if ($idempotencyKey) {
        $existing = afDiamondsGetExistingLedgerByIdempotency($pdo, $idempotencyKey);
        if ($existing) {
            $wallet = afDiamondsGetWallet($pdo, $userId);
            return [
                'ok' => true,
                'duplicate' => true,
                'ledger_id' => (int)$existing['id'],
                'wallet_id' => (int)$wallet['id'],
                'balance_before' => (int)$existing['balance_before'],
                'balance_after' => (int)$existing['balance_after'],
                'balance' => (int)$wallet['balance'],
            ];
        }
    }

    $tx = afDiamondsTxBegin($pdo);
    try {
        $wallet = afDiamondsEnsureWallet($pdo, $userId, true);
        $walletId = (int)$wallet['id'];
        $status = (string)($wallet['status'] ?? 'active');
        if ($status === 'blocked') {
            throw new RuntimeException('Carteira de Diamantes bloqueada.');
        }

        $before = (int)$wallet['balance'];
        if ($direction === 'debit') {
            if ($before < $amount) {
                afDiamondsAudit($pdo, 'diamond_debit_denied_insufficient_balance', 'diamond_wallet', $walletId, ['balance'=>$before], ['amount'=>$amount], [
                    'subject_user_id' => $userId,
                    'severity' => 'low',
                    'type' => $type,
                    'reference_type' => $options['reference_type'] ?? null,
                    'reference_id' => $options['reference_id'] ?? null,
                ]);
                throw new RuntimeException('Diamantes insuficientes.');
            }
            $after = $before - $amount;
            $pdo->prepare('UPDATE af_user_diamond_wallets SET balance = ?, lifetime_spent = lifetime_spent + ?, last_transaction_at = NOW(), updated_at = NOW() WHERE id = ?')
                ->execute([$after, $amount, $walletId]);
        } else {
            $after = $before + $amount;
            $pdo->prepare('UPDATE af_user_diamond_wallets SET balance = ?, lifetime_earned = lifetime_earned + ?, last_transaction_at = NOW(), updated_at = NOW() WHERE id = ?')
                ->execute([$after, $amount, $walletId]);
        }

        $referenceType = isset($options['reference_type']) ? substr((string)$options['reference_type'], 0, 80) : null;
        $referenceId = isset($options['reference_id']) ? substr((string)$options['reference_id'], 0, 80) : null;
        $description = isset($options['description']) ? substr((string)$options['description'], 0, 255) : null;
        $metadata = isset($options['metadata']) && is_array($options['metadata']) ? $options['metadata'] : [];
        $orderId = isset($options['order_id']) && (int)$options['order_id'] > 0 ? (int)$options['order_id'] : null;
        $paidReactionId = isset($options['paid_reaction_id']) && (int)$options['paid_reaction_id'] > 0 ? (int)$options['paid_reaction_id'] : null;
        $adminId = isset($options['admin_id']) && (int)$options['admin_id'] > 0 ? (int)$options['admin_id'] : null;

        $st = $pdo->prepare('INSERT INTO af_diamond_ledger
            (user_id, wallet_id, type, direction, amount, balance_before, balance_after, reference_type, reference_id, order_id, paid_reaction_id, admin_id, description, metadata_json, idempotency_key, ip_hash, created_at)
            VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,NOW())');
        $st->execute([
            $userId,
            $walletId,
            $type,
            $direction,
            $amount,
            $before,
            $after,
            $referenceType,
            $referenceId,
            $orderId,
            $paidReactionId,
            $adminId,
            $description,
            afDiamondsJson($metadata),
            $idempotencyKey,
            afDiamondsIpHash(),
        ]);
        $ledgerId = (int)$pdo->lastInsertId();

        afDiamondsAudit($pdo, 'diamond_' . $direction, 'diamond_ledger', $ledgerId, ['balance'=>$before], ['balance'=>$after,'amount'=>$amount,'type'=>$type], [
            'subject_user_id' => $userId,
            'severity' => 'info',
            'wallet_id' => $walletId,
            'reference_type' => $referenceType,
            'reference_id' => $referenceId,
        ]);

        afDiamondsTxCommit($pdo, $tx);
        return [
            'ok' => true,
            'duplicate' => false,
            'ledger_id' => $ledgerId,
            'wallet_id' => $walletId,
            'balance_before' => $before,
            'balance_after' => $after,
            'balance' => $after,
        ];
    } catch (PDOException $e) {
        afDiamondsTxRollback($pdo, $tx);
        if ($idempotencyKey && $e->getCode() === '23000') {
            $existing = afDiamondsGetExistingLedgerByIdempotency($pdo, $idempotencyKey);
            if ($existing) {
                $wallet = afDiamondsGetWallet($pdo, $userId);
                return [
                    'ok' => true,
                    'duplicate' => true,
                    'ledger_id' => (int)$existing['id'],
                    'wallet_id' => (int)$wallet['id'],
                    'balance_before' => (int)$existing['balance_before'],
                    'balance_after' => (int)$existing['balance_after'],
                    'balance' => (int)$wallet['balance'],
                ];
            }
        }
        throw $e;
    } catch (Throwable $e) {
        afDiamondsTxRollback($pdo, $tx);
        throw $e;
    }
}

function afDiamondsCredit(PDO $pdo, int $userId, int $amount, string $type = 'system_correction', string $description = '', array $options = []): array
{
    $options['description'] = $description !== '' ? $description : ($options['description'] ?? 'Crédito de Diamantes');
    return afDiamondsMovement($pdo, $userId, 'credit', $amount, $type, $options);
}

function afDiamondsDebit(PDO $pdo, int $userId, int $amount, string $type = 'system_correction', string $description = '', array $options = []): array
{
    $options['description'] = $description !== '' ? $description : ($options['description'] ?? 'Débito de Diamantes');
    return afDiamondsMovement($pdo, $userId, 'debit', $amount, $type, $options);
}

function afDiamondsAdminAdjust(PDO $pdo, int $userId, int $amount, string $direction, string $reason, ?int $adminId = null): array
{
    if (trim($reason) === '') throw new InvalidArgumentException('O motivo do ajuste manual é obrigatório.');
    $result = afDiamondsMovement($pdo, $userId, $direction, abs($amount), 'admin_adjustment', [
        'description' => 'Ajuste manual: ' . $reason,
        'reference_type' => 'admin_adjustment',
        'reference_id' => $adminId ? (string)$adminId : null,
        'admin_id' => $adminId,
        'metadata' => ['reason' => $reason],
        'idempotency_key' => 'admin_adjustment:' . ($adminId ?: 'system') . ':' . $userId . ':' . $direction . ':' . abs($amount) . ':' . sha1($reason . microtime(true)),
    ]);

    afDiamondsAudit($pdo, 'diamond_admin_adjustment', 'diamond_wallet', $result['wallet_id'] ?? null, [], $result, [
        'subject_user_id' => $userId,
        'actor_admin_id' => $adminId,
        'severity' => 'medium',
        'reason' => $reason,
        'direction' => $direction,
        'amount' => abs($amount),
    ]);
    return $result;
}

function afDiamondsGetActivePackages(PDO $pdo): array
{
    afDiamondsAssertInstalled($pdo);
    $st = $pdo->query('SELECT * FROM af_diamond_packages WHERE active = 1 ORDER BY sort_order ASC, diamonds ASC, id ASC');
    return $st->fetchAll() ?: [];
}

function afDiamondsGetPackage(PDO $pdo, int $packageId, bool $activeOnly = true): ?array
{
    afDiamondsAssertInstalled($pdo);
    if ($packageId <= 0) return null;
    $sql = 'SELECT * FROM af_diamond_packages WHERE id = ?';
    if ($activeOnly) $sql .= ' AND active = 1';
    $sql .= ' LIMIT 1';
    $st = $pdo->prepare($sql);
    $st->execute([$packageId]);
    $package = $st->fetch();
    return $package ?: null;
}

function afDiamondsFormatBalance(int $balance): string
{
    return number_format(max(0, $balance), 0, ',', '.');
}

function afDiamondsFormatPrice(float|string $price, string $currency = 'BRL'): string
{
    $value = (float)$price;
    if ($currency === 'BRL') return 'R$ ' . number_format($value, 2, ',', '.');
    return number_format($value, 2, ',', '.') . ' ' . $currency;
}
