<?php

namespace App\Http\Controllers;

use App\Models\Deposit;
use App\Models\DepositConfirmation;
use App\Models\Transaction;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

/**
 * DepositWebhookController
 *
 * Called by your teammate's TS sweeper ONLY after he has already
 * verified and confirmed the deposit on-chain on his side.
 *
 * YOUR ONLY JOBS:
 *   1. Verify the HMAC signature  — proves it genuinely came from him
 *   2. Replay attack prevention   — 5-minute timestamp window
 *   3. Idempotency                — same sweep twice = no-op
 *   4. Chain + token whitelist    — reject anything that isn't USDT-SOL / USDT-ERC20
 *   5. Find user by deposit wallet
 *   6. Credit balance + complete deposit record
 *
 * NO RPC calls. NO on-chain verification. He already did all that.
 * The HMAC signature is the only trust boundary needed here.
 */
class DepositWebhookController extends Controller
{
    private const USDT_DECIMALS      = 6;
    private const REPLAY_WINDOW_SECS = 300;

    private function allowedTokens(): array
    {
        return [
            'ethereum' => [
                'address' => strtolower(config('services.crypto.eth_usdt_address', '')),
                'label'   => 'ERC20',
            ],
            'solana' => [
                'address' => config('services.crypto.sol_usdt_mint', ''),
                'label'   => 'SOL',
            ],
        ];
    }

    public function handle(Request $request): \Illuminate\Http\JsonResponse
    {
        // ── 1. Global deposit pause ───────────────────────────────────────────
        // $paused = \App\Models\SiteDetail::where('title', 'is_deposit_paused')->value('description');
        // if ($paused == 1) {
        //     Log::info('Webhook received but deposits are paused.');
        //     return response()->json(['status' => 'paused'], 200); // 200 so he doesn't retry
        // }
        // Log::warning('Webhook accepted.', [
        //         'req' =>  $request,
        //     ]);
            
        // return response()->json(['error' => 'Request faxxx expired'], 401);;

        // ── 2. Replay attack prevention ───────────────────────────────────────
        $timestamp = (int) $request->header('X-Timestamp');
        if (!$timestamp || abs(time() - $timestamp) > self::REPLAY_WINDOW_SECS) {
            Log::warning('Webhook rejected: timestamp out of window.', [
                'timestamp'   => $timestamp,
                'server_time' => time(),
            ]);
            return response()->json(['error' => 'Request expired'], 401);
        }

        // ── 3. HMAC signature verification ───────────────────────────────────
        // Verified against the RAW body before JSON parsing — key ordering
        // between serialisers can differ, raw string is the only safe input.
        $rawBody = $request->getContent();
        $secret  = config('services.webhook.secret');

        if (empty($secret)) {
            Log::critical('WEBHOOK_SECRET is not configured.');
            return response()->json(['error' => 'Server misconfiguration'], 500);
        }

        $expected = hash_hmac('sha256', "{$timestamp}.{$rawBody}", $secret);
        $received = (string) $request->header('X-Signature', '');

        // hash_equals() is constant-time — prevents timing side-channel attacks
        if (!hash_equals($expected, $received)) {
            Log::warning('Webhook rejected: HMAC mismatch.', ['ip' => $request->ip()]);
            return response()->json(['error' => 'Invalid signature'], 401);
        }

        // ── 4. Parse and validate payload shape ───────────────────────────────
        $payload = json_decode($rawBody, true);
        if (
            !is_array($payload)
            || empty($payload['deposits'])
            || !is_array($payload['deposits'])
            || !isset($payload['chain'], $payload['block_number'], $payload['sweep_tx'])
        ) {
            return response()->json(['error' => 'Malformed payload'], 422);
        }

        // ── 5. Chain whitelist ────────────────────────────────────────────────
        $chain   = strtolower(trim($payload['chain']));
        $allowed = $this->allowedTokens();

        if (!array_key_exists($chain, $allowed)) {
            return response()->json([
                'error' => "Chain '{$chain}' not supported. Only 'ethereum' and 'solana' are accepted.",
            ], 422);
        }

        // ── 6. Sweep-level idempotency ────────────────────────────────────────
        $idempotencyKey = (string) $request->header('Idempotency-Key', '');
        if (empty($idempotencyKey)) {
            return response()->json(['error' => 'Missing Idempotency-Key header'], 422);
        }

        if (DepositConfirmation::where('idempotency_key', $idempotencyKey)->exists()) {
            Log::info("Webhook duplicate ignored: {$idempotencyKey}");
            return response()->json(['status' => 'already_processed'], 200);
        }

        // ── 7. Process each deposit ───────────────────────────────────────────
        $results = [];
        foreach ($payload['deposits'] as $index => $dep) {
            try {
                $results[] = $this->processDeposit($dep, $chain, $allowed[$chain]);
            } catch (\Throwable $e) {
                Log::error("Webhook: failed on deposit index {$index}.", [
                    'deposit' => $dep,
                    'chain'   => $chain,
                    'error'   => $e->getMessage(),
                    'trace'   => $e->getTraceAsString(),
                ]);
                $results[] = [
                    'id'      => $dep['id'] ?? "index_{$index}",
                    'status'  => 'error',
                    'message' => $e->getMessage(),
                ];
            }
        }

        // ── 8. Write idempotency log AFTER processing ─────────────────────────
        // Written last — if we crash mid-processing, the webhook stays un-logged
        // so his retry re-delivers it cleanly. Per-deposit guard handles duplicates.
        DepositConfirmation::create([
            'idempotency_key' => $idempotencyKey,
            'chain'           => $chain,
            'sweep_tx'        => $payload['sweep_tx'],
            'block_number'    => (int) $payload['block_number'],
            'raw_payload'     => $rawBody,
            'status'          => 'processed',
        ]);

        return response()->json(['status' => 'ok', 'results' => $results], 200);
    }


    private function processDeposit(array $dep, string $chain, array $tokenConfig): array
    {
        if (empty($dep['id']) || empty($dep['user']) || !isset($dep['amount']) || empty($dep['token'])) {
            throw new \InvalidArgumentException('Deposit entry missing required fields (id, user, amount, token).');
        }

        $depositId = (string) $dep['id'];
        $rawAmount = (string) $dep['amount'];
        $tokenAddr = strtolower(trim($dep['token']));
        $depUserAddy = (string) $dep['user'];

        // Normalise address casing by chain
        $fromAddr = $chain === 'ethereum'
            ? strtolower(trim($dep['user']))  // ETH: case-insensitive
            : trim($dep['user']);             // Solana: case-sensitive base58

        // ── Token address double-check ────────────────────────────────────────
        // He already verified this on-chain, but a cheap whitelist check here
        // catches any misconfiguration on either side before touching the DB.
        if (strtolower($tokenAddr) !== strtolower($tokenConfig['address'])) {
            Log::warning("Webhook: unexpected token '{$tokenAddr}' on {$chain}.", [
                'deposit_id' => $depositId,
                'expected'   => $tokenConfig['address'],
            ]);
            return ['id' => $depositId, 'status' => 'skipped', 'reason' => 'token_not_whitelisted'];
        }

        // ── Convert raw units → USD ───────────────────────────────────────────
        // USDT = 6 decimals on both chains: "1500000" → $1.50
        $usdAmount = round(
            (float) bcdiv($rawAmount, (string) pow(10, self::USDT_DECIMALS), 10),
            2
        );

        if ($usdAmount <= 0) {
            throw new \InvalidArgumentException(
                "USD amount resolved to zero for deposit {$depositId} (raw: {$rawAmount})."
            );
        }

        return DB::transaction(function () use (
            $depositId,
            $chain,
            $depUserAddy,
            $fromAddr,
            $rawAmount,
            $usdAmount,
            $tokenConfig
        ) {
            // Per-deposit idempotency with a row lock
            // $pendingDeposit = Deposit::where('payment_id', $depositId)
            //     ->lockForUpdate()
            //     ->first();
            
            $walletCol = $chain === 'ethereum' ? 'eth_deposit_wallet' : 'sol_deposit_wallet';
            $userrr = User::whereRaw('LOWER(' . $walletCol . ') = ?', [strtolower($depUserAddy)])->first();
            
            $pendingDeposit = Deposit::where('user_id', $userrr->id)
                ->where('type', 'crypto_auto')
                ->where('amount', $usdAmount)
                ->where('network', $tokenConfig['label'])
                ->whereNull('status')
                ->orderByDesc('created_at')
                ->lockForUpdate()
                ->first();

            if ($pendingDeposit && $pendingDeposit->status == 1) {
                return ['id' => $depositId, 'status' => 'already_credited'];
            }

            // ── Find user by their saved deposit wallet ───────────────────────
            // DepositConfirmController wrote this wallet address when the
            // frontend submitted the tx. We look up the user by that column.

            $user = User::whereRaw("LOWER({$walletCol}) = ?", [$fromAddr])
                ->lockForUpdate()
                ->first();

            if (!$user) {
                Log::warning("Webhook: no user for wallet '{$fromAddr}' on {$chain}.", [
                    'deposit_id' => $depositId,
                ]);
                if ($pendingDeposit) $pendingDeposit->update(['status' => 2]);
                return ['id' => $depositId, 'status' => 'skipped', 'reason' => 'user_not_found'];
            }

            if ($user->status == 0) {
                Log::info("Webhook: user #{$user->id} is banned — deposit {$depositId} skipped.");
                if ($pendingDeposit) $pendingDeposit->update(['status' => 2]);
                return ['id' => $depositId, 'status' => 'skipped', 'reason' => 'user_banned'];
            }

            // ── Credit balance ────────────────────────────────────────────────
            $user->increment('balance', $usdAmount);

            // ── Complete or create the Deposit record ─────────────────────────
            if ($pendingDeposit) {
                // Normal path: frontend registered it, webhook completes it
                $pendingDeposit->update([
                    'amount'  => $usdAmount,           // webhook amount is the source of truth
                    'network' => $tokenConfig['label'],
                    'status'  => 1,
                ]);
            } else {
                // Edge case: user closed the page before /deposit/confirm fired,
                // but tx went through and webhook arrived anyway. Create the record.
                Deposit::create([
                    'user_id'    => $user->id,
                    'payment_id' => $depositId,
                    'amount'     => $usdAmount,
                    'network'    => $tokenConfig['label'],
                    'type'       => 'crypto_auto',
                    'status'     => 1,
                ]);
            }

            // ── Audit log ─────────────────────────────────────────────────────
            Transaction::create([
                'user_id' => $user->id,
                'type'    => 'deposit',
                'amount'  => $usdAmount,
                'status'  => 1,
            ]);

            Log::info("Webhook: \${$usdAmount} USDT ({$tokenConfig['label']}) credited to user #{$user->id}.", [
                'deposit_id' => $depositId,
                'chain'      => $chain,
            ]);

            return [
                'id'      => $depositId,
                'status'  => 'credited',
                'amount'  => $usdAmount,
                'network' => $tokenConfig['label'],
                'user_id' => $user->id,
            ];
        });
    }
}
