<?php

namespace App\Http\Controllers;

use App\Exceptions\PostbackRejectedException;
use App\Models\Lead;
use App\Models\CallbackLog;
use App\Models\OfferwallClick;
use App\Support\LeadBalanceLedger;
use App\Support\PostbackVerifier;
use App\Support\CallbackPayload;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\DB;

class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;

    /**
     * Credit a verified provider conversion and persist its lead as one unit.
     * The unique (company, trx) index is the final concurrency guard: if a
     * duplicate insert loses the race, every balance/referral update rolls back.
     */
    public function creditLead(array $data): void
    {
        DB::transaction(function () use ($data) {
            $lead = $this->saveLead($data);

            if ($lead->status === 'approved') {
                app(LeadBalanceLedger::class)->adjust(
                    $lead,
                    (float) $lead->original_points,
                    'postback_credit',
                    'postback:'.hash('sha256', mb_strtolower($lead->company).'|'.$lead->trx),
                    null,
                    'coins',
                    (float) $lead->original_points,
                    'Verified provider postback.'
                );
            }

            $this->attributeOfferClick($lead);
        }, 3);
    }

    /**
     * Verify an inbound provider postback before any points are credited.
     * Throws PostbackRejectedException (with an httpStatus to respond with)
     * on failure; returns the resolved transaction id (or null) on success.
     */
    public function verifyProviderPostback(Request $request, string $providerName, string $trxParam = 'trx'): ?string
    {
        try {
            return app(PostbackVerifier::class)->verify($request, $providerName, $trxParam);
        } catch (PostbackRejectedException $e) {
            CallbackLog::create([
                'user_id' => is_numeric($request->input('user_id')) ? (int) $request->input('user_id') : null,
                'provider' => $providerName,
                'status' => $e->duplicate ? 'duplicate' : 'rejected',
                'endpoint' => $request->path(),
                'method' => $request->method(),
                'ip' => $request->ip(),
                'payload' => CallbackPayload::sanitize($request->all()),
                'message' => $e->getMessage(),
            ]);

            throw $e;
        }
    }

    public function saveLead($data): Lead
    {
        $status = \App\Support\LeadRuleEvaluator::statusFor($data);

        $lead = Lead::create([
            'company' => $data['company'],
            'type' => str_contains(strtolower($data['company']), 'survey') || str_contains(strtolower($data['company']), 'cpx') ? 'survey' : 'offer',
            'status' => $status,
            'user_id' => $data['user_id'],
            'offer_id' => $data['campaign_id'] ?? null,
            'offer_name' => $data['campaign_name'] ?? null,
            'points' => $data['amount'],
            'original_points' => $data['amount'],
            'credited_points' => 0,
            'payout' => $data['payout'],
            'ip' => $data['ip'] ?? null,
            'country' => $data['country_code'] ?? null,
            'trx' => $data['trx'] ?? null,
            'points_reversed_at' => $status === 'approved' ? null : now(),
        ]);

        CallbackLog::create([
            'user_id' => $lead->user_id,
            'lead_id' => $lead->id,
            'provider' => $lead->company,
            'status' => 'success',
            'endpoint' => request()?->path(),
            'method' => request()?->method(),
            'ip' => request()?->ip(),
            'payload' => CallbackPayload::sanitize($data),
            'message' => 'Lead credited successfully.',
        ]);

        return $lead;
    }

    public function reverseLeadPoints(
        Lead $lead,
        ?float $amount = null,
        ?string $token = null,
        $admin = null,
        ?string $note = null
    ): void
    {
        $lead->refresh();
        $credited = max(0, (float) $lead->credited_points);
        $amount = $amount === null ? $credited : min($credited, max(0, $amount));

        if ($amount <= 0 || !$lead->user_id) {
            return;
        }

        app(LeadBalanceLedger::class)->adjust(
            $lead,
            -$amount,
            'chargeback',
            $token ?: 'chargeback:'.hash('sha256', $lead->id.'|'.microtime(true)),
            $admin,
            'coins',
            $amount,
            $note
        );
    }

    public function awardLeadPoints(
        Lead $lead,
        ?float $amount = null,
        ?string $token = null,
        $admin = null,
        ?string $note = null
    ): void
    {
        $lead->refresh();
        $original = max(0, (float) ($lead->original_points ?? $lead->points));
        $remaining = max(0, $original - (float) $lead->credited_points);
        $amount = $amount === null ? $remaining : min($remaining, max(0, $amount));

        if ($amount <= 0 || !$lead->user_id) {
            return;
        }

        app(LeadBalanceLedger::class)->adjust(
            $lead,
            $amount,
            'release',
            $token ?: 'release:'.hash('sha256', $lead->id.'|'.microtime(true)),
            $admin,
            'coins',
            $amount,
            $note
        );
    }

    protected function attributeOfferClick(Lead $lead): void
    {
        $query = OfferwallClick::query()
            ->where('user_id', $lead->user_id)
            ->whereNull('completed_at')
            ->where('created_at', '>=', now()->subDays(30));

        $query->where(function ($match) use ($lead) {
            $match->whereRaw('LOWER(provider_name) = ?', [strtolower((string) $lead->company)]);
            if ($lead->offer_id) {
                $match->orWhereHas('providerOffer', fn ($offer) => $offer->where('provider_offer_id', $lead->offer_id))
                    ->orWhereHas('campaign', fn ($campaign) => $campaign->whereKey($lead->offer_id));
            }
        });

        $click = $query->latest()->first();
        if ($click) {
            $click->forceFill(['lead_id' => $lead->id, 'completed_at' => now()])->save();
        }
    }
}
