<?php
/**
 * AURA FARMING — Diamantes — Parte 05
 * Integração Mercado Pago para compra e liberação automática de Diamantes.
 *
 * Segurança:
 * - Diamantes só são creditados após consultar o pagamento direto na API do Mercado Pago.
 * - Retorno visual do navegador não libera saldo sozinho.
 * - Idempotência impede crédito duplicado em webhook repetido.
 */

if (!defined('AURA_DIAMONDS_PART05_VERSION')) {
    define('AURA_DIAMONDS_PART05_VERSION', '5.0.0');
}

require_once __DIR__ . '/diamond_shop_functions.php';

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

function afDiamondMpConfig(PDO $pdo, string $key, mixed $default = null): mixed
{
    $envKey = strtoupper($key);
    if (function_exists('af_env')) {
        $env = af_env($envKey, null);
        if ($env !== null && $env !== '') return $env;
    }

    if (defined($envKey) && constant($envKey) !== '') {
        return constant($envKey);
    }

    try {
        $st = $pdo->prepare('SELECT valor, tipo FROM af_configuracoes WHERE chave = ? LIMIT 1');
        $st->execute([$key]);
        $row = $st->fetch();
        if (!$row) return $default;
        return match ((string)($row['tipo'] ?? 'string')) {
            'bool' => filter_var($row['valor'], FILTER_VALIDATE_BOOLEAN),
            'int' => (int)$row['valor'],
            'float' => (float)$row['valor'],
            'json' => json_decode((string)$row['valor'], true) ?: $default,
            default => (string)$row['valor'],
        };
    } catch (Throwable $e) {
        return $default;
    }
}

function afDiamondMpSetConfig(PDO $pdo, string $key, mixed $value, string $type = 'string', int $public = 0, string $description = ''): void
{
    $val = is_bool($value) ? ($value ? '1' : '0') : (string)$value;
    $st = $pdo->prepare('INSERT INTO af_configuracoes (`chave`, `valor`, `tipo`, `publica`, `descricao`) VALUES (?,?,?,?,?)
        ON DUPLICATE KEY UPDATE `valor` = VALUES(`valor`), `tipo` = VALUES(`tipo`), `publica` = VALUES(`publica`), `descricao` = VALUES(`descricao`), `atualizado_em` = NOW()');
    $st->execute([$key, $val, $type, $public, $description]);
}

function afDiamondMpAccessToken(PDO $pdo): string
{
    return trim((string)afDiamondMpConfig($pdo, 'diamond_mp_access_token', ''));
}

function afDiamondMpWebhookToken(PDO $pdo): string
{
    $token = trim((string)afDiamondMpConfig($pdo, 'diamond_mp_webhook_token', ''));
    if ($token === '') {
        $token = bin2hex(random_bytes(24));
        afDiamondMpSetConfig($pdo, 'diamond_mp_webhook_token', $token, 'string', 0, 'Token interno da URL do webhook Mercado Pago para Diamantes.');
    }
    return $token;
}

function afDiamondMpEnabled(PDO $pdo): bool
{
    return filter_var(afDiamondMpConfig($pdo, 'diamond_mp_enabled', false), FILTER_VALIDATE_BOOLEAN)
        && afDiamondMpAccessToken($pdo) !== '';
}

function afDiamondMpEnvironment(PDO $pdo): string
{
    $env = strtolower(trim((string)afDiamondMpConfig($pdo, 'diamond_mp_environment', 'production')));
    return in_array($env, ['production', 'sandbox'], true) ? $env : 'production';
}

function afDiamondMpWebhookUrl(PDO $pdo): string
{
    return SITE_URL . '/api/v1/diamonds/mercado-pago-webhook.php?token=' . rawurlencode(afDiamondMpWebhookToken($pdo));
}

function afDiamondMpReturnUrl(string $status): string
{
    $status = in_array($status, ['success', 'pending', 'failure'], true) ? $status : 'pending';
    return SITE_URL . '/pages/diamantes-pagamento-retorno.php?retorno=' . $status;
}

function afDiamondMpLog(PDO $pdo, ?int $orderId, ?string $paymentId, string $eventType, string $source, string $status, array $payload = []): void
{
    try {
        $st = $pdo->prepare('INSERT INTO af_diamond_payment_logs (order_id, payment_id, event_type, source, status, payload_json, ip_hash, created_at)
            VALUES (?,?,?,?,?,?,?,NOW())');
        $st->execute([
            $orderId ?: null,
            $paymentId ? substr($paymentId, 0, 120) : null,
            substr($eventType, 0, 80),
            substr($source, 0, 40),
            substr($status, 0, 40),
            afDiamondMpJson($payload),
            function_exists('afDiamondsIpHash') ? afDiamondsIpHash() : null,
        ]);
    } catch (Throwable $e) {
        if (defined('APP_DEBUG') && APP_DEBUG) error_log('[AURA-DIAMONDS-MP-LOG] ' . $e->getMessage());
    }
}

function afDiamondMpHttp(PDO $pdo, string $method, string $url, ?array $body = null, ?string $idempotencyKey = null): array
{
    $token = afDiamondMpAccessToken($pdo);
    if ($token === '') {
        throw new RuntimeException('Mercado Pago ainda não configurado. Informe o Access Token no painel administrativo.');
    }
    if (!function_exists('curl_init')) {
        throw new RuntimeException('A extensão cURL do PHP não está habilitada no servidor.');
    }

    $headers = [
        'Authorization: Bearer ' . $token,
        'Content-Type: application/json',
        'Accept: application/json',
    ];
    if ($idempotencyKey) {
        $headers[] = 'X-Idempotency-Key: ' . substr(hash('sha256', $idempotencyKey), 0, 64);
    }

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CUSTOMREQUEST => strtoupper($method),
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_SSL_VERIFYHOST => 2,
    ]);
    if ($body !== null) {
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
    }

    $raw = curl_exec($ch);
    $curlErr = curl_error($ch);
    $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($raw === false) {
        throw new RuntimeException('Falha ao conectar ao Mercado Pago: ' . $curlErr);
    }

    $json = json_decode((string)$raw, true);
    if (!is_array($json)) {
        $json = ['raw' => (string)$raw];
    }

    if ($code < 200 || $code >= 300) {
        $msg = $json['message'] ?? $json['error'] ?? 'Erro ao consultar Mercado Pago.';
        throw new RuntimeException('Mercado Pago retornou erro HTTP ' . $code . ': ' . $msg);
    }

    return $json;
}

function afDiamondMpUserForOrder(PDO $pdo, int $userId): array
{
    try {
        $st = $pdo->prepare('SELECT id, nome, email FROM af_usuarios WHERE id = ? LIMIT 1');
        $st->execute([$userId]);
        return $st->fetch() ?: [];
    } catch (Throwable $e) {
        return [];
    }
}

function afDiamondMpGetOrderByUuid(PDO $pdo, string $uuid): ?array
{
    $st = $pdo->prepare('SELECT * FROM af_diamond_orders WHERE public_uuid = ? LIMIT 1');
    $st->execute([trim($uuid)]);
    $row = $st->fetch();
    return $row ?: null;
}

function afDiamondMpGetOrderByExternalReference(PDO $pdo, string $externalReference, bool $lock = false): ?array
{
    $sql = 'SELECT * FROM af_diamond_orders WHERE external_reference = ? LIMIT 1';
    if ($lock && $pdo->inTransaction()) $sql .= ' FOR UPDATE';
    $st = $pdo->prepare($sql);
    $st->execute([trim($externalReference)]);
    $row = $st->fetch();
    return $row ?: null;
}

function afDiamondMpGetOrderById(PDO $pdo, int $orderId, bool $lock = false): ?array
{
    $sql = 'SELECT * FROM af_diamond_orders WHERE id = ? LIMIT 1';
    if ($lock && $pdo->inTransaction()) $sql .= ' FOR UPDATE';
    $st = $pdo->prepare($sql);
    $st->execute([$orderId]);
    $row = $st->fetch();
    return $row ?: null;
}

function afDiamondMpCreatePreferenceForOrder(PDO $pdo, array $order): array
{
    if (!afDiamondMpEnabled($pdo)) {
        throw new RuntimeException('Mercado Pago ainda não está ativo para compra de Diamantes. Configure e ative no painel administrativo.');
    }

    $orderId = (int)($order['id'] ?? 0);
    if ($orderId <= 0) throw new RuntimeException('Pedido inválido para pagamento.');
    if ((string)($order['status'] ?? '') !== 'pending') {
        throw new RuntimeException('Este pedido não está pendente para pagamento.');
    }

    $user = afDiamondMpUserForOrder($pdo, (int)$order['user_id']);
    $title = trim((string)($order['package_name'] ?? 'Pacote de Diamantes'));
    $description = (int)$order['total_diamonds'] . ' Diamantes no Aura Farming';

    $payload = [
        'items' => [[
            'id' => (string)$order['package_code'],
            'title' => $title,
            'description' => $description,
            'quantity' => 1,
            'currency_id' => (string)($order['currency'] ?: 'BRL'),
            'unit_price' => (float)$order['amount'],
        ]],
        'external_reference' => (string)$order['external_reference'],
        'notification_url' => afDiamondMpWebhookUrl($pdo),
        'back_urls' => [
            'success' => afDiamondMpReturnUrl('success'),
            'pending' => afDiamondMpReturnUrl('pending'),
            'failure' => afDiamondMpReturnUrl('failure'),
        ],
        'auto_return' => 'approved',
        'statement_descriptor' => 'AURA FARMING',
        'metadata' => [
            'aura_farming' => true,
            'diamond_order_id' => $orderId,
            'public_uuid' => (string)$order['public_uuid'],
            'user_id' => (int)$order['user_id'],
            'package_id' => (int)$order['package_id'],
        ],
    ];

    if (!empty($user['email'])) {
        $payload['payer'] = [
            'name' => (string)($user['nome'] ?? ''),
            'email' => (string)$user['email'],
        ];
    }

    $response = afDiamondMpHttp($pdo, 'POST', 'https://api.mercadopago.com/checkout/preferences', $payload, 'diamond_preference:' . $orderId . ':' . $order['idempotency_key']);
    $preferenceId = (string)($response['id'] ?? '');
    $checkoutUrl = afDiamondMpEnvironment($pdo) === 'sandbox'
        ? (string)($response['sandbox_init_point'] ?? $response['init_point'] ?? '')
        : (string)($response['init_point'] ?? $response['sandbox_init_point'] ?? '');

    if ($preferenceId === '' || $checkoutUrl === '') {
        throw new RuntimeException('Mercado Pago não retornou o link de pagamento.');
    }

    $st = $pdo->prepare('UPDATE af_diamond_orders SET mercado_pago_preference_id = ?, checkout_url = ?, updated_at = NOW() WHERE id = ?');
    $st->execute([$preferenceId, $checkoutUrl, $orderId]);

    afDiamondMpLog($pdo, $orderId, null, 'preference_created', 'checkout', 'ok', ['request' => $payload, 'response' => $response]);
    afDiamondsAudit($pdo, 'diamond_mp_preference_created', 'diamond_order', $orderId, [], ['preference_id' => $preferenceId], [
        'subject_user_id' => (int)$order['user_id'],
        'source' => 'diamonds_part05_mp',
    ]);

    $order['mercado_pago_preference_id'] = $preferenceId;
    $order['checkout_url'] = $checkoutUrl;
    return ['order' => $order, 'preference' => $response, 'checkout_url' => $checkoutUrl];
}

function afDiamondShopStartMercadoPagoCheckout(PDO $pdo, int $userId, int $packageId): array
{
    $order = afDiamondShopCreatePendingOrder($pdo, $userId, $packageId);
    return afDiamondMpCreatePreferenceForOrder($pdo, $order);
}

function afDiamondMpFetchPayment(PDO $pdo, string $paymentId): array
{
    $paymentId = preg_replace('/[^0-9]/', '', $paymentId);
    if ($paymentId === '') throw new RuntimeException('ID de pagamento Mercado Pago inválido.');
    return afDiamondMpHttp($pdo, 'GET', 'https://api.mercadopago.com/v1/payments/' . rawurlencode($paymentId));
}

function afDiamondMpOrderStatusFromPayment(array $payment): string
{
    $status = (string)($payment['status'] ?? '');
    return match ($status) {
        'approved' => 'approved',
        'rejected' => 'rejected',
        'cancelled' => 'cancelled',
        'refunded', 'charged_back' => 'refunded',
        'pending', 'in_process', 'authorized' => 'pending',
        default => 'failed',
    };
}

function afDiamondMpPaymentExternalReference(array $payment): string
{
    return trim((string)($payment['external_reference'] ?? ''));
}

function afDiamondMpPaymentId(array $payment): string
{
    return (string)($payment['id'] ?? '');
}

function afDiamondMpPaymentAmount(array $payment): float
{
    if (isset($payment['transaction_amount'])) return (float)$payment['transaction_amount'];
    if (isset($payment['transaction_details']['total_paid_amount'])) return (float)$payment['transaction_details']['total_paid_amount'];
    return 0.0;
}

function afDiamondMpUpdateOrderFromPayment(PDO $pdo, int $orderId, array $payment, string $mappedStatus, string $source): void
{
    $paymentId = afDiamondMpPaymentId($payment);
    $status = (string)($payment['status'] ?? '');
    $statusDetail = (string)($payment['status_detail'] ?? '');
    $paymentMethod = (string)($payment['payment_method_id'] ?? $payment['payment_type_id'] ?? '');
    $merchantOrderId = (string)($payment['order']['id'] ?? '');

    $st = $pdo->prepare('UPDATE af_diamond_orders
        SET status = ?, mercado_pago_payment_id = COALESCE(NULLIF(?, \'\'), mercado_pago_payment_id), mercado_pago_merchant_order_id = COALESCE(NULLIF(?, \'\'), mercado_pago_merchant_order_id), mercado_pago_status = ?, mercado_pago_status_detail = ?, payment_method = ?, webhook_payload = ?, updated_at = NOW()
        WHERE id = ?');
    $st->execute([
        $mappedStatus,
        $paymentId,
        $merchantOrderId,
        $status,
        $statusDetail,
        $paymentMethod,
        afDiamondMpJson($payment),
        $orderId,
    ]);

    afDiamondMpLog($pdo, $orderId, $paymentId, 'payment_update', $source, $mappedStatus, $payment);
}

function afDiamondMpProcessPayment(PDO $pdo, string $paymentId, string $source = 'webhook'): array
{
    afDiamondsAssertInstalled($pdo);
    $payment = afDiamondMpFetchPayment($pdo, $paymentId);
    $paymentId = afDiamondMpPaymentId($payment);
    $externalReference = afDiamondMpPaymentExternalReference($payment);
    $mappedStatus = afDiamondMpOrderStatusFromPayment($payment);

    if ($externalReference === '') {
        afDiamondMpLog($pdo, null, $paymentId, 'payment_without_external_reference', $source, 'ignored', $payment);
        return ['ok' => false, 'credited' => false, 'message' => 'Pagamento sem referência externa do Aura Farming.', 'payment' => $payment];
    }

    $order = afDiamondMpGetOrderByExternalReference($pdo, $externalReference, false);
    if (!$order) {
        afDiamondMpLog($pdo, null, $paymentId, 'order_not_found', $source, 'ignored', $payment);
        return ['ok' => false, 'credited' => false, 'message' => 'Pedido não encontrado para este pagamento.', 'payment' => $payment];
    }

    $tx = afDiamondsTxBegin($pdo);
    try {
        $order = afDiamondMpGetOrderById($pdo, (int)$order['id'], true);
        if (!$order) throw new RuntimeException('Pedido não encontrado durante o processamento.');

        // Se outro pedido já foi creditado com este payment_id, bloqueia qualquer novo crédito.
        if ($paymentId !== '') {
            $dup = $pdo->prepare('SELECT id FROM af_diamond_orders WHERE mercado_pago_payment_id = ? AND id <> ? AND credited_at IS NOT NULL LIMIT 1');
            $dup->execute([$paymentId, (int)$order['id']]);
            if ($dup->fetchColumn()) {
                afDiamondMpLog($pdo, (int)$order['id'], $paymentId, 'duplicate_payment_id_blocked', $source, 'blocked', $payment);
                throw new RuntimeException('Pagamento já usado em outro pedido de Diamantes.');
            }
        }

        // Atualiza status para pendente/recusado/cancelado sem creditar.
        if ($mappedStatus !== 'approved') {
            afDiamondMpUpdateOrderFromPayment($pdo, (int)$order['id'], $payment, $mappedStatus, $source);
            afDiamondsTxCommit($pdo, $tx);
            return ['ok' => true, 'credited' => false, 'status' => $mappedStatus, 'order_id' => (int)$order['id'], 'payment' => $payment];
        }

        // Pagamento aprovado: valida valor e evita crédito duplicado.
        $expected = (float)$order['amount'];
        $paid = afDiamondMpPaymentAmount($payment);
        if (abs($paid - $expected) > 0.02) {
            $pdo->prepare('UPDATE af_diamond_orders SET status = \'failed\', mercado_pago_payment_id = ?, mercado_pago_status = ?, mercado_pago_status_detail = ?, webhook_payload = ?, updated_at = NOW() WHERE id = ?')
                ->execute([$paymentId, (string)($payment['status'] ?? ''), 'amount_mismatch', afDiamondMpJson($payment), (int)$order['id']]);
            afDiamondMpLog($pdo, (int)$order['id'], $paymentId, 'amount_mismatch', $source, 'blocked', ['expected' => $expected, 'paid' => $paid, 'payment' => $payment]);
            throw new RuntimeException('Valor pago não confere com o pedido. Crédito bloqueado.');
        }

        if (!empty($order['credited_at']) || (string)$order['status'] === 'approved') {
            afDiamondMpUpdateOrderFromPayment($pdo, (int)$order['id'], $payment, 'approved', $source);
            afDiamondMpLog($pdo, (int)$order['id'], $paymentId, 'already_credited', $source, 'duplicate_ok', $payment);
            afDiamondsTxCommit($pdo, $tx);
            return ['ok' => true, 'credited' => false, 'duplicate' => true, 'status' => 'approved', 'order_id' => (int)$order['id'], 'payment' => $payment];
        }

        $totalDiamonds = (int)$order['total_diamonds'];
        if ($totalDiamonds <= 0) throw new RuntimeException('Pedido aprovado sem quantidade válida de Diamantes.');

        $ledger = afDiamondsCredit($pdo, (int)$order['user_id'], $totalDiamonds, 'purchase_credit', 'Compra de Diamantes aprovada via Mercado Pago', [
            'order_id' => (int)$order['id'],
            'reference_type' => 'diamond_order',
            'reference_id' => (string)$order['id'],
            'idempotency_key' => 'mp_payment_credit:' . $paymentId . ':order:' . (int)$order['id'],
            'metadata' => [
                'source' => $source,
                'mercado_pago_payment_id' => $paymentId,
                'external_reference' => $externalReference,
                'amount' => $expected,
            ],
        ]);

        $st = $pdo->prepare('UPDATE af_diamond_orders
            SET status = \'approved\', mercado_pago_payment_id = ?, mercado_pago_merchant_order_id = ?, mercado_pago_status = ?, mercado_pago_status_detail = ?, payment_method = ?, webhook_payload = ?, credited_at = COALESCE(credited_at, NOW()), updated_at = NOW()
            WHERE id = ?');
        $st->execute([
            $paymentId,
            (string)($payment['order']['id'] ?? ''),
            (string)($payment['status'] ?? ''),
            (string)($payment['status_detail'] ?? ''),
            (string)($payment['payment_method_id'] ?? $payment['payment_type_id'] ?? ''),
            afDiamondMpJson($payment),
            (int)$order['id'],
        ]);

        afDiamondMpLog($pdo, (int)$order['id'], $paymentId, 'diamonds_credited', $source, 'approved', ['ledger' => $ledger, 'payment' => $payment]);
        afDiamondsAudit($pdo, 'diamond_order_approved_and_credited', 'diamond_order', (int)$order['id'], $order, ['ledger' => $ledger], [
            'subject_user_id' => (int)$order['user_id'],
            'source' => 'diamonds_part05_mp',
            'payment_id' => $paymentId,
        ]);

        afDiamondsTxCommit($pdo, $tx);
        return ['ok' => true, 'credited' => true, 'status' => 'approved', 'order_id' => (int)$order['id'], 'ledger' => $ledger, 'payment' => $payment];
    } catch (Throwable $e) {
        afDiamondsTxRollback($pdo, $tx);
        afDiamondMpLog($pdo, isset($order['id']) ? (int)$order['id'] : null, $paymentId, 'payment_process_error', $source, 'error', ['error' => $e->getMessage(), 'payment' => $payment]);
        throw $e;
    }
}

function afDiamondMpExtractPaymentId(array $payload, array $query): ?string
{
    $candidates = [
        $query['data_id'] ?? null,
        $query['data.id'] ?? null,
        $query['id'] ?? null,
        $query['payment_id'] ?? null,
        $query['collection_id'] ?? null,
        $payload['data']['id'] ?? null,
        $payload['id'] ?? null,
    ];
    foreach ($candidates as $candidate) {
        $id = preg_replace('/[^0-9]/', '', (string)$candidate);
        if ($id !== '') return $id;
    }
    return null;
}

function afDiamondMpIsPaymentNotification(array $payload, array $query): bool
{
    $type = strtolower((string)($query['type'] ?? $payload['type'] ?? ''));
    $topic = strtolower((string)($query['topic'] ?? $payload['topic'] ?? ''));
    $action = strtolower((string)($payload['action'] ?? ''));
    return str_contains($type, 'payment') || str_contains($topic, 'payment') || str_contains($action, 'payment') || !empty($query['payment_id']) || !empty($query['collection_id']);
}

function afDiamondMpSaveAdminConfig(PDO $pdo, array $data): void
{
    $enabled = !empty($data['diamond_mp_enabled']);
    $environment = in_array((string)($data['diamond_mp_environment'] ?? 'production'), ['production', 'sandbox'], true)
        ? (string)$data['diamond_mp_environment'] : 'production';

    afDiamondMpSetConfig($pdo, 'diamond_mp_enabled', $enabled, 'bool', 0, 'Ativa compra de Diamantes via Mercado Pago.');
    afDiamondMpSetConfig($pdo, 'diamond_mp_environment', $environment, 'string', 0, 'Ambiente Mercado Pago: production ou sandbox.');
    afDiamondMpSetConfig($pdo, 'diamond_mp_public_key', trim((string)($data['diamond_mp_public_key'] ?? '')), 'string', 0, 'Public Key Mercado Pago para Diamantes.');

    $accessToken = trim((string)($data['diamond_mp_access_token'] ?? ''));
    if ($accessToken !== '') {
        afDiamondMpSetConfig($pdo, 'diamond_mp_access_token', $accessToken, 'string', 0, 'Access Token Mercado Pago para Diamantes.');
    }

    $webhookToken = trim((string)($data['diamond_mp_webhook_token'] ?? ''));
    if ($webhookToken === '') $webhookToken = afDiamondMpWebhookToken($pdo);
    $webhookToken = preg_replace('/[^a-zA-Z0-9_]/', '', $webhookToken) ?: bin2hex(random_bytes(24));
    afDiamondMpSetConfig($pdo, 'diamond_mp_webhook_token', $webhookToken, 'string', 0, 'Token interno da URL do webhook Mercado Pago para Diamantes.');

    afDiamondsAudit($pdo, 'diamond_mp_config_updated', 'diamond_config', null, [], ['enabled' => $enabled, 'environment' => $environment], [
        'source' => 'diamonds_part05_admin',
        'severity' => 'medium',
    ]);
}
