<?php
/**
 * Email Notification Service
 *
 * Sends branded alert emails through the Newton Security API server.
 * Falls back to wp_mail() plain-text if the API is unreachable.
 *
 * Usage:
 *   VMPFence_Email_Service::send_alert('test_email', $context);
 *   VMPFence_Email_Service::send_alert('weak_password_alert', $context, ['admin@example.com']);
 *
 * @package    VMPFence
 * @subpackage Includes
 * @since      2.2.6
 */

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

class VMPFence_Email_Service {

    /**
     * API endpoint path for sending alerts (logical name for endpoint mapper).
     */
    const API_ENDPOINT = 'notifications/send-alert';

    /**
     * Send a branded alert email via the API server.
     *
     * 1. Checks the master email toggle (email_notifications_enabled).
     * 2. POSTs to /api/v1/notifications/send-alert.
     * 3. On failure, falls back to wp_mail() with plain text.
     *
     * @since 2.2.6
     * @param string      $event_type  Template name (e.g. 'test_email').
     * @param array       $context     Template context variables.
     * @param array|null  $recipients  Override recipients. Default: notification_email config.
     * @return array      {success: bool, method: 'api'|'fallback'|'disabled', message: string}
     */
    public static function send_alert($event_type, $context = array(), $recipients = null) {
        // ---------------------------------------------------------------
        // 1. Master kill switch
        // ---------------------------------------------------------------
        if (!VMPFence_Config::get('email_notifications_enabled', true)) {
            return array(
                'success' => false,
                'method'  => 'disabled',
                'message' => 'Email notifications are disabled.',
            );
        }

        // ---------------------------------------------------------------
        // 2. Resolve recipients
        // ---------------------------------------------------------------
        if (empty($recipients)) {
            $recipients = self::get_recipients();
        }
        if (empty($recipients)) {
            return array(
                'success' => false,
                'method'  => 'disabled',
                'message' => 'No recipients configured.',
            );
        }

        // ---------------------------------------------------------------
        // 3. Enrich context with site info
        // ---------------------------------------------------------------
        $context['site_name'] = isset($context['site_name'])
            ? $context['site_name']
            : get_bloginfo('name');
        $context['site_url'] = isset($context['site_url'])
            ? $context['site_url']
            : get_site_url();

        // ---------------------------------------------------------------
        // 4. Try API server
        // ---------------------------------------------------------------
        $api_result = self::send_via_api($event_type, $context, $recipients);

        if ($api_result['success']) {
            $result = array(
                'success' => true,
                'method'  => 'api',
                'message' => $api_result['message'],
                'email_id' => isset($api_result['email_id']) ? $api_result['email_id'] : null,
            );
            // Propagate rate-limit info so callers can distinguish "sent" from "suppressed"
            if (!empty($api_result['rate_limited'])) {
                $result['rate_limited'] = true;
                $result['retry_after']  = isset($api_result['retry_after']) ? (int) $api_result['retry_after'] : null;
            }
            return $result;
        }

        // ---------------------------------------------------------------
        // 5. Fallback to wp_mail()
        // ---------------------------------------------------------------
        if (class_exists('VMPFence_Utils')) {
            VMPFence_Utils::log(
                'API email failed, falling back to wp_mail: ' . $api_result['message'],
                'warning'
            );
        }

        $fallback = self::fallback_wp_mail($event_type, $context, $recipients);

        return array(
            'success' => $fallback,
            'method'  => 'fallback',
            'message' => $fallback
                ? 'Sent via wp_mail fallback.'
                : 'Both API and wp_mail fallback failed.',
        );
    }

    /**
     * Get the configured notification recipients.
     *
     * @since 2.2.6
     * @return array List of email addresses.
     */
    public static function get_recipients() {
        $config_email = VMPFence_Config::get('notification_email', '');

        if (!empty($config_email)) {
            // Comma-separated list
            $list = array_map('trim', explode(',', $config_email));
            return array_filter($list, function ($e) {
                return is_email($e);
            });
        }

        // Fallback to site admin email
        $admin = get_option('admin_email');
        return $admin ? array($admin) : array();
    }

    // ------------------------------------------------------------------
    // Private: API call
    // ------------------------------------------------------------------

    /**
     * POST the alert to the server API.
     *
     * Uses wp_remote_post() directly with Bearer token auth.
     * The endpoint uses PluginAPIKeyAuth (same as scan sync, settings sync).
     *
     * @since 2.2.6
     * @param string $event_type
     * @param array  $context
     * @param array  $recipients
     * @return array {success: bool, message: string, email_id: string|null}
     */
    private static function send_via_api($event_type, $context, $recipients) {
        // Gate: skip if API is known to be unavailable (matches other plugin modules)
        if (!VMPFence_Config::is_api_available()) {
            return array(
                'success' => false,
                'message' => 'API not available.',
            );
        }

        $api_key = VMPFence_Config::get('central_api_key', '');
        if (empty($api_key)) {
            return array(
                'success' => false,
                'message' => 'No API key configured.',
            );
        }

        // Build the full URL using the endpoint config
        $base_url = VMPFence_Endpoint_Config::get_api_base_url();
        $url = trailingslashit($base_url) . 'notifications/send-alert';

        $body = array(
            'event_type' => $event_type,
            'recipients' => array_values($recipients),
            'site_url'   => isset($context['site_url']) ? $context['site_url'] : get_site_url(),
            'site_name'  => isset($context['site_name']) ? $context['site_name'] : get_bloginfo('name'),
            'context'    => $context,
        );

        $args = array(
            'method'      => 'POST',
            'timeout'     => 30,
            'redirection' => 5,
            'httpversion' => '1.1',
            'headers'     => array(
                'Content-Type'  => 'application/json',
                'Accept'        => 'application/json',
                'Authorization' => 'Bearer ' . $api_key,
            ),
            'body'        => wp_json_encode($body),
            'sslverify'   => true,
        );

        $response = wp_remote_post($url, $args);

        if (is_wp_error($response)) {
            return array(
                'success' => false,
                'message' => $response->get_error_message(),
            );
        }

        $code = wp_remote_retrieve_response_code($response);
        $data = json_decode(wp_remote_retrieve_body($response), true);

        // Phase 6: Rate-limited (429) — server intentionally suppressed.
        // Do NOT fall back to wp_mail; that would bypass the rate limit.
        if ($code === 429) {
            return array(
                'success'      => true,
                'rate_limited' => true,
                'message'      => isset($data['message']) ? $data['message'] : 'Rate limited',
                'retry_after'  => isset($data['retry_after']) ? (int) $data['retry_after'] : null,
                'email_id'     => null,
            );
        }

        if ($code >= 200 && $code < 300 && isset($data['success']) && $data['success']) {
            return array(
                'success'  => true,
                'message'  => isset($data['message']) ? $data['message'] : 'Email queued.',
                'email_id' => isset($data['email_id']) ? $data['email_id'] : null,
            );
        }

        $error_msg = isset($data['message']) ? $data['message'] : "HTTP $code";
        return array(
            'success' => false,
            'message' => $error_msg,
        );
    }

    // ------------------------------------------------------------------
    // Private: wp_mail fallback
    // ------------------------------------------------------------------

    /**
     * Send a plain-text fallback email via wp_mail().
     *
     * @since 2.2.6
     * @param string $event_type
     * @param array  $context
     * @param array  $recipients
     * @return bool  True on success.
     */
    private static function fallback_wp_mail($event_type, $context, $recipients) {
        $site_name = isset($context['site_name']) ? $context['site_name'] : get_bloginfo('name');

        $subject = sprintf('[%s] VMP Security Alert — %s', $site_name, self::format_event_type($event_type));

        $message  = "VMP Security Alert\n";
        $message .= "===================\n\n";
        $message .= "Event: " . self::format_event_type($event_type) . "\n";
        $message .= "Site:  " . (isset($context['site_url']) ? $context['site_url'] : get_site_url()) . "\n";
        $message .= "Time:  " . current_time('Y-m-d H:i:s') . "\n\n";

        // Include context as readable key-value pairs
        foreach ($context as $key => $value) {
            if (in_array($key, array('site_name', 'site_url'), true)) {
                continue; // already included above
            }
            // Handle special keys first (before is_scalar, which would catch empty strings)
            if ($key === 'html_content') {
                if (!empty($value)) {
                    $message .= "\n" . wp_strip_all_tags((string) $value) . "\n";
                }
                continue;
            }
            if ($key === 'report_text') {
                if (!empty($value)) {
                    $message .= "\n" . (string) $value . "\n";
                }
                continue;
            }
            if (is_bool($value)) {
                $message .= ucfirst(str_replace('_', ' ', $key)) . ": " . ($value ? 'Yes' : 'No') . "\n";
            } elseif (is_scalar($value) && !is_null($value)) {
                $message .= ucfirst(str_replace('_', ' ', $key)) . ": " . $value . "\n";
            } elseif (is_array($value)) {
                $label = ucfirst(str_replace('_', ' ', $key));
                $is_indexed = array_values($value) === $value;
                if ($is_indexed) {
                    // Indexed array (e.g. vulnerabilities, issues)
                    $message .= "\n" . $label . ":\n";
                    foreach (array_slice($value, 0, 20) as $item) {
                        if (is_array($item)) {
                            $parts = array();
                            foreach ($item as $k => $v) {
                                if (is_scalar($v) && !is_null($v) && $v !== '') {
                                    $parts[] = $v;
                                }
                            }
                            $message .= "  - " . implode(' | ', $parts) . "\n";
                        } elseif (is_scalar($item) && !is_null($item)) {
                            $message .= "  - " . $item . "\n";
                        }
                    }
                } else {
                    // Associative array (e.g. debug_logs, scan_stats, gsb_results)
                    $message .= "\n" . $label . ":\n";
                    foreach ($value as $k => $v) {
                        if (is_scalar($v) && !is_null($v)) {
                            $message .= "  " . $k . ": " . $v . "\n";
                        } elseif (is_array($v)) {
                            $message .= "  " . $k . ":\n";
                            foreach (array_slice($v, 0, 50) as $sub_item) {
                                if (is_array($sub_item) && isset($sub_item['message'])) {
                                    $message .= "    " . (isset($sub_item['timestamp']) ? '[' . $sub_item['timestamp'] . '] ' : '')
                                        . (isset($sub_item['level']) ? $sub_item['level'] . ': ' : '')
                                        . $sub_item['message'] . "\n";
                                } elseif (is_scalar($sub_item) && !is_null($sub_item)) {
                                    $message .= "    " . $sub_item . "\n";
                                }
                            }
                        }
                    }
                }
            }
        }

        $message .= "\n---\nThis is a fallback plain-text email. The VMP Security server was unreachable.\n";
        $message .= "VMPFence Security - https://vmpsecurity.com\n";

        $to = implode(',', $recipients);
        $headers = array('Content-Type: text/plain; charset=UTF-8');

        return wp_mail($to, $subject, $message, $headers);
    }

    // ------------------------------------------------------------------
    // Private: helpers
    // ------------------------------------------------------------------

    /**
     * Convert event_type slug to human-readable label.
     *
     * @since 2.2.6
     * @param string $event_type
     * @return string
     */
    private static function format_event_type($event_type) {
        return ucwords(str_replace('_', ' ', $event_type));
    }

    // ------------------------------------------------------------------
    // Public: Alert toggle check
    // ------------------------------------------------------------------

    /**
     * Maps event_type to its config toggle key.
     *
     * Returns null for event types that are always sent (test_email,
     * diagnostic_report, support_ticket_confirmation, activity_log).
     *
     * @since 2.2.6
     */
    private static $toggle_map = array(
        'weak_password_alert'        => 'alert_on_breached_password',
        'activity_report'            => 'enable_email_summary',
        'cve_vulnerability_alert'    => 'alert_on_scan_results',
        'scan_completion'            => 'alert_on_scan_results',
        'auto_update_installed'      => 'alert_on_auto_update',
        'plugin_deactivated'         => 'alert_on_deactivation',
        'waf_disabled'               => 'alert_on_waf_disabled',
        'ip_blocked'                 => 'alert_on_ip_blocked',
        'account_lockout'            => 'alert_on_lockout',
        'breached_password'          => 'alert_on_breached_password',
        'password_reset'             => 'alert_on_lost_password',
        'admin_login'                => 'alert_on_admin_login',
        'admin_new_device_login'     => 'alert_admin_login_new_device',
        'user_login'                 => 'alert_on_user_login',
        'user_new_device_login'      => 'alert_user_login_new_device',
        'attack_spike'               => 'alert_on_attack_increase',
        'gsb_blocklist_detected'     => 'scansEnabled_checkGSB',
    );

    /**
     * Check if the alert toggle for a given event_type is enabled.
     *
     * Returns true for event types without a toggle (always-send types).
     * Checks the VMPFence_Config value for the mapped toggle key.
     *
     * @since 2.2.6
     * @param string $event_type  The email event type.
     * @return bool  True if the alert should be sent.
     */
    public static function is_alert_enabled($event_type) {
        if (!isset(self::$toggle_map[$event_type])) {
            // No toggle for this type — always allowed
            return true;
        }

        // Per-key defaults matching the all-options settings page.
        // Without these, fresh installs (DB key not yet set) would
        // default to OFF despite the UI showing them as ON.
        $toggle_defaults = array(
            'alert_on_breached_password'   => 1,
            'enable_email_summary'         => 1,
            'alert_on_scan_results'        => 1,
            'alert_on_auto_update'         => 0,
            'alert_on_deactivation'        => 1,
            'alert_on_waf_disabled'        => 1,
            'alert_on_ip_blocked'          => 1,
            'alert_on_lockout'             => 1,
            'alert_on_lost_password'       => 1,
            'alert_on_admin_login'         => 1,
            'alert_admin_login_new_device' => 0,
            'alert_on_user_login'          => 0,
            'alert_user_login_new_device'  => 0,
            'alert_on_attack_increase'     => 1,
            'scansEnabled_checkGSB'        => 1,
        );

        $config_key = self::$toggle_map[$event_type];
        $default = isset($toggle_defaults[$config_key]) ? $toggle_defaults[$config_key] : 0;

        return (bool) VMPFence_Config::get($config_key, $default);
    }
}
