<?php

namespace App\Services;

use App\Models\Contact;
use App\Services\AdvancedFilter\FilterQueryBuilder;
use App\Services\AdvancedFilter\InvalidFilterException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;

class CampaignRecipientResolver
{
    /**
     * Resolve a `recipients_config` blob into a flat, de-duplicated list of
     * contact IDs, scoped to the given tenant. $userId is required (not read
     * from auth()) because this runs from queued jobs and console commands
     * with no authenticated session — the tenant must come from the
     * campaign/template itself.
     *
     * Two mutually-exclusive shapes, discriminated by `mode` (missing mode
     * is treated as the original 'list_tag' shape, for rows created before
     * this discriminator existed):
     *   {mode: 'list_tag', included: [{list_id?, tag_id?}], excluded: [...]}
     *   {mode: 'advanced_filter', filter: {groups: [{conditions: [...]}]}}
     *
     * @return array<int>
     */
    public static function resolve(?array $recipientsConfig, int $userId): array
    {
        $config = $recipientsConfig ?? [];
        $mode   = $config['mode'] ?? 'list_tag';

        if ($mode === 'advanced_filter') {
            return self::resolveAdvancedFilter($config['filter'] ?? null, $userId);
        }

        return self::resolveListTag($config, $userId);
    }

    /**
     * Same resolution, but returns just the count  used for the live
     * "N contacts found" preview while a user edits recipient rows.
     */
    public static function count(?array $recipientsConfig, int $userId): int
    {
        return count(self::resolve($recipientsConfig, $userId));
    }

    private static function resolveAdvancedFilter(?array $filter, int $userId): array
    {
        if (empty($filter)) {
            return [];
        }

        try {
            return (new FilterQueryBuilder($userId))->build($filter)->pluck('id')->all();
        } catch (InvalidFilterException $e) {
            // Fail closed - never fall back to "all contacts" just because a
            // filter tree was malformed or referenced something out of scope.
            Log::warning("Rejected invalid advanced filter for user {$userId}: {$e->getMessage()}");

            return [];
        }
    }

    private static function resolveListTag(array $config, int $userId): array
    {
        $included = $config['included'] ?? [];
        $excluded = $config['excluded'] ?? [];

        $includedIds = self::resolveIncluded($included, $userId);
        $excludedIds = self::resolveExcluded($excluded, $userId);

        return $includedIds->diff($excludedIds)->values()->all();
    }

    private static function resolveIncluded(array $included, int $userId): Collection
    {
        $includedIds = collect();

        if (empty($included)) {
            return Contact::where('user_id', $userId)->where('status', 'subscribed')->pluck('id')->unique();
        }

        foreach ($included as $row) {
            $listId = $row['list_id'] ?? null;
            $tagId  = $row['tag_id']  ?? null;

            $query = Contact::where('user_id', $userId)->where('status', 'subscribed');

            if (!$listId && !$tagId) {
                $includedIds = $includedIds->merge($query->pluck('id'));
                continue;
            }

            if ($listId) {
                $query->whereHas('lists', fn ($q) => $q->where('lists.id', $listId));
            }
            if ($tagId) {
                $query->whereHas('tags', fn ($q) => $q->where('tags.id', $tagId));
            }
            $includedIds = $includedIds->merge($query->pluck('id'));
        }

        return $includedIds->unique();
    }

    private static function resolveExcluded(array $excluded, int $userId): Collection
    {
        $excludedIds = collect();

        foreach ($excluded as $row) {
            $listId = $row['list_id'] ?? null;
            $tagId  = $row['tag_id']  ?? null;

            if (!$listId) {
                continue;
            }

            $query = Contact::where('user_id', $userId)->whereHas('lists', fn ($q) => $q->where('lists.id', $listId));
            if ($tagId) {
                $query->whereHas('tags', fn ($q) => $q->where('tags.id', $tagId));
            }
            $excludedIds = $excludedIds->merge($query->pluck('id'));
        }

        return $excludedIds->unique();
    }
}
