<?php
/**
 * WP Mail SMTP importer.
 *
 * @since 1.0.0
 *
 * @package NimblyMail\SMTP\Import
 */

namespace NimblyMail\SMTP\Import;

defined( 'ABSPATH' ) || exit;

/**
 * Class WpMailSmtpImporter
 *
 * Imports email settings from the WP Mail SMTP plugin into NimblyMail.
 * Reads settings stored under the `wp_mail_smtp` option key and maps
 * them to the NimblyMail options format, skipping encrypted credentials
 * and adding actionable warnings for each skipped field.
 *
 * @since 1.0.0
 * @since 1.0.4 Added Brevo credential mapping.
 * @since 1.0.6 Added Outlook / Microsoft 365 credential mapping. Debug-event
 *              retention is now converted from the source's seconds to
 *              NimblyMail's days. A source with no mailer key at all no
 *              longer defaults to PHP mail() and now leaves mail.mailer
 *              unset instead.
 */
class WpMailSmtpImporter extends AbstractImporter {

    /**
     * Get the importer slug identifier.
     *
     * @since 1.0.0
     *
     * @return string
     */
    public function get_slug(): string {
        return 'wp-mail-smtp';
    }

    /**
     * Get the human-readable name of the source plugin.
     *
     * @since 1.0.0
     *
     * @return string
     */
    public function get_name(): string {
        return __( 'WP Mail SMTP', 'nimblymail-smtp' );
    }

    /**
     * Get a short description for the importer card UI.
     *
     * @since 1.0.0
     *
     * @return string
     */
    public function get_description(): string {
        return __( 'Import your email settings from WP Mail SMTP.', 'nimblymail-smtp' );
    }

    /**
     * Get the wp_options key for the WP Mail SMTP plugin settings.
     *
     * @since 1.0.0
     *
     * @return string
     */
    public function get_option_key(): string {
        return 'wp_mail_smtp';
    }

    /**
     * Get the plugin basename for active-plugin detection.
     *
     * Returns the free-edition basename. The Pro edition lives at
     * `wp-mail-smtp-pro/wp_mail_smtp.php` and is detected by `is_available()`.
     *
     * @since 1.0.0
     *
     * @return string
     */
    public function get_plugin_basename(): string {
        return 'wp-mail-smtp/wp_mail_smtp.php';
    }

    /**
     * Pro-edition plugin basename for active-plugin detection.
     *
     * @since 1.0.0
     *
     * @var string
     */
    private const PRO_PLUGIN_BASENAME = 'wp-mail-smtp-pro/wp_mail_smtp.php';

    /**
     * Check if WP Mail SMTP (free or Pro) is active and has importable data.
     *
     * Detects either the free or Pro edition (both store settings under
     * the same `wp_mail_smtp` option key) in any scope (current site or
     * network), then defers data lookup to the inherited
     * `get_source_data()` which handles network/main-site fallback.
     *
     * @since 1.0.0
     *
     * @return bool
     */
    public function is_available(): bool {
        $basenames = array(
            $this->get_plugin_basename(),
            self::PRO_PLUGIN_BASENAME,
        );

        if ( ! $this->is_source_plugin_active( $basenames ) ) {
            return false;
        }

        return ! empty( $this->get_source_data() );
    }

    /**
     * Map WP Mail SMTP settings to the NimblyMail options format.
     *
     * Iterates over the source data and produces mapped settings, warnings
     * for encrypted credentials that cannot be imported, and a skipped list
     * for fields omitted for other reasons. Only groups that contain at least
     * one importable value are included in the returned settings array.
     *
     * @since 1.0.0
     * @since 1.0.4 Brevo credentials are now carried across. A mailer is only
     *              saved when its credentials actually came with it, and a
     *              source mailer NimblyMail cannot handle now raises a warning
     *              instead of being passed over in silence.
     * @since 1.0.6 Outlook credentials are now carried across. Debug-event
     *              retention is now converted from the source's seconds to
     *              NimblyMail's days instead of being copied verbatim. A
     *              source with no mailer key at all now leaves mail.mailer
     *              unset instead of defaulting to PHP mail() and silently
     *              overwriting a working configuration.
     *
     * @param array $source_data Raw WP Mail SMTP options array from wp_options.
     *
     * @return array{settings: array, warnings: array, skipped: array}
     */
    protected function map_settings( array $source_data ): array {
        $settings = array();
        $warnings = array();
        $skipped  = array();

        $mail_settings = isset( $source_data['mail'] ) && is_array( $source_data['mail'] )
            ? $source_data['mail']
            : array();

        $this->map_mail_settings( $mail_settings, $settings );

        $mailer        = isset( $mail_settings['mailer'] ) ? (string) $mail_settings['mailer'] : null;
        $mapped_mailer = null;

        if ( null !== $mailer ) {
            $mapped_mailer = $this->map_mailer_slug( $mailer );

            if ( null !== $mapped_mailer ) {
                $settings['mail']['mailer'] = $mapped_mailer;
            } else {
                $this->skip_unsupported_mailer( $mailer, $warnings, $skipped );
            }
        }

        $this->map_mailer_settings( $mapped_mailer, $source_data, $settings, $warnings, $skipped );

        // General settings.
        $general_source = isset( $source_data['general'] ) && is_array( $source_data['general'] )
            ? $source_data['general']
            : array();

        if ( isset( $general_source['do_not_send'] ) ) {
            $settings['general']['do_not_send'] = $this->to_bool( $general_source['do_not_send'] );
        }

        // Debug events settings.
        $debug_events_source = isset( $source_data['debug_events'] ) && is_array( $source_data['debug_events'] )
            ? $source_data['debug_events']
            : array();

        if ( isset( $debug_events_source['retention_period'] ) ) {
            $settings['general']['debug_events_retention_days'] = max( 1, (int) round( absint( $debug_events_source['retention_period'] ) / DAY_IN_SECONDS ) );
        }

        // Logs settings.
        $logs_source = isset( $source_data['logs'] ) && is_array( $source_data['logs'] )
            ? $source_data['logs']
            : array();

        if ( isset( $logs_source['enabled'] ) ) {
            $settings['logs']['enabled'] = $this->to_bool( $logs_source['enabled'] );
        }

        // Alert settings (email + non-email channel warnings).
        $this->map_alert_settings( $source_data, $settings, $warnings );

        $this->guard_mailer_has_credentials( $mapped_mailer, $settings, $warnings, $skipped );

        return array(
            'settings' => $settings,
            'warnings' => $warnings,
            'skipped'  => $skipped,
        );
    }

    /**
     * Map alert settings from the source data.
     *
     * Reads `alert_email.enabled` and the list of `alert_email.connections`,
     * mapping the first recipient to NimblyMail's `alerts.email_to` and any
     * additional recipients to `alerts.cc`. Also surfaces warnings when the
     * source plugin had non-email alert channels (Slack, Discord, Twilio,
     * custom webhook, push notifications, WhatsApp) enabled, since NimblyMail
     * supports email alerts only.
     *
     * @since 1.0.0
     *
     * @param array $source_data Full raw options array from the source plugin.
     * @param array $settings    Settings array passed by reference.
     * @param array $warnings    Warnings array passed by reference.
     */
    private function map_alert_settings( array $source_data, array &$settings, array &$warnings ): void {
        $alert_email = isset( $source_data['alert_email'] ) && is_array( $source_data['alert_email'] )
            ? $source_data['alert_email']
            : array();

        if ( isset( $alert_email['enabled'] ) ) {
            $settings['alerts']['enabled'] = $this->to_bool( $alert_email['enabled'] );
        }

        $recipients = array();
        if ( isset( $alert_email['connections'] ) && is_array( $alert_email['connections'] ) ) {
            foreach ( $alert_email['connections'] as $connection ) {
                if ( ! is_array( $connection ) || empty( $connection['send_to'] ) ) {
                    continue;
                }
                $email = sanitize_text_field( $connection['send_to'] );
                if ( '' !== $email ) {
                    $recipients[] = $email;
                }
            }
        }

        if ( ! empty( $recipients ) ) {
            $settings['alerts']['email_to'] = array_shift( $recipients );

            if ( ! empty( $recipients ) ) {
                $settings['alerts']['cc'] = implode( ',', $recipients );
            }
        }

        $other_channels = array(
            'alert_slack_webhook'      => __( 'Slack', 'nimblymail-smtp' ),
            'alert_discord_webhook'    => __( 'Discord', 'nimblymail-smtp' ),
            'alert_twilio_sms'         => __( 'Twilio SMS', 'nimblymail-smtp' ),
            'alert_custom_webhook'     => __( 'custom webhook', 'nimblymail-smtp' ),
            'alert_push_notifications' => __( 'push notifications', 'nimblymail-smtp' ),
            'alert_whatsapp'           => __( 'WhatsApp', 'nimblymail-smtp' ),
        );

        foreach ( $other_channels as $key => $label ) {
            if (
                isset( $source_data[ $key ] ) &&
                is_array( $source_data[ $key ] ) &&
                ! empty( $source_data[ $key ]['enabled'] )
            ) {
                $warnings[] = array(
                    'field'   => 'alerts.' . $key,
                    'message' => sprintf(
                        /* translators: %s: alert channel label (Slack, Discord, etc.). */
                        __( 'The %s alert channel was enabled in the source plugin but is not supported by NimblyMail. Only email alerts will be active after the import.', 'nimblymail-smtp' ),
                        $label
                    ),
                );
            }
        }
    }

    /**
     * Map general mail settings (from, return path) from the source data.
     *
     * @since 1.0.0
     *
     * @param array $mail_settings The `mail` sub-array from the source data.
     * @param array $settings      Settings array passed by reference.
     */
    private function map_mail_settings( array $mail_settings, array &$settings ): void {
        $string_fields = array(
            'from_email' => 'from_email',
            'from_name'  => 'from_name',
        );

        foreach ( $string_fields as $source_key => $target_key ) {
            if ( isset( $mail_settings[ $source_key ] ) && '' !== $mail_settings[ $source_key ] ) {
                $settings['mail'][ $target_key ] = sanitize_text_field( $mail_settings[ $source_key ] );
            }
        }

        $bool_fields = array(
            'from_email_force' => 'from_email_force',
            'from_name_force'  => 'from_name_force',
            'return_path'      => 'return_path',
        );

        foreach ( $bool_fields as $source_key => $target_key ) {
            if ( isset( $mail_settings[ $source_key ] ) ) {
                $settings['mail'][ $target_key ] = $this->to_bool( $mail_settings[ $source_key ] );
            }
        }
    }

    /**
     * Map mailer-specific settings based on the resolved NimblyMail mailer slug.
     *
     * @since 1.0.0
     * @since 1.0.6 Added the 'outlook' case.
     *
     * @param string|null $mapped_mailer The resolved NimblyMail mailer slug, or null if unsupported.
     * @param array       $source_data   Full raw options array from the source plugin.
     * @param array       $settings      Settings array passed by reference.
     * @param array       $warnings      Warnings array passed by reference.
     * @param array       $skipped       Skipped array passed by reference.
     */
    private function map_mailer_settings(
        ?string $mapped_mailer,
        array $source_data,
        array &$settings,
        array &$warnings,
        array &$skipped
    ): void {
        switch ( $mapped_mailer ) {
            case 'smtp':
                $this->map_smtp_settings( $source_data, $settings, $warnings, $skipped );
                break;

            case 'ses':
                $this->map_ses_settings( $source_data, $settings );
                break;

            case 'gmail':
                $this->map_gmail_settings( $source_data, $settings, $warnings );
                break;

            case 'outlook':
                $this->map_outlook_settings( $source_data, $settings, $warnings );
                break;

            case 'sendlayer':
                $this->map_sendlayer_settings( $source_data, $settings );
                break;

            case 'resend':
                $this->map_resend_settings( $source_data, $settings );
                break;

            case 'brevo':
                $this->map_brevo_settings( $source_data, $settings );
                break;
        }
    }

    /**
     * Map SMTP-specific settings from the source data.
     *
     * Imports host, port, encryption, autotls, auth, and user verbatim.
     * The SMTP password is the only credential WP Mail SMTP encrypts at
     * rest (via Crypto::encrypt) and therefore cannot be recovered, so it
     * is skipped with a warning. All other mailer credentials are stored
     * in plain text by WP Mail SMTP and ARE imported by sibling helpers.
     *
     * @since 1.0.0
     *
     * @param array $source_data Full raw options array from the source plugin.
     * @param array $settings    Settings array passed by reference.
     * @param array $warnings    Warnings array passed by reference.
     * @param array $skipped     Skipped array passed by reference.
     */
    private function map_smtp_settings(
        array $source_data,
        array &$settings,
        array &$warnings,
        array &$skipped
    ): void {
        $smtp = isset( $source_data['smtp'] ) && is_array( $source_data['smtp'] )
            ? $source_data['smtp']
            : array();

        if ( isset( $smtp['host'] ) && '' !== $smtp['host'] ) {
            $settings['smtp']['host'] = sanitize_text_field( $smtp['host'] );
        }

        if ( isset( $smtp['port'] ) && '' !== (string) $smtp['port'] ) {
            $settings['smtp']['port'] = absint( $smtp['port'] );
        }

        if ( isset( $smtp['encryption'] ) ) {
            $encryption_map = array(
                'none' => 'none',
                'ssl'  => 'ssl',
                'tls'  => 'tls',
            );
            $enc_value      = strtolower( (string) $smtp['encryption'] );
            if ( isset( $encryption_map[ $enc_value ] ) ) {
                $settings['smtp']['encryption'] = $encryption_map[ $enc_value ];
            }
        }

        if ( isset( $smtp['autotls'] ) ) {
            $settings['smtp']['autotls'] = $this->to_bool( $smtp['autotls'] );
        }

        if ( isset( $smtp['auth'] ) ) {
            $settings['smtp']['auth'] = $this->to_bool( $smtp['auth'] );
        }

        if ( isset( $smtp['user'] ) && '' !== $smtp['user'] ) {
            $settings['smtp']['user'] = sanitize_text_field( $smtp['user'] );
        }

        $this->skip_encrypted( __( 'SMTP Password', 'nimblymail-smtp' ), 'smtp.pass', $warnings, $skipped );
    }

    /**
     * Map Amazon SES settings from the source data.
     *
     * WP Mail SMTP stores SES credentials under the `amazonses` group with
     * fields `client_id` (access key id), `client_secret` (secret access key),
     * and `region`. All three are stored as plain text via sanitize_text_field,
     * so each is imported when present.
     *
     * @since 1.0.0
     *
     * @param array $source_data Full raw options array from the source plugin.
     * @param array $settings    Settings array passed by reference.
     */
    private function map_ses_settings(
        array $source_data,
        array &$settings
    ): void {
        $ses = isset( $source_data['amazonses'] ) && is_array( $source_data['amazonses'] )
            ? $source_data['amazonses']
            : array();

        if ( isset( $ses['client_id'] ) && '' !== $ses['client_id'] ) {
            $settings['ses']['access_key_id'] = sanitize_text_field( $ses['client_id'] );
        }

        if ( isset( $ses['client_secret'] ) && '' !== $ses['client_secret'] ) {
            $settings['ses']['secret_access_key'] = sanitize_text_field( $ses['client_secret'] );
        }

        if ( isset( $ses['region'] ) && '' !== $ses['region'] ) {
            $settings['ses']['region'] = sanitize_text_field( $ses['region'] );
        }
    }

    /**
     * Map Gmail OAuth settings from the source data.
     *
     * Imports `client_id` and `client_secret`, both of which WP Mail SMTP
     * stores in plain text. OAuth access/refresh tokens are not transferable
     * between plugins (different redirect URIs), so the user must re-authorise
     * the Gmail account in NimblyMail after the import — that is communicated
     * via a single warning.
     *
     * @since 1.0.0
     *
     * @param array $source_data Full raw options array from the source plugin.
     * @param array $settings    Settings array passed by reference.
     * @param array $warnings    Warnings array passed by reference.
     */
    private function map_gmail_settings(
        array $source_data,
        array &$settings,
        array &$warnings
    ): void {
        $gmail = isset( $source_data['gmail'] ) && is_array( $source_data['gmail'] )
            ? $source_data['gmail']
            : array();

        if ( isset( $gmail['client_id'] ) && '' !== $gmail['client_id'] ) {
            $settings['gmail']['client_id'] = sanitize_text_field( $gmail['client_id'] );
        }

        if ( isset( $gmail['client_secret'] ) && '' !== $gmail['client_secret'] ) {
            $settings['gmail']['client_secret'] = sanitize_text_field( $gmail['client_secret'] );
        }

        $warnings[] = array(
            'field'   => 'gmail.oauth',
            'message' => __( 'Gmail OAuth authorisation must be completed again inside NimblyMail. Please reconnect your Google account after the import.', 'nimblymail-smtp' ),
        );
    }

    /**
     * Map Outlook / Microsoft 365 OAuth settings from the source data.
     *
     * Imports `client_id` and `client_secret`, both of which WP Mail SMTP
     * stores in plain text. OAuth access/refresh tokens are not transferable
     * between plugins (different redirect URIs), so the user must re-authorise
     * the Outlook account in NimblyMail after the import — that is communicated
     * via a single warning. WP Mail SMTP has no equivalent stored option for
     * NimblyMail's `outlook.tenant`, so it is left at its default.
     *
     * @since 1.0.6
     *
     * @param array $source_data Full raw options array from the source plugin.
     * @param array $settings    Settings array passed by reference.
     * @param array $warnings    Warnings array passed by reference.
     */
    private function map_outlook_settings(
        array $source_data,
        array &$settings,
        array &$warnings
    ): void {
        $outlook = isset( $source_data['outlook'] ) && is_array( $source_data['outlook'] )
            ? $source_data['outlook']
            : array();

        if ( isset( $outlook['client_id'] ) && '' !== $outlook['client_id'] ) {
            $settings['outlook']['client_id'] = sanitize_text_field( $outlook['client_id'] );
        }

        if ( isset( $outlook['client_secret'] ) && '' !== $outlook['client_secret'] ) {
            $settings['outlook']['client_secret'] = sanitize_text_field( $outlook['client_secret'] );
        }

        $warnings[] = array(
            'field'   => 'outlook.oauth',
            'message' => __( 'Outlook OAuth authorisation must be completed again inside NimblyMail. Please reconnect your Microsoft account after the import.', 'nimblymail-smtp' ),
        );
    }

    /**
     * Map SendLayer settings from the source data.
     *
     * WP Mail SMTP stores the SendLayer API key in plain text under the
     * `sendlayer.api_key` group/key, so it is imported when present.
     *
     * @since 1.0.0
     *
     * @param array $source_data Full raw options array from the source plugin.
     * @param array $settings    Settings array passed by reference.
     */
    private function map_sendlayer_settings( array $source_data, array &$settings ): void {
        $sendlayer = isset( $source_data['sendlayer'] ) && is_array( $source_data['sendlayer'] )
            ? $source_data['sendlayer']
            : array();

        if ( isset( $sendlayer['api_key'] ) && '' !== $sendlayer['api_key'] ) {
            $settings['sendlayer']['api_key'] = sanitize_text_field( $sendlayer['api_key'] );
        }
    }

    /**
     * Map Resend settings from the source data.
     *
     * WP Mail SMTP stores the Resend API key in plain text under the
     * `resend.api_key` group/key, so it is imported when present.
     *
     * @since 1.0.0
     *
     * @param array $source_data Full raw options array from the source plugin.
     * @param array $settings    Settings array passed by reference.
     */
    private function map_resend_settings( array $source_data, array &$settings ): void {
        $resend = isset( $source_data['resend'] ) && is_array( $source_data['resend'] )
            ? $source_data['resend']
            : array();

        if ( isset( $resend['api_key'] ) && '' !== $resend['api_key'] ) {
            $settings['resend']['api_key'] = sanitize_text_field( $resend['api_key'] );
        }
    }

    /**
     * Map Brevo settings from the source data.
     *
     * WP Mail SMTP stores Brevo under the `sendinblue` group (its legacy
     * provider slug, retained for backward compatibility on the source
     * side). The API key is stored in plain text, so it is imported when
     * present.
     *
     * @since 1.0.4
     *
     * @param array $source_data Full raw options array from the source plugin.
     * @param array $settings    Settings array passed by reference.
     */
    private function map_brevo_settings( array $source_data, array &$settings ): void {
        $sendinblue = isset( $source_data['sendinblue'] ) && is_array( $source_data['sendinblue'] )
            ? $source_data['sendinblue']
            : array();

        if ( isset( $sendinblue['api_key'] ) && '' !== $sendinblue['api_key'] ) {
            $settings['brevo']['api_key'] = sanitize_text_field( $sendinblue['api_key'] );
        }
    }
}
