<?php
namespace RtmMail;

use Exception;
use RtmMail\Helpers\LogHelper;
use RtmMail\Mailing\Providers\SmtpHandler;
use RtmMail\Utils\LoggerUtils;
use RtmMailVendor\Dependencies\PHPMailer\PHPMailer\PHPMailer;
use WP_Error;

class CustomMailer
{
    public static function send(string|array $to, string $subject, string $message, string|array $headers = '', array $attachments = []): bool
    {
        $args = compact('to', 'subject', 'message', 'headers', 'attachments');
        return self::processMail($args);
    }



    public static function processMail(array $args): bool
    {
        $settings = get_option('rtm_mail_settings');

        // Ensure 'to' is an array
        $args['to'] = is_string($args['to']) ? explode(';', str_replace(',', ';', $args['to'])) : $args['to'];

        // Ensure 'headers' is an array
        $args['headers'] = is_string($args['headers']) ? explode("\n", str_replace("\\r\\n", "\n", $args['headers'])) : $args['headers'];

        $args['backtrace'] = self::getBacktrace();

        $settings['addresses'] = $settings['addresses'] ?? [];

        foreach ($settings['addresses'] as $address) {
            if (!empty($address['address'])) {
                if ($address['type'] === 'recipient') {
                    $args['to'][] = $address['address'];
                } else {
                    $args['headers'][] = ucfirst($address['type']) . ': ' . $address['address'];
                }
            }
        }

        $mail_data = self::format($args);
        $log_id = LogHelper::save($mail_data);

        Logger::info(
            "Mail has been logged",
            "A new email has been logged by the plugin with given id: #{$log_id}",
            'CustomMailer::processMail',
            'Log',
            [
                "mail_log" => $log_id,
                "request"  => LoggerUtils::filterRequest([
                    'HEADERS' => getallheaders(),
                    'POST'    => $_POST,
                    'GET'     => $_GET,
                ]),
                "user"     => LoggerUtils::getUser(),
            ]
        );

        if ($settings['block_mails'] ?? false) {
            return false;
        }

        $smtp_settings = get_option('rtm_mail_smtp_settings');
        $smtp_enabled = isset($smtp_settings['smtp_enabled']) && filter_var($smtp_settings['smtp_enabled'], FILTER_VALIDATE_BOOLEAN);

        if ($smtp_enabled) {
            $result = self::sendSmtp($log_id, $mail_data);
            return $result === 'success';
        } else {
            LogHelper::update($log_id, ['status' => 'sent', 'sent' => date('Y-m-d H:i:s')]);
            array_unshift($args['headers'], "Id: " . $log_id);
            return self::sendDirectMail($args);
        }
}

    private static function sendSmtp($log_id, $mail_data, $single_address = null)
    {
        $smtp_settings = get_option('rtm_mail_smtp_settings');
        $required_settings = ['other_smtp_host', 'other_smtp_port'];
        foreach ($required_settings as $setting) {
            if (empty($smtp_settings['smtp_settings']['other'][$setting])) {
                return __("SMTP setting '{$setting}' is missing or empty.");
            }
        }

        $handler = new SmtpHandler();
        self::initializeMailer($handler, $log_id);

        $sender_options = $mail_data['headers']['from'] ?? null;
        if ($sender_options && strpos($sender_options, '<') !== false) {
            [$send_title, $send_address] = explode('<', str_replace('>', '', $sender_options));
            $handler->setFrom(trim($send_title), trim($send_address));
        } else {
            $handler->setFrom('', $mail_data['sender']);
        }

        self::addAddresses($handler, $mail_data, $single_address);
        self::addHeadersAndAttachments($handler, $mail_data);

        $result = $handler->send();
        return $result === 'success' ? 'success' : __('Failed to send email via SMTP.');
    }

    private static function initializeMailer(SmtpHandler $handler, $log_id)
    {
        global $wp_version;
        $mailer = new PHPMailer(true);

        if (version_compare($wp_version, '5.4.99') > 0) {
            require_once ABSPATH . WPINC . '/class-phpmailer.php';
        } else {
            require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php';
            require_once ABSPATH . WPINC . '/PHPMailer/SMTP.php';
            require_once ABSPATH . WPINC . '/PHPMailer/Exception.php';
        }

        $handler->setMailer($mailer);
        $handler->setLogId($log_id);
    }

    private static function addAddresses(SmtpHandler $handler, $mail_data, $single_address)
    {
        if ($single_address) {
            $handler->addAddress($single_address);
        } else {
            foreach ($mail_data['receiver'] as $to_address) {
                if (!empty($to_address)) {
                    $handler->addAddress($to_address);
                }
            }
        }

        foreach ($mail_data['cc'] as $cc_address) {
            if (!empty($cc_address)) {
                $handler->addAddress($cc_address, 'cc');
            }
        }

        foreach ($mail_data['bcc'] as $bcc_address) {
            if (!empty($bcc_address)) {
                $handler->addAddress($bcc_address, 'bcc');
            }
        }
    }

    private static function addHeadersAndAttachments(SmtpHandler $handler, $mail_data)
    {
        foreach ($mail_data['headers'] as $name => $value) {
            $handler->setHeader($name, $value);
        }

        $handler->setSubject($mail_data['subject']);
        $handler->setBody($mail_data['body']);

        foreach ($mail_data['attachments'] as $attachment) {
            $handler->addAttachment($attachment);
        }
    }

    private static function sendDirectMail(array $args): bool
    {
        $mail = new PHPMailer(true);

        try {
            // Set mailer to use the default PHP mail()
            $mail->isMail();

            // Recipients
            foreach ($args['to'] as $recipient) {
                $mail->addAddress($recipient);
            }

            // Headers
            foreach ($args['headers'] as $header) {
                $headerArr = explode(':', $header, 2);
                if (count($headerArr) === 2) {
                    $mail->addCustomHeader(trim($headerArr[0]), trim($headerArr[1]));
                }
            }

            // Attachments
            foreach ($args['attachments'] as $attachment) {
                $mail->addAttachment($attachment);
            }

            // Content
            $mail->Subject = $args['subject'];
            $mail->Body = $args['message'];
            $mail->isHTML(true);

            $mail->send();
            return true;
        } catch (Exception $e) {
            Logger::error(
                "Failed to send email",
                "Mailer Error: {$mail->ErrorInfo}",
                'CustomMailer::sendDirectMail',
                'Mailing'
            );
            return false;
        }
    }

    /**
     * send_mail - sends mail based on saved log
     *
     * @param $log_id
     * @param null $single_address
     *
     * @return string|void error message or 'success'
     */

    public static function sendMail($log_id, $single_address = null)
    {
        $mail_log = LogHelper::get([
            'post_per_page' => null,
            'where'         => [
                'id' => [
                    'type'  => '=',
                    'value' => $log_id,
                ]
            ]
        ]);

        $mail_log = $mail_log[0] ?? null;

        // Return if the log doesn't exist anymore
        if ($mail_log == null) {
            return __('Log not found.');
        }

        $smtp_settings      = get_option('rtm_mail_smtp_settings');
        $smtp_enabled       = isset($smtp_settings['smtp_enabled']) && filter_var($smtp_settings['smtp_enabled'], FILTER_VALIDATE_BOOLEAN);
        $mail_log['sender'] = ( str_contains(strtolower($mail_log['sender']), '<') ) ? str_replace('>', '', explode('<', $mail_log['sender'])[1]) : $mail_log['sender'];
        if ($smtp_enabled) {
            return self::sendSmtp($log_id, $mail_log, $single_address);
        } else {
            $mail_data = [];
            // set the subject
            $mail_data['subject'] = $mail_log['subject'];
            // set the receivers
            $mail_data['to'] = $mail_log['receiver'];
            // set the body
            $mail_data['message'] = $mail_log['body'];
            // set the headers
            $mail_data['headers'] = [];
            // set receiver status
            $mail_data['receiver_status'] = $mail_log['receiver_status'];

            // Set the cc
            if (! empty($mail_log['cc'])) {
                foreach ($mail_log['cc'] as $cc_address) {
                    if (! empty($cc_address)) {
                        $mail_data['headers'][] = 'Cc: ' . $cc_address;
                    }
                }
            }
            // Set the bcc
            if (! empty($mail_log['bcc'])) {
                foreach ($mail_log['bcc'] as $bcc_address) {
                    if (! empty($bcc_address)) {
                        $mail_data['headers'][] = 'Bcc: ' . $bcc_address;
                    }
                }
            }

            // Set the remaining headers
            if (! empty($mail_log['headers'])) {
                foreach ($mail_log['headers'] as $header_key => $header_value) {
                    if (! empty($header_value)) {
                        $mail_data['headers'][] = ucfirst($header_key) . ': ' . $header_value;
                    }
                }
            }

            // Set the id in headers
            array_unshift($mail_data['headers'], "Id: " . $log_id);
            // set the attachments
            $mail_data['attachments'] = [];
            if (! empty($mail_log['attachments'])) {
                foreach ($mail_log['attachments'] as $attachment) {
                    if (! empty($attachment)) {
                        $mail_data['attachments'][] = $attachment;
                    }
                }
            }

            // Prepare success and receiver status array
            $success         = [];
            $receiver_status = [];

            if ($single_address === null) {
                // Loop over every 'to' mail address
                foreach ($mail_data['to'] as $to_address) {
                    // Add tracking link to message
                    $mail_data['message'] .= '<img src="' . self::getTrackUrl($log_id, $to_address) . '" style="display:none;" />';

                    // Update 'to' field for single address sending
                    $mail_data['to'] = [$to_address];

                    $mail_sent = self::sendDirectMail($mail_data);
                    $receiver_status[$to_address] = ['opened' => false, 'sent' => $mail_sent];
                    $success[] = $mail_sent;
                }
            } else {
                // Add tracking link to message
                $mail_data['message'] .= '<img src="' . self::getTrackUrl($log_id, $single_address) . '" style="display:none;" />';

                // Update 'to' field for single address sending
                $mail_data['to'] = [$single_address];

                $mail_sent = self::sendDirectMail($mail_data);
                $receiver_status[$single_address] = ['opened' => false, 'sent' => $mail_sent];
                $success[] = $mail_sent;
            }

            // Update log with receiver status
            LogHelper::update($log_id, ['receiver_status' => json_encode($receiver_status)]);

            // Check if sending was successful
            if (in_array(true, $success, true)) {
                // Set status to 'sent'
                LogHelper::update($log_id, ['status' => 'sent', 'sent' => date('Y-m-d H:i:s', time())]);
                Logger::info(
                    "Email (#{$log_id}) has been sent",
                    "Email with log id #{$log_id} has been sent successfully",
                    'CustomMailer::sendMail',
                    'Mailing',
                    [
                        "mail_log" => $log_id,
                        "user"     => LoggerUtils::getUser(),
                    ]
                );

                return 'success';
            } else {
                // Set status to 'failed'
                LogHelper::update($log_id, ['status' => 'failed', 'sent' => null]);
                return __('Failed sending email, check the debug logs for more info!', 'rtm-mail');
            }
        }
    }

    /**
     * format - formats the mail data to save as a log item
     *
     * @param $args
     *
     * @return array (formatted mail data)
     */
    private static function format($args): array
    {
        $settings       = get_option('rtm_mail_settings');
        $sender_options = $settings['sender_options'] ?? null;
        // Return value array
        $formatted_data = [];
        // Set default sender options if it exists otherwise set the wordpress admin mail
        $formatted_data['sender']          = ( $sender_options != null && ! empty($sender_options['address']) ) ? $sender_options['address'] : get_option('admin_email');
        $sender_title                      = ( $sender_options != null && ! empty($sender_options['title']) ) ? $sender_options['title'] : get_bloginfo('name');
        $formatted_data['receiver']        = [];
        $formatted_data['cc']              = [];
        $formatted_data['bcc']             = [];
        $formatted_data['subject']         = $args['subject'];
        $formatted_data['body']            = stripslashes(htmlspecialchars_decode($args['message']));
        $formatted_data['attachments']     = [];
        $formatted_data['headers']         = [];
        $formatted_data['backtrace']       = $args['backtrace'] ?? [];
        $formatted_data['created']         = date('Y-m-d H:i:s', time());
        $formatted_data['receiver_status'] = [];

        $args['to']          = $args['to'] ?? [];
        $args['headers']     = $args['headers'] ?? [];
        $args['attachments'] = $args['attachments'] ?? [];

        // Check for every 'to' if it's not empty
        foreach ($args['to'] as $receiver_mail) {
            if (! empty($receiver_mail)) {
                $formatted_data['receiver'][] = $receiver_mail;
                // Set receiver status default
                $formatted_data['receiver_status'][ $receiver_mail ] = [
                    'opened' => false,
                    'sent'   => false,
                ];
            }
        }

        // Check for every header if the email is not empty and add it to the formatted data
        foreach ($args['headers'] as $header) {
            if (strpos(strtolower($header), 'from:') !== false) {
                // Remove header key and trim quotes
                $from_email = trim(str_replace('from: ', '', strtolower($header)), '\'"');
                if (! empty($from_email)) {
                    if (count(explode('<', $from_email)) > 1) {
                        $new_mail                 = rtrim(explode('<', $from_email)[1], '>');
                        $sender_title             = explode('<', $from_email)[0];
                        $formatted_data['sender'] = $new_mail;
                    } else {
                        $formatted_data['sender'] = $from_email;
                    }
                }
            } else if (strpos(strtolower($header), 'bcc:') !== false) {
                // Remove header key and trim quotes
                $bcc_email = trim(str_replace('bcc: ', '', strtolower($header)), '\'"');
                if (! empty($bcc_email)) {
                    $formatted_data['bcc'][] = $bcc_email;
                }
            } else if (strpos(strtolower($header), 'cc:') !== false) {
                // Remove header key and trim quotes
                $cc_email = trim(str_replace('cc: ', '', strtolower($header)), '\'"');
                if (! empty($cc_email)) {
                    $formatted_data['cc'][] = $cc_email;
                }
            } else {
                $header_data                                  = explode(':', str_replace(' ', '', strtolower($header)));
                $formatted_data['headers'][ $header_data[0] ] = $header_data[1] ?? '';
            }
        }
        // Set the default from header
        $formatted_data['headers']['from'] = $sender_title . ' <' . $formatted_data['sender'] . '>';

        // Set attachments
        $upload     = wp_upload_dir();
        $upload_dir = $upload['basedir'] . '/rtm-mail-attachments';
        // Create new dir directory if it doesn't exist
        if (! is_dir($upload_dir)) {
            mkdir($upload_dir, 0755);
        }

        // Loop through every attachment and add it to the formatted data
        $save_attachments  = apply_filters('rtm_mail_save_attachments', true);
        $exclude_name      = apply_filters('rtm_mail_exclude_attachment_name', []);
        $exclude_extension = apply_filters('rtm_mail_exclude_attachment_extension', []);
        foreach ($args['attachments'] as $attachment) {
            if ($save_attachments) {
                $date      = date('Y_m_d H_i_s', time());
                $file_name = substr($attachment, strrpos($attachment, '/'));
                $extension = explode('.', $file_name)[1];
                // Look if the file_name is a part of the exclude_name array
                if (in_array($file_name, $exclude_name) || in_array($extension, $exclude_extension)) {
                    $formatted_data['attachments'][] = $attachment;
                    continue;
                }
                // new file directory and name based on date so its unique (rtm-mail/attachments/filename-YYYY_mm_dd_hh_mm_ss.ext)
                $new_file = $upload_dir . str_replace('.' . $extension, '', $file_name) . '-' . str_replace(' ', '', $date) . '.' . $extension;
                if (copy($attachment, $new_file)) {
                    $formatted_data['attachments'][] = $new_file;
                } else {
                    printf(__('WP Mail Logger FATAL ERROR: Couldn\'t copy file %s to directory. Attachment is not added to logged mail', 'rtm-mail'), $new_file);
                }
            } else {
                $formatted_data['attachments'][] = $attachment;
            }
        }

        return $formatted_data;
    }

    public static function mailError(WP_Error $error): void
    {
        if (!empty($error->error_data['wp_mail_failed']['headers'])) {
            $log_id = (int) $error->error_data['wp_mail_failed']['headers']['Id'];
            LogHelper::update($log_id, ['status' => 'failed', 'sent' => null]);
            Logger::error(
                "Failed to send email (#{$log_id})",
                "The email failed to be sent because of error: {$error->errors['wp_mail_failed'][0]}",
                'CustomMailer::mailError',
                'Mailing',
                [
                    "mail_log" => $log_id,
                    "error"    => $error->errors['wp_mail_failed'][0],
                    "user"     => LoggerUtils::getUser(),
                ]
            );
        }
    }
    public static function sendCaughtLogs(): void
    {
        // Get all caught mail logs
        $caught_logs = LogHelper::get([
            'post_per_page' => null,
            'where'         => [
                'status' => [
                    'type'  => '=',
                    'value' => 'caught',
                ]
            ]
        ]);

        // Send every caught mail log
        if (isset($caught_logs[0])) {
            foreach ($caught_logs as $log) {
                self::sendMail($log['id']);
            }
        }
    }
    public function setReadRequest(&$wp): void
    {
        if (isset($wp->query_vars['rtm_mail_read'])) {
            set_query_var('log_id', $wp->query_vars['log_id']);
            set_query_var('address', $wp->query_vars['address']);
            $log_id  = get_query_var('log_id', null);
            $address = get_query_var('address', null);
            if ($log_id !== null && $address !== null) {
                $mail_log = LogHelper::get([
                    'post_per_page' => null,
                    'where'         => [
                        'id' => [
                            'type'  => '=',
                            'value' => $log_id,
                        ]
                    ]
                ]);
                if (isset($mail_log[0])) {
                    $receiver_status = $mail_log[0]['receiver_status'];
                    foreach ($receiver_status as $status) {
                        if (isset($receiver_status[ $address ]) && ! $status['opened']) {
                            $receiver_status[ $address ]['opened'] = true;
                        }
                    }
                    LogHelper::update($log_id, [
                        'receiver_status' => json_encode($receiver_status),
                    ]);
                }
            }
        }
    }

    public static function getTrackUrl($log_id, $address): string
    {
        return home_url() . '/trackmail/' . $log_id . '/' . $address;
    }

    /**
     * get_backtrace - trace back function calls until the catch_mail call
     * @return array
     */

    public static function getBacktrace(): array
    {
        $result = [];
        $trace  = array_reverse(debug_backtrace());
        array_pop($trace); // remove the last call from the trace
        // Loop through every line and add it to the result
        foreach ($trace as $trace_line) {
            if (! isset($trace_line['class']) || $trace_line['class'] !== 'WP_Hook') {
                $call = isset($trace_line['class']) ? $trace_line['class'] . $trace_line['type'] . $trace_line['function'] : $trace_line['function'];
                if (strpos($call, 'require') === false && strpos($call, 'include') === false) {
                    if (isset($trace_line['line']) && isset($trace_line['file'])) {
                        $result[] = [
                            'call' => $call,
                            'line' => $trace_line['line'],
                            'file' => $trace_line['file']
                        ];
                    }
                }
            }
        }
        return $result;
    }
}
