<?php

namespace App\Http\Controllers\Api\V1;

use App\Http\Controllers\Controller;
use App\Models\Campaign;
use App\Models\Lead;
use App\Models\User;
use App\Support\IpAllowlist;
use Illuminate\Http\JsonResponse;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;

class CustomPostbackController extends Controller
{
    public function __invoke(Request $request)
    {
        $data = $request->validate([
            'campaign_id' => ['required', 'integer', 'exists:campaigns,id'],
            'user_id' => ['required', 'integer', 'exists:users,id'],
            'transaction_id' => ['required', 'string', 'max:190', 'regex:/^[A-Za-z0-9._:-]+$/'],
            'timestamp' => ['nullable', 'integer', 'min:1'],
            'signature' => ['nullable', 'string', 'size:64', 'regex:/^[a-fA-F0-9]{64}$/'],
        ]);

        $signature = strtolower((string) ($request->header('X-Basketloot-Signature') ?: ($data['signature'] ?? '')));
        $timestamp = isset($data['timestamp']) ? (int) $data['timestamp'] : null;

        if ($signature === '' || !preg_match('/^[a-f0-9]{64}$/', $signature)) {
            return $this->error('invalid_signature', 'A valid postback signature is required.', 403);
        }

        if (!$this->sourceIsAllowed($request)) {
            return $this->error('unauthorized_source', 'The postback source is not authorized.', 403);
        }

        if (config('rewards.custom_postback_require_timestamp') && $timestamp === null) {
            return $this->error('missing_timestamp', 'A signed Unix timestamp is required.', 422);
        }

        if ($timestamp !== null && abs(now()->timestamp - $timestamp) > config('rewards.custom_postback_max_age_seconds')) {
            return $this->error('expired_postback', 'The postback timestamp is outside the accepted window.', 408);
        }

        $secret = (string) config('rewards.custom_postback_secret');
        $message = $data['campaign_id'].'|'.$data['user_id'].'|'.$data['transaction_id'];

        if ($timestamp !== null) {
            $message .= '|'.$timestamp;
        }

        $expected = hash_hmac('sha256', $message, $secret);

        if ($secret === '' || !hash_equals($expected, $signature)) {
            return $this->error('invalid_signature', 'Postback signature is invalid.', 403);
        }

        $campaign = Campaign::findOrFail($data['campaign_id']);
        $user = User::findOrFail($data['user_id']);

        if ($campaign->status !== 'active'
            || ($campaign->starts_at && $campaign->starts_at->isFuture())
            || ($campaign->ends_at && $campaign->ends_at->isPast())) {
            return $this->error('campaign_unavailable', 'The campaign is not active for crediting.', 409);
        }

        if ((bool) $user->is_banned) {
            return $this->error('account_unavailable', 'The member account is not eligible for crediting.', 409);
        }

        $reward = (float) $campaign->reward_points;
        $maximum = (float) config('rewards.max_offer_reward_points');

        if ($reward <= 0 || $reward > $maximum) {
            return $this->error('invalid_reward', 'The campaign reward is outside the accepted range.', 409);
        }

        $company = 'custom:'.$campaign->id;

        if (Lead::where('company', $company)->where('trx', $data['transaction_id'])->exists()) {
            return response()->json(['data' => ['status' => 'duplicate', 'transaction_id' => $data['transaction_id']]]);
        }

        try {
            $this->creditLead([
                'company' => $company,
                'user_id' => $data['user_id'],
                'campaign_id' => $campaign->id,
                'campaign_name' => $campaign->name,
                'amount' => (float) $campaign->reward_points,
                'payout' => (float) $campaign->payout,
                'trx' => $data['transaction_id'],
                'ip' => ip(),
                'country_code' => null,
            ]);
        } catch (QueryException $exception) {
            if (Lead::where('company', $company)->where('trx', $data['transaction_id'])->exists()) {
                return response()->json(['data' => ['status' => 'duplicate', 'transaction_id' => $data['transaction_id']]]);
            }

            throw $exception;
        }

        return response()->json(['data' => [
            'status' => 'credited',
            'transaction_id' => $data['transaction_id'],
            'user_id' => (int) $data['user_id'],
            'campaign_id' => (int) $data['campaign_id'],
            'reward_points' => (float) $campaign->reward_points,
        ]]);
    }

    private function sourceIsAllowed(Request $request): bool
    {
        $allowlist = config('rewards.custom_postback_allowed_ips', []);

        return empty($allowlist) || IpAllowlist::contains($allowlist, ip());
    }

    private function error(string $code, string $message, int $status): JsonResponse
    {
        return response()->json(['error' => ['code' => $code, 'message' => $message]], $status);
    }
}
