<?php

namespace App\Services;

use App\Models\Campaign;
use App\Models\SmtpConfig;

class SmtpConfigService
{
    /**
     * Configure Laravel's mailer at runtime from a stored connection (or raw
     * array of the same shape) and return the mailer name to send with.
     *
     * $persistTo lets a raw-array call (e.g. the "test connection" form)
     * still persist a rotated OAuth refresh token back to its saved row -
     * otherwise only calls that pass a real SmtpConfig model do.
     */
    public static function configureRuntime(SmtpConfig|array $config, ?SmtpConfig $persistTo = null): string
    {
        $model = $config instanceof SmtpConfig ? $config : $persistTo;

        if ($config instanceof SmtpConfig) {
            $config = [
                'provider'     => $config->provider,
                'host'         => $config->host,
                'port'         => $config->port,
                'encryption'   => $config->encryption,
                'auto_tls'     => $config->auto_tls,
                'auth_enabled' => $config->auth_enabled,
                'username'     => $config->username,
                'password'     => $config->password,
                'from_email'   => $config->from_email,
                'from_name'    => $config->from_name,
                'settings'     => $config->settings,
                'oauth_token'  => $config->oauth_token,
            ];
        }

        $provider = $config['provider'] ?? 'smtp';

        config([
            'mail.from.address' => $config['from_email'] ?? config('mail.from.address'),
            'mail.from.name'    => $config['from_name']  ?? config('mail.from.name'),
        ]);

        return match ($provider) {
            'ses'       => self::configureSes($config),
            'mailgun'   => self::configureMailgun($config),
            'sendgrid'  => self::configureSendgrid($config),
            'brevo'     => self::configureBrevo($config),
            'sparkpost' => self::configureSparkpost($config),
            'netcore'   => self::configureNetcore($config),
            'postmark'  => self::configurePostmark($config),
            'elastic'   => self::configureElastic($config),
            'smtp2go'   => self::configureSmtp2Go($config),
            'tosend'    => self::configureToSend($config),
            'phpmail'   => self::configurePhpMail($config),
            'gmail'     => self::configureGoogle($config),
            'outlook'   => self::configureMicrosoft($config, $model),
            default     => self::configureSmtp($config),
        };
    }

    /** PHP's native mail() equivalent - no credentials, no settings. */
    private static function configurePhpMail(array $config): string
    {
        config([
            'mail.default'         => 'phpmail',
            'mail.mailers.phpmail' => ['transport' => 'phpmail'],
        ]);

        app('mail.manager')->purge('phpmail');

        return 'phpmail';
    }

    /** Gmail / Google Workspace via the Gmail API (OAuth2 refresh token). */
    private static function configureGoogle(array $config): string
    {
        config([
            'mail.default'       => 'gmail',
            'mail.mailers.gmail' => [
                'transport'     => 'gmail',
                'client_id'     => $config['username'] ?? '',
                'client_secret' => $config['password'] ?? '',
                'refresh_token' => $config['oauth_token']['refresh_token'] ?? null,
            ],
        ]);

        app('mail.manager')->purge('gmail');

        return 'gmail';
    }

    /**
     * Microsoft Graph API (Outlook / Office365) via an OAuth2 refresh token.
     * Microsoft rotates refresh tokens on every use, so $model (when given)
     * is updated in place as soon as the transport refreshes a new one.
     */
    private static function configureMicrosoft(array $config, ?SmtpConfig $model): string
    {
        config([
            'mail.default'         => 'outlook',
            'mail.mailers.outlook' => [
                'transport'          => 'outlook',
                'client_id'          => $config['username'] ?? '',
                'client_secret'      => $config['password'] ?? '',
                'refresh_token'      => $config['oauth_token']['refresh_token'] ?? null,
                'on_token_refreshed' => $model
                    ? function (array $tokens) use ($model) {
                        $model->update(['oauth_token' => array_merge($model->oauth_token ?? [], $tokens)]);
                    }
                    : null,
            ],
        ]);

        app('mail.manager')->purge('outlook');

        return 'outlook';
    }

    /** toSend via the v2 Email API (password = API Key). */
    private static function configureToSend(array $config): string
    {
        config([
            'mail.default'        => 'tosend',
            'mail.mailers.tosend' => [
                'transport' => 'tosend',
                'key'       => $config['password'],
            ],
        ]);

        app('mail.manager')->purge('tosend');

        return 'tosend';
    }

    /** SMTP2GO via the v3 Email API (password = API Key). */
    private static function configureSmtp2Go(array $config): string
    {
        config([
            'mail.default'         => 'smtp2go',
            'mail.mailers.smtp2go' => [
                'transport' => 'smtp2go',
                'key'       => $config['password'],
            ],
        ]);

        app('mail.manager')->purge('smtp2go');

        return 'smtp2go';
    }

    /** Elastic Email via the v2 Web API (password = API Key). */
    private static function configureElastic(array $config): string
    {
        config([
            'mail.default'         => 'elastic',
            'mail.mailers.elastic' => [
                'transport'        => 'elastic',
                'key'              => $config['password'],
                'is_transactional' => $config['settings']['is_transactional'] ?? true,
            ],
        ]);

        app('mail.manager')->purge('elastic');

        return 'elastic';
    }

    /** Postmark via the Email API (password = Server API Token). */
    private static function configurePostmark(array $config): string
    {
        config([
            'mail.default'          => 'postmark',
            'mail.mailers.postmark' => [
                'transport'      => 'postmark',
                'key'            => $config['password'],
                'track_opens'    => $config['settings']['track_opens'] ?? false,
                'track_links'    => $config['settings']['track_links'] ?? 'None',
                'message_stream' => $config['settings']['message_stream'] ?? 'outbound',
            ],
        ]);

        app('mail.manager')->purge('postmark');

        return 'postmark';
    }

    /** Netcore (Pepipost) via the v5 Email API (password = API Key). */
    private static function configureNetcore(array $config): string
    {
        config([
            'mail.default'         => 'netcore',
            'mail.mailers.netcore' => [
                'transport' => 'netcore',
                'key'       => $config['password'],
                'region'    => $config['settings']['region'] ?? 'us',
            ],
        ]);

        app('mail.manager')->purge('netcore');

        return 'netcore';
    }

    /** SparkPost via the Transmissions API (password = API Key). */
    private static function configureSparkpost(array $config): string
    {
        config([
            'mail.default'           => 'sparkpost',
            'mail.mailers.sparkpost' => [
                'transport' => 'sparkpost',
                'key'       => $config['password'],
                'region'    => $config['settings']['region'] ?? 'us',
            ],
        ]);

        app('mail.manager')->purge('sparkpost');

        return 'sparkpost';
    }

    /** Brevo (formerly Sendinblue) via the HTTP API (password = v3 API Key). */
    private static function configureBrevo(array $config): string
    {
        config([
            'mail.default'       => 'brevo',
            'mail.mailers.brevo' => [
                'transport' => 'brevo',
                'key'       => $config['password'],
            ],
        ]);

        app('mail.manager')->purge('brevo');

        return 'brevo';
    }

    /** SendGrid via the HTTP API (password = API Key). */
    private static function configureSendgrid(array $config): string
    {
        config([
            'mail.default'          => 'sendgrid',
            'mail.mailers.sendgrid' => [
                'transport' => 'sendgrid',
                'key'       => $config['password'],
            ],
        ]);

        app('mail.manager')->purge('sendgrid');

        return 'sendgrid';
    }

    /** Mailgun via the HTTP API (password = Private API Key). */
    private static function configureMailgun(array $config): string
    {
        $region = $config['settings']['region'] ?? 'us';

        config([
            'mail.default'         => 'mailgun',
            'mail.mailers.mailgun' => ['transport' => 'mailgun'],
            'services.mailgun'     => [
                'domain'   => $config['settings']['domain'] ?? '',
                'secret'   => $config['password'],
                'endpoint' => $region === 'eu' ? 'api.eu.mailgun.net' : 'api.mailgun.net',
                'scheme'   => 'https',
            ],
        ]);

        app('mail.manager')->purge('mailgun');

        return 'mailgun';
    }

    /** Amazon SES via the AWS API (username = Access Key, password = Secret Key). */
    private static function configureSes(array $config): string
    {
        config([
            'mail.default'     => 'ses',
            'mail.mailers.ses' => ['transport' => 'ses'],
            'services.ses'     => [
                'key'    => $config['username'],
                'secret' => $config['password'],
                'region' => $config['settings']['region'] ?? 'us-east-1',
            ],
        ]);

        app('mail.manager')->purge('ses');

        return 'ses';
    }

    /** Generic SMTP relay ("Other SMTP"). */
    private static function configureSmtp(array $config): string
    {
        $encryption  = strtolower($config['encryption'] ?? 'none');
        $authEnabled = (bool) ($config['auth_enabled'] ?? true);

        // Implicit TLS (smtps) for SSL, or TLS on port 465; otherwise plain
        // connection where STARTTLS is negotiated when the server offers it.
        $scheme = ($encryption === 'ssl' || ($encryption === 'tls' && (int) $config['port'] === 465))
            ? 'smtps'
            : 'smtp';

        config([
            'mail.default'                 => 'smtp',
            'mail.mailers.smtp.scheme'     => $scheme,
            'mail.mailers.smtp.host'       => $config['host'],
            'mail.mailers.smtp.port'       => (int) $config['port'],
            'mail.mailers.smtp.encryption' => $encryption === 'none' ? null : $encryption,
            'mail.mailers.smtp.username'   => $authEnabled ? ($config['username'] ?? null) : null,
            'mail.mailers.smtp.password'   => $authEnabled ? ($config['password'] ?? null) : null,
            // Honored by Symfony Mailer >= 7.1; on 6.x STARTTLS is always
            // auto-negotiated when the server advertises it.
            'mail.mailers.smtp.auto_tls'   => (bool) ($config['auto_tls'] ?? true),
        ]);

        app('mail.manager')->purge('smtp');

        return 'smtp';
    }

    public static function getDefault(): ?SmtpConfig
    {
        return SmtpConfig::where('is_default', true)->first()
            ?? SmtpConfig::latest()->first();
    }

    /**
     * Resolve which SMTP profile a campaign should send through: an explicit
     * per-campaign choice (Sender Identity picker, saved to
     * campaign.settings.smtp_config_id, or passed as $overrideId for calls
     * that haven't been persisted yet, e.g. "send a test email") if set,
     * else the tenant's default profile. Always scoped by the campaign's
     * own user_id  this runs from both authenticated controllers and
     * queued jobs with no session, so it can't rely on the auth()-based
     * tenant global scope.
     */
    public static function resolveForCampaign(Campaign $campaign, ?int $overrideId = null): ?SmtpConfig
    {
        $id = $overrideId ?? ($campaign->settings['smtp_config_id'] ?? null);

        $smtp = $id
            ? SmtpConfig::where('user_id', $campaign->user_id)->find($id)
            : null;

        return $smtp
            ?? SmtpConfig::where('user_id', $campaign->user_id)->where('is_default', true)->first()
            ?? SmtpConfig::where('user_id', $campaign->user_id)->latest()->first();
    }

    /**
     * Resolve the From/Reply-To identity a send should use, applying the
     * same precedence everywhere (real sends and test sends alike):
     *   1. The SMTP profile's own From Name/Email.
     *   2. A campaign-level override, unless the profile forces its identity.
     *   3. Settings > Email Settings > Default From Settings, as the
     *      last-resort fallback.
     * Reply-To Email follows the campaign override then the default; Reply-To
     * Name has no per-campaign override and always comes from the default.
     *
     * @return array{from_email: ?string, from_name: ?string, reply_email: ?string, reply_name: ?string, return_path: ?string}
     */
    public static function resolveSenderIdentity(Campaign $campaign, ?SmtpConfig $smtp): array
    {
        $fromEmail = $smtp?->from_email ?: null;
        $fromName  = $smtp?->from_name  ?: null;

        if ($smtp && !$smtp->force_from_email && !empty($campaign->settings['from_email'])) {
            $fromEmail = $campaign->settings['from_email'];
        }
        if ($smtp && !$smtp->force_from_name && !empty($campaign->settings['from_name'])) {
            $fromName = $campaign->settings['from_name'];
        }

        $emailSettings = BusinessSettings::emailSettings($campaign->id);
        if (empty($fromEmail) && !empty($emailSettings['from_email'])) {
            $fromEmail = $emailSettings['from_email'];
        }
        if (empty($fromName) && !empty($emailSettings['from_name'])) {
            $fromName = $emailSettings['from_name'];
        }

        $replyEmail = $campaign->settings['reply_to_email'] ?? ($emailSettings['reply_to_email'] ?: null);
        $replyName  = $emailSettings['reply_to_name'] ?: null;
        $returnPath = ($smtp?->return_path_match && $fromEmail) ? $fromEmail : null;

        return [
            'from_email'  => $fromEmail,
            'from_name'   => $fromName,
            'reply_email' => $replyEmail,
            'reply_name'  => $replyName,
            'return_path' => $returnPath,
        ];
    }
}
