<?php

namespace App\Http\Middleware;

use App\Models\Affiliate;
use App\Models\AffiliateClick;
use App\Models\AffiliateLink;
use App\Models\AffiliateReferral;
use App\Models\Client;
use App\Models\Setting;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;

class TrackAffiliateReferral
{
    /**
     * Handle an incoming request.
     *
     * Detects the `ref` query parameter, looks up the affiliate,
     * sets a tracking cookie, logs clicks, auto-applies coupons,
     * and sets client attribution.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        // Only process if affiliate system is enabled
        if (!Setting::get('enable_affiliate', false)) {
            return $next($request);
        }

        $refCode = $request->query('ref');

        if ($refCode) {
            $this->processReferral($request, $refCode);
        }

        // If authenticated and has affiliate cookie, set client attribution
        $this->setClientAttribution($request);

        return $next($request);
    }

    /**
     * Process a referral from the ref query parameter.
     */
    private function processReferral(Request $request, string $refCode): void
    {
        // Look up affiliate by referral code
        $affiliate = Affiliate::where('referral_code', $refCode)
            ->where('status', 'active')
            ->first();

        if (!$affiliate) {
            return;
        }

        // Determine link ID (optional lid parameter)
        $linkId = $request->query('lid');
        $affiliateLink = null;

        if ($linkId) {
            $affiliateLink = AffiliateLink::where('id', $linkId)
                ->where('affiliate_id', $affiliate->id)
                ->where('is_active', true)
                ->first();
        }

        // If no specific link found, try to find a matching link by target URL
        if (!$affiliateLink) {
            $affiliateLink = AffiliateLink::where('affiliate_id', $affiliate->id)
                ->where('is_active', true)
                ->orderBy('id')
                ->first();
        }

        // Set cookie with affiliate tracking data
        $cookieDuration = (int) Setting::get('affiliate_cookie_duration', 90);

        $cookieData = json_encode([
            'affiliate_id' => $affiliate->id,
            'link_id' => $affiliateLink?->id,
        ]);

        cookie()->queue(
            cookie(
                'affiliate_ref',
                $cookieData,
                $cookieDuration * 24 * 60, // Convert days to minutes
                '/',
                null,
                $request->isSecure(),
                true, // httpOnly
                false,
                'Lax'
            )
        );

        // Log click to affiliate_clicks
        $this->logClick($request, $affiliateLink, $affiliate);

        // PRC-622 — 'AffiliateClickthru' action hook'u (hook-only).
        app(\App\Core\Hook\HookBridge::class)->fireAction('AffiliateClickthru', [
            'affiliate_id' => $affiliate->id,
            'client_id' => $affiliate->client_id,
            'link_id' => $affiliateLink?->id,
            'code' => $refCode,
            'ref_code' => $refCode,
        ]);

        // If the link has a coupon, store it in session for auto-apply
        if ($affiliateLink && $affiliateLink->coupon_id) {
            session(['affiliate_coupon_id' => $affiliateLink->coupon_id]);
        }

        Log::info('TrackAffiliateReferral: Referral tracked', [
            'affiliate_id' => $affiliate->id,
            'link_id' => $affiliateLink?->id,
            'ref_code' => $refCode,
            'ip' => $request->ip(),
        ]);
    }

    /**
     * Log a click to the affiliate_clicks table and update link counters.
     */
    private function logClick(Request $request, ?AffiliateLink $link, Affiliate $affiliate): void
    {
        if (!$link) {
            return;
        }

        $ipAddress = $request->ip();

        // Check if this is a unique click (same IP + link not seen recently)
        $isUnique = !DB::table('affiliate_clicks')
            ->where('affiliate_link_id', $link->id)
            ->where('ip_address', $ipAddress)
            ->where('created_at', '>=', now()->subHours(24))
            ->exists();

        // Create click record
        AffiliateClick::create([
            'affiliate_link_id' => $link->id,
            'ip_address' => $ipAddress,
            'user_agent' => mb_substr($request->userAgent() ?? '', 0, 500),
            'referrer_url' => mb_substr($request->header('referer', '') ?? '', 0, 500),
            'country' => $this->detectCountry($request),
            'is_unique' => $isUnique,
        ]);

        // Increment link click counters
        $link->increment('clicks');

        if ($isUnique) {
            $link->increment('unique_clicks');
        }
    }

    /**
     * Set the affiliate_id on the client record if authenticated
     * and not already attributed.
     */
    private function setClientAttribution(Request $request): void
    {
        if (!Auth::guard('client')->check()) {
            return;
        }

        $client = Auth::guard('client')->user();

        // Never overwrite existing attribution (first-touch permanent)
        if ($client->affiliate_id) {
            return;
        }

        // Check for affiliate cookie
        $cookieData = $this->getAffiliateCookieData($request);

        if (!$cookieData || !isset($cookieData['affiliate_id'])) {
            return;
        }

        $affiliateId = (int) $cookieData['affiliate_id'];

        // Verify the affiliate exists and is active
        $affiliate = Affiliate::where('id', $affiliateId)
            ->where('status', 'active')
            ->first();

        if (!$affiliate) {
            return;
        }

        // Prevent self-attribution
        if ($affiliate->client_id === $client->id) {
            return;
        }

        // Set permanent attribution
        $client->update(['affiliate_id' => $affiliateId]);

        // PRC-1094 — Persist the referral row at signup/attribution time so
        // affiliate_referrals is populated (it was never written anywhere →
        // always empty). Commission creation later reuses this same row via
        // firstOrCreate, so referral_id (NOT NULL FK) always resolves.
        AffiliateReferral::firstOrCreate(
            [
                'affiliate_id' => $affiliateId,
                'referred_client_id' => $client->id,
            ],
            [
                'ip_address' => $request->ip(),
                'status' => 'signup',
            ]
        );

        Log::info('TrackAffiliateReferral: Client attributed to affiliate', [
            'client_id' => $client->id,
            'affiliate_id' => $affiliateId,
        ]);
    }

    /**
     * Parse the affiliate tracking cookie data.
     */
    private function getAffiliateCookieData(Request $request): ?array
    {
        $cookieValue = $request->cookie('affiliate_ref');

        if (!$cookieValue) {
            return null;
        }

        $data = json_decode($cookieValue, true);

        if (!is_array($data)) {
            return null;
        }

        return $data;
    }

    /**
     * Detect the visitor's country from GeoIP (if available).
     *
     * Returns the 2-letter country code or null.
     */
    private function detectCountry(Request $request): ?string
    {
        try {
            // Try to use the GeoIP service if available
            $geoIpHeader = $request->header('CF-IPCountry');

            if ($geoIpHeader && strlen($geoIpHeader) === 2) {
                return strtoupper($geoIpHeader);
            }

            // Fallback: check for X-Country header (set by reverse proxy)
            $xCountry = $request->header('X-Country');

            if ($xCountry && strlen($xCountry) === 2) {
                return strtoupper($xCountry);
            }

            return null;
        } catch (\Exception $e) {
            return null;
        }
    }
}
