<?php

namespace App\Support;

use App\Models\Offerwall;
use App\Models\ProviderOffer;
use App\Models\Campaign;
use Illuminate\Support\Collection;
use Throwable;

class DirectOfferFeed
{
    public static function activeProviderOffers(): Collection
    {
        self::ensureCurated();
        $liveOfferwallIds = Offerwall::where('is_active', true)
            ->get()
            ->filter(fn (Offerwall $offerwall) => $offerwall->hasLiveApiFeed())
            ->pluck('id');

        if ($liveOfferwallIds->isEmpty()) {
            return collect();
        }

        return ProviderOffer::with(['offerwall'])
            ->withCount('clicks')
            ->where('is_active', true)
            ->whereIn('offerwall_id', $liveOfferwallIds)
            ->orderByDesc('is_hot')
            ->orderByDesc('is_featured')
            ->orderByDesc('reward_points')
            ->orderBy('sort_order')
            ->get();
    }

    public static function ensureCurated(): void
    {
        try {
            if (!site_bool('curation_auto_enabled', true)) {
                return;
            }

            $hasOffers = ProviderOffer::where('is_active', true)->exists();
            $hasPlacement = ProviderOffer::where('is_active', true)
                ->where(fn ($query) => $query->where('is_hot', true)->orWhere('is_featured', true))
                ->exists();

            if ($hasOffers && !$hasPlacement) {
                app(OfferCurator::class)->curate();
            }
        } catch (Throwable) {
            // Offer browsing should never fail because optional curation cannot run.
        }
    }

    public static function from(Collection $offerwalls, ?Collection $providerOffers = null): Collection
    {
        $providerOffers ??= self::activeProviderOffers();
        $offers = collect();
        $country = self::visitorCountry();
        $device = OfferAudience::deviceForRequest();

        foreach ($providerOffers as $providerOffer) {
            if (!$providerOffer->offerwall) {
                continue;
            }

            if (!OfferAudience::allows($providerOffer, $country, $device)) {
                continue;
            }

            $offers->push(self::providerOffer($providerOffer));
        }

        foreach (self::activeCampaigns() as $campaign) {
            if (!self::campaignAllows($campaign, $country, $device)) {
                continue;
            }

            $offers->push(self::campaignOffer($campaign));
        }

        return $offers->unique('id')->values();
    }

    private static function activeCampaigns(): Collection
    {
        return Campaign::with('offerwall')
            ->withCount('clicks')
            ->where('status', 'active')
            ->where(fn ($query) => $query->whereNull('starts_at')->orWhere('starts_at', '<=', now()))
            ->where(fn ($query) => $query->whereNull('ends_at')->orWhere('ends_at', '>=', now()))
            ->where(function ($query) {
                $query->whereNotNull('direct_url')
                    ->orWhereHas('offerwall', fn ($offerwall) => $offerwall->where('is_active', true));
            })
            ->get();
    }

    private static function campaignAllows(Campaign $campaign, ?string $country, string $device): bool
    {
        $countries = collect(preg_split('/[,|]/', (string) $campaign->countries))
            ->map(fn ($value) => strtoupper(trim((string) $value)))
            ->reject(fn ($value) => $value === '' || in_array($value, ['ALL', 'WORLDWIDE', 'GLOBAL', '*'], true))
            ->all();

        if ($country && $countries && !in_array(strtoupper($country), $countries, true)) {
            return false;
        }

        $target = strtolower((string) ($campaign->device ?: 'all'));

        return $target === 'all'
            || $target === $device
            || ($target === 'mobile' && in_array($device, ['android', 'ios'], true));
    }

    private static function campaignOffer(Campaign $campaign): array
    {
        $provider = optional($campaign->offerwall)->name ?: 'Basketloot Exclusive';
        $device = strtolower((string) ($campaign->device ?: 'all'));
        $platforms = match ($device) {
            'android' => ['android'],
            'ios' => ['ios'],
            'desktop' => ['desktop'],
            'mobile' => ['android', 'ios'],
            default => ['android', 'ios', 'desktop'],
        };
        $reward = (int) max(1, round((float) $campaign->reward_points));

        return [
            'id' => 'campaign-'.$campaign->id,
            'name' => $campaign->name,
            'description' => (string) $campaign->description,
            'requirements' => (string) $campaign->description,
            'reward' => $reward,
            'image_path' => $campaign->icon_path ?: optional($campaign->offerwall)->photo_path,
            'icon_url' => null,
            'provider' => $provider,
            'offerwall' => $campaign->offerwall,
            'provider_offer' => null,
            'campaign' => $campaign,
            'category' => $campaign->category ?: 'other',
            'featured' => (bool) $campaign->featured,
            'device' => $device,
            'platforms' => $platforms,
            'countries' => collect(preg_split('/[,|]/', (string) $campaign->countries))->filter()->values()->all(),
            'background_color' => optional($campaign->offerwall)->background_color ?: '#11192a',
            'badge' => $campaign->is_hot ? 'Hot' : ($campaign->featured ? 'Featured' : 'Exclusive'),
            'badge_color' => $campaign->is_hot ? '#14e888' : '#48bdf4',
            'popularity' => $reward + ((int) ($campaign->clicks_count ?? 0) * 120) + ($campaign->is_hot ? 5000 : 0) + ($campaign->featured ? 1500 : 0),
            'is_direct' => true,
            'steps' => [$campaign->description ?: 'Complete the offer requirements.'],
            'rewards' => [['coins' => $reward, 'label' => $campaign->description ?: 'Complete the offer.']],
        ];
    }

    public static function hot(Collection $offers, int $limit = 10): Collection
    {
        return $offers
            ->sortByDesc(fn ($offer) => (float) $offer['reward'] + ($offer['featured'] ? 250 : 0))
            ->take(max($limit * 2, $limit))
            ->shuffle()
            ->take($limit)
            ->values();
    }

    public static function featured(Collection $offers, int $limit = 10): Collection
    {
        return $offers
            ->sortByDesc(fn ($offer) => ($offer['featured'] ? 100000 : 0) + (float) $offer['reward'])
            ->take(max($limit * 2, $limit))
            ->shuffle()
            ->take($limit)
            ->values();
    }

    public static function counts(Collection $offers, Collection $offerwalls): array
    {
        return [
            'all' => $offers->count(),
            'games' => $offers->where('category', 'games')->count(),
            'other' => $offers->where('category', 'other')->count(),
            'surveys' => $offers->where('category', 'surveys')->count(),
            'partners' => $offers->where('category', 'partners')->count() ?: $offerwalls->count(),
        ];
    }

    public static function filtered(Collection $offers, array $filters): Collection
    {
        $type = $filters['type'] ?? 'all';
        $search = mb_strtolower(trim((string) ($filters['search'] ?? '')));
        $provider = (string) ($filters['provider'] ?? '');
        $device = (string) ($filters['device'] ?? '');
        $sort = $filters['sort'] ?? 'popular';

        $offers = $offers
            ->when($type !== 'all', fn ($items) => $items->where('category', $type))
            ->when($provider !== '', fn ($items) => $items->where('provider', $provider))
            ->when($device !== '', function ($items) use ($device) {
                return $items->filter(fn ($offer) => in_array($device, $offer['platforms'], true));
            })
            ->when($search !== '', function ($items) use ($search) {
                return $items->filter(function ($offer) use ($search) {
                    return str_contains(mb_strtolower($offer['name']), $search)
                        || str_contains(mb_strtolower($offer['description']), $search)
                        || str_contains(mb_strtolower($offer['provider']), $search);
                });
            });

        return match ($sort) {
            'highest' => $offers->sortByDesc('reward')->values(),
            'lowest' => $offers->sortBy('reward')->values(),
            'newest' => $offers->sortByDesc('id')->values(),
            default => $offers->sortByDesc('popularity')->values(),
        };
    }

    private static function providerOffer(ProviderOffer $providerOffer): array
    {
        $offerwall = $providerOffer->offerwall;
        $devices = $providerOffer->normalizedDevices();
        $reward = (int) max(1, round((float) $providerOffer->reward_points));
        $description = trim((string) $providerOffer->description);
        $requirements = trim((string) $providerOffer->requirements);

        // Some feeds put a raw goal code (e.g. "74018-1") in requirements while
        // the real instruction sits in description. Prefer whichever is a real
        // sentence for the human-readable fallbacks below.
        if (self::isGarbageMilestoneLabel($requirements)) {
            $requirements = self::isGarbageMilestoneLabel($description) ? '' : $description;
        }

        $steps = collect($providerOffer->steps ?: [])
            ->map(fn ($step) => is_array($step) ? (string) ($step['label'] ?? $step['title'] ?? $step['name'] ?? '') : (string) $step)
            ->map(fn ($step) => trim($step))
            ->reject(fn ($step) => self::isGarbageMilestoneLabel($step))
            ->values()
            ->all();

        $rewards = collect($providerOffer->rewards ?: [])
            ->map(function ($rewardRow) use ($reward, $requirements, $description) {
                if (is_array($rewardRow)) {
                    $label = trim((string) ($rewardRow['label'] ?? $rewardRow['description'] ?? ''));

                    if (self::isGarbageMilestoneLabel($label)) {
                        $label = $requirements ?: $description;
                    }

                    return [
                        'coins' => (int) max(1, round((float) ($rewardRow['coins'] ?? $rewardRow['points'] ?? $reward))),
                        'label' => $label,
                    ];
                }

                $label = trim((string) $rewardRow);

                return [
                    'coins' => $reward,
                    'label' => self::isGarbageMilestoneLabel($label) ? ($requirements ?: $description) : $label,
                ];
            })
            ->filter(fn ($rewardRow) => trim((string) $rewardRow['label']) !== '')
            ->values()
            ->all();

        // If a feed only shipped raw goal codes (e.g. "74018-1") the cleaned
        // lists come back empty. Fall back to the human-readable requirement
        // text and a single reward row so the popup never shows garbage or a
        // "no data" placeholder.
        if (empty($steps) && ($requirements !== '' || $description !== '')) {
            $steps = [$requirements ?: $description];
        }

        if (empty($rewards)) {
            $rewards = [[
                'coins' => $reward,
                'label' => $requirements ?: $description ?: 'Complete the offer to earn your reward.',
            ]];
        }

        return [
            'id' => 'provider-offer-'.$providerOffer->id,
            'name' => $providerOffer->name,
            'description' => $description ?: $requirements ?: '',
            'requirements' => $requirements ?: $description,
            'reward' => $reward,
            'image_path' => $providerOffer->icon_path ?: $offerwall->photo_path,
            'icon_url' => $providerOffer->icon_url,
            'provider' => $offerwall->name,
            'offerwall' => $offerwall,
            'provider_offer' => $providerOffer,
            'campaign' => null,
            'category' => $providerOffer->category ?: self::categoryFor($offerwall, implode(',', $devices)),
            'featured' => (bool) $providerOffer->is_featured,
            'device' => implode(',', $devices),
            'platforms' => $devices ?: self::platformsFor('all', (bool) $offerwall->is_mobile),
            'countries' => self::countriesFor($providerOffer),
            'background_color' => $offerwall->background_color ?: '#141820',
            'badge' => $providerOffer->is_hot ? 'Hot' : ($providerOffer->is_featured ? 'Featured' : null),
            'badge_color' => $providerOffer->is_hot ? '#14e888' : ($offerwall->badge_color ?: '#37c7ff'),
            'popularity' => $reward + ((int) ($providerOffer->clicks_count ?? 0) * 120) + ($providerOffer->is_hot ? 5000 : 0) + ($providerOffer->is_featured ? 1500 : 0),
            'is_direct' => true,
            'steps' => $steps,
            'rewards' => $rewards,
        ];
    }

    /**
     * A milestone label is "garbage" when it carries no meaning for a user:
     * an empty string, a raw goal/event id like "74018-1" or "GOAL_2", or the
     * importer's generic "Complete milestone." placeholder.
     */
    public static function isGarbageMilestoneLabel(?string $label): bool
    {
        $label = trim((string) $label);

        if ($label === '') {
            return true;
        }

        if (strcasecmp($label, 'Complete milestone.') === 0 || strcasecmp($label, 'Complete milestone') === 0) {
            return true;
        }

        // Pure id/code tokens: digits with optional -, _, or single letters and
        // no real words (no spaces, contains a digit, short). e.g. 74018-1, g12, 3-1
        if (!str_contains($label, ' ') && preg_match('/\d/', $label) && strlen($label) <= 12) {
            return true;
        }

        return false;
    }

    private static function categoryFor(Offerwall $offerwall, string $device): string
    {
        if ($offerwall->type === 'survey') {
            return 'surveys';
        }

        return str_contains($device, 'android') || str_contains($device, 'ios') || str_contains($device, 'mobile')
            ? 'games'
            : 'other';
    }

    /**
     * The visitor's country used to geo-filter direct/API offers. Returns null
     * (i.e. "show everything") when geo filtering is disabled or the country
     * can't be determined.
     */
    public static function visitorCountry(): ?string
    {
        if (!site_bool('offers_geo_filter_enabled', true)) {
            return null;
        }

        return \App\Support\GeoLocation::countryForRequest();
    }

    private static function countriesFor(ProviderOffer $providerOffer): array
    {
        return OfferAudience::countries($providerOffer);
    }

    private static function platformsFor(string $device, bool $mobile): array
    {
        $device = strtolower($device);

        if (str_contains($device, 'android')) {
            return ['android'];
        }

        if (str_contains($device, 'ios') || str_contains($device, 'iphone')) {
            return ['ios'];
        }

        if ($mobile || str_contains($device, 'mobile')) {
            return ['android', 'ios'];
        }

        if (str_contains($device, 'desktop')) {
            return ['desktop'];
        }

        return ['android', 'ios', 'desktop'];
    }
}
