<?php

namespace App\Mail;

use App\Facades\Settings;
use Illuminate\Mail\Events\MessageSending;
use Illuminate\Support\Facades\Log;
use Mtc\MercuryDataModels\User;
use Symfony\Component\Mime\Address;

class EmailWhitelistFilter
{
    private string $catchAllEmail;

    public function __construct()
    {
        $this->catchAllEmail = config('mail.staging_log_email');
    }

    /**
     * Handle the MessageSending event.
     */
    public function handle(MessageSending $event): void
    {
        if (!$this->shouldFilter()) {
            return;
        }

        $message = $event->message;
        $originalTo = $message->getTo();
        $originalCc = $message->getCc();
        $originalBcc = $message->getBcc();

        $filteredTo = $this->filterRecipients($originalTo);
        $filteredCc = $this->filterRecipients($originalCc);
        $filteredBcc = $this->filterRecipients($originalBcc);

        // Replace recipients with filtered list
        $message->to(...$filteredTo);
        $message->cc(...$filteredCc);
        $message->bcc(...$filteredBcc);

        // Log filtered recipients for debugging
        $this->logFilteredRecipients($originalTo, $filteredTo, 'to');
        $this->logFilteredRecipients($originalCc, $filteredCc, 'cc');
        $this->logFilteredRecipients($originalBcc, $filteredBcc, 'bcc');
    }

    /**
     * Partition a list of email addresses into ones the whitelist filter
     * would deliver to the original recipient ("sent") and ones it would
     * redirect to the catch-all ("skipped").
     *
     * When filtering is not active (live site, local env, etc.), all
     * supplied emails are reported as sent.
     *
     * @param  string[]  $emails
     * @return array{sent: string[], skipped: string[]}
     */
    public function partitionRecipients(array $emails): array
    {
        $emails = array_values(array_filter(array_map(
            fn ($email) => trim((string) $email),
            $emails
        )));

        if (!$this->shouldFilter()) {
            return ['sent' => $emails, 'skipped' => []];
        }

        $allowedEmails = array_merge(
            $this->getWhitelist(),
            $this->getSiteUserEmails(),
            $this->getSuperAdminEmails()
        );

        $sent = [];
        $skipped = [];

        foreach ($emails as $email) {
            if (in_array(strtolower($email), $allowedEmails, true)) {
                $sent[] = $email;
            } else {
                $skipped[] = $email;
            }
        }

        return ['sent' => $sent, 'skipped' => $skipped];
    }

    /**
     * Determine if email filtering should be applied.
     */
    private function shouldFilter(): bool
    {
        // Never filter in local environment
        if (app()->environment('local')) {
            return false;
        }

        // In production, only filter if site is NOT live and whitelist is enabled
        if (app()->environment('production')) {
            return !$this->isSiteLive() && $this->isWhitelistEnabled();
        }

        // In staging/other environments, always filter (unless whitelist is disabled)
        return $this->isWhitelistEnabled();
    }

    /**
     * Check if the current site is marked as live.
     */
    private function isSiteLive(): bool
    {
        // No tenant - means system, do not apply filter
        if (!tenant('id')) {
            return true;
        }

        return !empty(tenant()->live_at);
    }

    /**
     * Check if whitelist filtering is enabled for this site.
     */
    private function isWhitelistEnabled(): bool
    {
        if (!tenant('id') || app()->runningUnitTests()) {
            return false;
        }

        return (bool) Settings::get('mail-whitelist-enabled', true);
    }

    /**
     * Filter recipients based on whitelist rules.
     *
     * @param  Address[]  $recipients
     * @return Address[]
     */
    private function filterRecipients(array $recipients): array
    {
        if (empty($recipients)) {
            return [];
        }

        $allowedEmails = array_merge(
            $this->getWhitelist(),
            $this->getSiteUserEmails(),
            $this->getSuperAdminEmails()
        );

        $filtered = [];
        $redirected = [];

        foreach ($recipients as $recipient) {
            $email = strtolower($recipient->getAddress());

            if (in_array($email, $allowedEmails, true)) {
                $filtered[] = $recipient;
            } else {
                $redirected[] = $email;
            }
        }

        // If all recipients were filtered out, send to catch-all
        if (empty($filtered) && !empty($redirected)) {
            $filtered[] = new Address($this->catchAllEmail, 'Testing Catch-All');
        }

        return $filtered;
    }


    /**
     * Get the whitelist from settings.
     */
    private function getWhitelist(): array
    {
        if (!tenant('id')) {
            return [];
        }

        $whitelist = Settings::get('mail-whitelist-emails', '');

        if (empty($whitelist)) {
            return [];
        }

        return array_map(
            fn ($email) => strtolower(trim($email)),
            explode(',', $whitelist)
        );
    }

    /**
     * Get email addresses of users assigned to the current site.
     */
    private function getSiteUserEmails(): array
    {
        if (!tenant('id')) {
            return [];
        }

        return tenant()->users()
            ->pluck('email')
            ->map(fn ($email) => strtolower($email))
            ->toArray();
    }

    /**
     * Get email addresses of users with MTC role.
     */
    private function getSuperAdminEmails(): array
    {
        return User::query()
            ->whereHas('roles', fn ($query) => $query->where('name', 'mtc'))
            ->pluck('email')
            ->map(fn ($email) => strtolower($email))
            ->toArray();
    }

    /**
     * Log filtered recipients for debugging.
     */
    private function logFilteredRecipients(array $original, array $filtered, string $type): void
    {
        $originalEmails = array_map(fn ($email) => $email->getAddress(), $original);
        $filteredEmails = array_map(fn ($email) => $email->getAddress(), $filtered);
        $blocked = array_diff($originalEmails, $filteredEmails);

        if (!empty($blocked)) {
            Log::info('Email whitelist filter', [
                'tenant' => tenant('id'),
                'type' => $type,
                'original' => $originalEmails,
                'allowed' => $filteredEmails,
                'blocked' => array_values($blocked),
            ]);
        }
    }
}
