<?php

namespace App\Models;

use App\Models\Admin;
use App\Models\Bank;
use App\Models\MessageSetting;
use App\Models\Organization;
use App\Models\Post;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class Mailing extends Model
{
    use HasFactory, SoftDeletes;

    protected $fillable = [
        'title', 'type', 'subject', 'content_blocks', 'scheduled_at', 'sent_at',
        'status', 'recipients_count', 'sent_count', 'failed_count',
        'updated_by_user_id', 'filter_by_location', 'location_country_id',
        'location_division_id', 'location_city_id', 'location_district_id',
        'filter_by_profile_type', 'selected_profile_types'
    ];

    protected $casts = [
        'content_blocks' => 'array',
        'subject' => 'array',
        'scheduled_at' => 'datetime',
        'sent_at' => 'datetime',
        'filter_by_location' => 'boolean',
        'filter_by_profile_type' => 'boolean',
        'selected_profile_types' => 'array',
    ];

    protected $dates = ['scheduled_at', 'sent_at', 'deleted_at'];

    /**
     * Get the user who last updated the mailing
     */
    public function updatedByUser()
    {
        return $this->belongsTo(\App\Models\User::class, 'updated_by_user_id');
    }

    /**
     * Get the bounces for this mailing
     */
    public function bounces()
    {
        return $this->hasMany(\App\Models\MailingBounce::class, 'mailing_id');
    }

    /**
     * Get the bounced count dynamically from the mailing_bounces table
     */
    public function getBouncedCountAttribute()
    {
        return $this->bounces()->count();
    }

    /**
     * Get posts from content_blocks JSON
     */
    public function getSelectedPosts()
    {
        if (empty($this->content_blocks)) {
            return collect();
        }

        $postIds = collect($this->content_blocks)->pluck('post_id');

        return Post::whereIn('id', $postIds)->get()->sortBy(function ($post) {
            return array_search($post->id, collect($this->content_blocks)->pluck('post_id')->toArray());
        });
    }

    /**
     * Get posts with all their translations
     */
    public function getSelectedPostsWithTranslations()
    {
        return $this->getSelectedPosts()->load('translations');
    }

    /**
     * Get post translation for specific locale with fallback
     */
    public function getPostTranslationForLocale($postId, $locale = null)
    {
        $locale = $locale ?: App::getLocale();

        $post = Post::find($postId);
        if (!$post) {
            return null;
        }

        // Try preferred locale first
        $translation = $post->translations()->where('locale', $locale)->first();

        // Only fallback if enabled in configuration
        if (!$translation && timebank_config('bulk_mail.use_fallback_locale', true)) {
            $fallbackLocale = timebank_config('bulk_mail.fallback_locale', 'en');
            if ($locale !== $fallbackLocale) { // Avoid infinite loop
                $translation = $post->translations()->where('locale', $fallbackLocale)->first();
            }
        }

        return $translation;
    }

    /**
     * Get recipients query based on mailing type
     */
    public function getRecipientsQuery()
    {
        $queries = [];

        // Initialize all profile type queries
        $users = User::whereHas('messageSettings', function ($query) {
            $query->where($this->type, true);
        })->select('id', 'name', 'email', 'lang_preference');

        $organizations = Organization::whereHas('messageSettings', function ($query) {
            $query->where($this->type, true);
        })->select('id', 'name', 'email', 'lang_preference');

        $banks = Bank::whereHas('message_settings', function ($query) {
            $query->where($this->type, true);
        })->select('id', 'name', 'email', 'lang_preference');

        $admins = Admin::whereHas('message_settings', function ($query) {
            $query->where($this->type, true);
        })->select('id', 'name', 'email', 'lang_preference');

        // Apply profile type filtering if enabled
        if ($this->filter_by_profile_type && !empty($this->selected_profile_types)) {
            $selectedTypes = $this->selected_profile_types;

            // Only include selected profile types
            if (in_array('User', $selectedTypes)) {
                $queries[] = $users;
            }
            if (in_array('Organization', $selectedTypes)) {
                $queries[] = $organizations;
            }
            if (in_array('Bank', $selectedTypes)) {
                $queries[] = $banks;
            }
            if (in_array('Admin', $selectedTypes)) {
                $queries[] = $admins;
            }
        } else {
            // If no profile type filtering, include all types
            $queries = [$users, $organizations, $banks, $admins];
        }

        // Apply location filtering if enabled
        if ($this->filter_by_location) {
            $queries = array_map(function($query) {
                return $this->applyLocationFilter($query);
            }, $queries);
        }

        // Get count before bounce filtering for logging
        $beforeFilteringCount = 0;
        foreach ($queries as $query) {
            $beforeFilteringCount += $query->count();
        }

        // Apply bounce filtering to all queries
        $queries = array_map(function($query) {
            return $query->whereNotIn('email', function($subquery) {
                $subquery->select('email')
                    ->from('mailing_bounces')
                    ->where('is_suppressed', true);
            });
        }, $queries);

        // Get count after bounce filtering for logging
        $afterFilteringCount = 0;
        foreach ($queries as $query) {
            $afterFilteringCount += $query->count();
        }

        // Log suppression statistics if any emails were excluded
        $suppressedCount = $beforeFilteringCount - $afterFilteringCount;
        if ($suppressedCount > 0) {
            Log::warning("MAILING RECIPIENTS: Excluded {$suppressedCount} suppressed emails from Mailing ID: {$this->id}");

            // Log which specific emails were suppressed
            $suppressedEmails = MailingBounce::where('is_suppressed', true)->pluck('email');
            foreach ($suppressedEmails as $email) {
                Log::warning("MAILING RECIPIENTS: Suppressed email excluded: {$email}");
            }
        }

        // Combine all queries with union
        if (empty($queries)) {
            // Return empty query if no profile types selected
            return User::whereRaw('1 = 0')->select('id', 'name', 'email', 'lang_preference');
        }

        $finalQuery = array_shift($queries);
        foreach ($queries as $query) {
            $finalQuery = $finalQuery->union($query);
        }

        return $finalQuery;
    }

    /**
     * Apply location filtering to a query
     */
    private function applyLocationFilter($query)
    {
        return $query->whereHas('locations', function ($locationQuery) {
            if ($this->location_district_id) {
                $locationQuery->where('district_id', $this->location_district_id);
            } elseif ($this->location_city_id) {
                $locationQuery->where('city_id', $this->location_city_id);
            } elseif ($this->location_division_id) {
                $locationQuery->where('division_id', $this->location_division_id);
            } elseif ($this->location_country_id) {
                $locationQuery->where('country_id', $this->location_country_id);
            }
        });
    }

    /**
     * Get recipients grouped by language preference for efficient processing
     */
    public function getRecipientsByLanguage()
    {
        $recipients = $this->getRecipientsQuery()->get();

        return $recipients->groupBy(function ($recipient) {
            return $recipient->lang_preference ?: timebank_config('base_language', 'en');
        });
    }

    /**
     * Status checking methods
     */
    public function canBeSent()
    {
        $hasValidStatus = in_array($this->status, ['draft', 'scheduled']);
        $hasPosts = !empty($this->content_blocks) && count(array_filter($this->content_blocks, function($block) {
            return !empty($block['post_id']);
        })) > 0;

        return $hasValidStatus && $hasPosts;
    }

    public function canBeScheduled()
    {
        return in_array($this->status, ['draft']);
    }

    public function canBeCancelled()
    {
        return in_array($this->status, ['scheduled', 'sending']);
    }

    /**
     * Scopes
     */
    public function scopeDraft($query)
    {
        return $query->where('status', 'draft');
    }

    public function scopeScheduled($query)
    {
        return $query->where('status', 'scheduled');
    }

    public function scopeSent($query)
    {
        return $query->where('status', 'sent');
    }

    public function scopeByType($query, $type)
    {
        return $query->where('type', $type);
    }

    /**
     * Get available locales for recipients of this mailing type
     */
    public function getAvailableRecipientLocales()
    {
        $recipientsQuery = $this->getRecipientsQuery();

        return $recipientsQuery
            ->whereNotNull('lang_preference')
            ->distinct()
            ->pluck('lang_preference')
            ->filter()
            ->toArray();
    }

    /**
     * Get available locales from selected posts
     */
    public function getAvailablePostLocales()
    {
        if (empty($this->content_blocks)) {
            return [];
        }

        $postIds = collect($this->content_blocks)->pluck('post_id');
        $locales = \DB::table('post_translations')
            ->whereIn('post_id', $postIds)
            ->where('status', 1) // Only published translations
            ->distinct()
            ->pluck('locale')
            ->toArray();

        // Always include base language first
        $baseLanguage = timebank_config('base_language', 'en');
        $locales = array_unique(array_merge([$baseLanguage], $locales));

        return array_values($locales);
    }

    /**
     * Get subject for a specific locale
     */
    public function getSubjectForLocale($locale = null)
    {
        $locale = $locale ?: timebank_config('base_language', 'en');
        $baseLanguage = timebank_config('base_language', 'en');

        // Handle legacy string subjects
        if (is_string($this->subject)) {
            return $this->subject;
        }

        // Handle multilingual JSON subjects
        $subjects = $this->subject ?? [];

        // Try preferred locale first
        if (isset($subjects[$locale])) {
            return $subjects[$locale];
        }

        // Fallback to base language
        if (isset($subjects[$baseLanguage])) {
            return $subjects[$baseLanguage];
        }

        // Fallback to first available subject
        return !empty($subjects) ? array_values($subjects)[0] : '';
    }

    /**
     * Set subject for a specific locale
     */
    public function setSubjectForLocale($locale, $subject)
    {
        $subjects = $this->subject ?? [];
        $subjects[$locale] = $subject;
        $this->subject = $subjects;
    }

    /**
     * Get all subjects with their locales
     */
    public function getAllSubjects()
    {
        return $this->subject ?? [];
    }

    /**
     * Check if subject exists for a locale
     */
    public function hasSubjectForLocale($locale)
    {
        $subjects = $this->subject ?? [];
        return isset($subjects[$locale]) && !empty($subjects[$locale]);
    }

    /**
     * Get recipients for a specific locale
     */
    public function getRecipientsForLocale($locale)
    {
        return $this->getRecipientsQuery()
            ->where('lang_preference', $locale);
    }

    /**
     * Get recipients grouped by available locales with fallback handling
     */
    public function getRecipientsGroupedByAvailableLocales()
    {
        $availableLocales = $this->getAvailablePostLocales();
        $allRecipients = $this->getRecipientsQuery()->get();
        $recipientsByLocale = [];

        foreach ($allRecipients as $recipient) {
            $preferredLocale = $recipient->lang_preference ?? timebank_config('base_language', 'en');

            // Check if preferred locale has content
            if (in_array($preferredLocale, $availableLocales)) {
                $recipientsByLocale[$preferredLocale][] = $recipient;
            } else {
                // Handle fallback logic
                if (timebank_config('bulk_mail.use_fallback_locale', true)) {
                    $fallbackLocale = timebank_config('bulk_mail.fallback_locale', 'en');
                    if (in_array($fallbackLocale, $availableLocales)) {
                        $recipientsByLocale[$fallbackLocale][] = $recipient;
                    }
                }
                // If fallback is disabled or not available, recipient is skipped
            }
        }

        return $recipientsByLocale;
    }

    /**
     * Filter content blocks to only include posts with published translations in the given locale
     */
    public function getContentBlocksForLocale($locale)
    {
        if (empty($this->content_blocks)) {
            return [];
        }

        $now = now();
        $filteredBlocks = [];

        foreach ($this->content_blocks as $block) {
            $post = Post::with('translations')->find($block['post_id']);

            if (!$post) {
                continue; // Skip if post doesn't exist
            }

            // Check if post has a published translation in the requested locale
            $hasPublishedTranslation = $post->translations->contains(function ($translation) use ($locale, $now) {
                return $translation->locale === $locale
                    && $translation->status == 1
                    && $translation->from <= $now
                    && ($translation->till === null || $translation->till >= $now);
            });

            if ($hasPublishedTranslation) {
                $filteredBlocks[] = $block;
            }
        }

        return $filteredBlocks;
    }

    /**
     * Get recipient count grouped by locale with content availability
     */
    public function getRecipientCountsByLocale()
    {
        // Get all recipients using the same logic as getRecipientsQuery()
        $recipients = $this->getRecipientsQuery()->get();

        // Get base language
        $baseLanguage = timebank_config('base_language', 'en');

        // Group recipients by language preference (with NULL fallback to base language)
        $recipientsByLocale = $recipients->groupBy(function ($recipient) use ($baseLanguage) {
            return $recipient->lang_preference ?: $baseLanguage;
        });

        // Add content block information for each locale
        $result = [];
        foreach ($recipientsByLocale as $locale => $localeRecipients) {
            $contentBlocks = $this->getContentBlocksForLocale($locale);
            $result[$locale] = [
                'count' => $localeRecipients->count(),
                'content_blocks' => count($contentBlocks),
                'has_content' => !empty($contentBlocks)
            ];
        }

        return $result;
    }

    /**
     * Get formatted recipient counts display
     */
    public function getFormattedRecipientCounts()
    {
        $counts = $this->getRecipientCountsByLocale();
        $formatted = [];

        foreach ($counts as $locale => $data) {
            $language = \App\Models\Language::where('lang_code', $locale)->first();
            $flag = $language ? $language->flag : '🏳️';

            if ($data['has_content']) {
                $formatted[] = "{$flag} {$data['count']}";
            } else {
                $formatted[] = "{$flag} {$data['count']} (no content)";
            }
        }

        return implode(' ', $formatted);
    }

    /**
     * Get total recipients that will actually receive content
     */
    public function getEffectiveRecipientsCount()
    {
        $counts = $this->getRecipientCountsByLocale();

        return collect($counts)
            ->where('has_content', true)
            ->sum('count');
    }

    /**
     * Dispatch locale-specific email jobs for this mailing
     */
    public function dispatchLocaleSpecificJobs()
    {
        Log::info("MAILING: Starting dispatch process for Mailing ID: {$this->id}");

        // Get batch size from configuration
        $batchSize = timebank_config('mailing.batch_size', 10);

        // Get recipients grouped by available locales with fallback logic
        $recipientsByLocale = $this->getRecipientsGroupedByAvailableLocales();

        $totalJobsDispatched = 0;

        foreach ($recipientsByLocale as $locale => $recipients) {
            $contentBlocks = $this->getContentBlocksForLocale($locale);

            if (!empty($contentBlocks) && !empty($recipients)) {
                // Split recipients into batches
                $recipientBatches = collect($recipients)->chunk($batchSize);
                $batchCount = $recipientBatches->count();

                Log::info("MAILING: Dispatching {$batchCount} batch job(s) for Mailing ID: {$this->id}, Locale: {$locale}, Total Recipients: " . count($recipients) . ", Batch Size: {$batchSize}");

                // Dispatch a separate job for each batch
                foreach ($recipientBatches as $batchIndex => $batch) {
                    $batchNumber = $batchIndex + 1;

                    Log::info("MAILING: Dispatching batch {$batchNumber}/{$batchCount} for Mailing ID: {$this->id}, Locale: {$locale}, Recipients in batch: " . $batch->count());

                    // Dispatch the actual job with batch of recipients to dedicated mailing queue
                    \App\Jobs\SendBulkMailJob::dispatch($this, $locale, [], $batch)
                        ->onQueue('mailing');

                    $totalJobsDispatched++;
                }
            }
        }

        // Calculate actual recipients who will receive emails
        $totalRecipients = $this->getRecipientsQuery()->count();
        $processedRecipients = collect($recipientsByLocale)->sum(function($recipients) {
            return count($recipients);
        });

        // Update recipients_count to reflect actual sendable recipients
        $this->update(['recipients_count' => $processedRecipients]);

        // Get suppressed email count for logging
        $suppressedCount = MailingBounce::where('is_suppressed', true)->count();

        Log::info("MAILING: Completed dispatch for Mailing ID: {$this->id} - Jobs dispatched: {$totalJobsDispatched}, Locales: " . count($recipientsByLocale) . ", Recipients processed: {$processedRecipients}, Suppressed emails: {$suppressedCount}");

        // If no jobs were dispatched (no recipients or no content), mark as sent immediately
        if (empty($recipientsByLocale) || $processedRecipients === 0) {
            Log::warning("MAILING: No jobs dispatched for Mailing ID: {$this->id} - marking as sent immediately");
            $this->update([
                'status' => 'sent',
                'sent_at' => now()
            ]);
        }
    }
}