<?php

namespace RtmMail;

use PHPMailer\PHPMailer\PHPMailer;
use RtmMail\Helpers\LogHelper;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Client;

/**
 * Catcher - handles every incoming mail from the wp_mail hook
 */
class Catcher
{
    /**
     * catch_email - wp_mail hook callback to log all emails
     * @param $args
     * @return array (phpmailer)
     */
    public function catch_mail($args)
    {
        // if there is no Log_id in the headers it has to be logged
        if (empty($args['headers']) || substr($args["headers"][0], 0, 2) !== 'Id') {
            $settings = get_option('rtm_mail_settings');
            // Convert "to" to an array when it's a string
            if (is_string($args['to'])) {
                // Replace commas in the strings with semicolons
                $args['to'] = str_replace(',', ';', $args['to']);
                // Set every email in a new recipients array
                $recipients = [];
                foreach (explode(';', $args['to']) as $recipient) {
                    $recipients[] = str_replace(' ', '', $recipient);
                }
                // Set the recipients array to the new "to" arg
                $args['to'] = $recipients;
            }
            $args['headers'] = $args['headers'] ?? [];
            $args['headers'] = is_string($args['headers']) ? explode("\n", str_replace("\\r\\n", "\n", $args['headers'])) : $args['headers'];
            $args['backtrace'] = $this->get_backtrace();

            $settings['addresses'] = $settings['addresses'] ?? [];
            // Add every email from setting to receiver/cc/bcc
            foreach ($settings['addresses'] as $address) {
                $email_address = $address['address'];
                $email_type = $address['type'];
                if (!empty($email_address)) {
                    // If the type is a recipient, add it to "to" otherwise add it to the "headers"
                    if ($email_type === 'recipient') {
                        $args['to'][] = $email_address;
                    } else {
                        $args['headers'][] = ucfirst($email_type) . ': ' . $email_address;
                    }
                }
            }

            // format the mail data
            $mail_data = $this->format($args);

            // Save the mail data as new log item
            $log_id = LogHelper::save($mail_data);

            // Log caught mail
            do_action('rtmmail_mail_caught', $log_id);

            // Check if email has to be sent or not
            $block_mails = $settings['block_mails'] ?? false;
            if ($block_mails) {
                // Set empty receivers so the email won't be sent
                $args['to'] = [];
                $args['headers'] = [];
            } else {
                $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) {
                    self::send_smtp($log_id, $mail_data);
                } else {
                    LogHelper::update($log_id, ['status' => 'sent', 'sent' => date('Y-m-d H:i:s', time())]);
                    // Set the Id of the mail log in header
                    array_unshift($args['headers'], "Id: " . $log_id);
                }
            }
        }

        return $args;
    }

    /**
     * mail_error - handles error on mail failure
     * @param $error
     */
    public function mail_error($error)
    {
        // Check if there are headers available, so you can retrieve the Id
        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]);
            self::send_text_message($log_id);
            do_action('rtmmail_send_failed', $log_id, $error->errors['wp_mail_failed'][0]);
        }
    }

    /**
     * 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 send_mail($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;
        }

        $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) {
            return self::send_smtp($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;
                    }
                }
            }
            // Loop over every to mail address
            if ($single_address === null) {
                // set success and receiver status array
                $success = [];
                $receiver_status = [];
                foreach ($mail_data['to'] as $to_address) {
                    // Add tracking link to message
                    $mail_data['message'] .= '<img src="' . self::get_track_url($log_id, $to_address)  . '" style="display:none;" />';
                    $mail_sent = wp_mail($to_address, $mail_data['subject'], $mail_data['message'], $mail_data['headers'], $mail_data['attachments']);
                    $receiver_status[$to_address] = ['opened' => false, 'sent' => $mail_sent];
                    $success[] = $mail_sent;
                }
                LogHelper::update($log_id, ['receiver_status' => json_encode($receiver_status)]);

                // Check if sending was successful
                if (in_array(true, $success, true)) {
                    // Set status on sent and redirect to the log details page
                    LogHelper::update($log_id, ['status' => 'sent', 'sent' => date('Y-m-d H:i:s', time())]);
                    do_action('rtmmail_send_success', $log_id);
                    return 'success';
                } else {
                    // Set the status to failed and redirect to the log details page
                    LogHelper::update($log_id, ['status' => 'failed', 'sent' => null]);
                    return __('Failed sending email, check the debug logs for more info!', 'rtm-mail');
                }
            } else {
                $mail_data['message'] .= '<img src="' . self::get_track_url($log_id, $single_address)  . '" style="display:none;" />';
                $mail_sent = wp_mail($single_address, $mail_data['subject'], $mail_data['message'], $mail_data['headers'], $mail_data['attachments']);
                $mail_data['receiver_status'][$single_address] = ['opened' => false, 'sent' => $mail_sent];
                LogHelper::update($log_id, ['receiver_status' => json_encode($mail_data['receiver_status'])]);
                if ($mail_sent) {
                    return 'success';
                } else {
                    return __('Failed sending email, check the debug logs for more info!', 'rtm-mail');
                }
            }
        }
    }

    private static function send_smtp($log_id, $mail_data, $single_address = null)
    {
        $settings = get_option('rtm_mail_settings');
        $sender_options = $settings['sender_options'] ?? null;

        $sender_mail = ($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');

        $encryption_key = get_option('rtm_mail_smtp_key', false);
        $smtp_settings = get_option('rtm_mail_smtp_settings');
        $smtp_enabled = $smtp_settings['smtp_enabled'] ?? false;

        if (!$smtp_enabled) {
            return __('SMTP is not enabled');
        }

        $host = $smtp_settings['host'] ?? '';
        $encryption = $smtp_settings['encryption'] ?? 'none';
        $port = $smtp_settings['port'] ?? 0;
        $authentication = $smtp_settings['authentication'] ?? true;
        $username = $smtp_settings['username'] ?? '';
        $password = isset($smtp_settings['password']) ? Cryptor::Decrypt($smtp_settings['password'], $encryption_key) : '';

        // Include the PHPMailer class
        require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php';
        require_once ABSPATH . WPINC . '/PHPMailer/SMTP.php';
        require_once ABSPATH . WPINC . '/PHPMailer/Exception.php';
        $mail = new PHPMailer(true);

        $mail->CharSet = get_bloginfo('charset');
        $mail->IsSMTP();

        // Set content type
        $mail->ContentType = $mail_data['headers']['content-type'];
        $mail->IsHTML($mail_data['headers']['content-type'] === 'text/html');

        // Set SMTP Host and Port
        $mail->Host = $host;
        $mail->Port = $port;

        // Set encryption type
        if ($encryption !== 'none') {
            $mail->SMTPSecure = $encryption;
        }

        // Check and set authentication
        if ($authentication) {
            $mail->SMTPAuth = true;
            $mail->Username = $username;
            $mail->Password = $password;
        }

        // Set success and receiver status array
        $success = [];
        $receiver_status = $mail_data['receiver_status'];

        if ($single_address === null) {
            // Set all to addresses
            foreach ($mail_data['receiver'] as $to_address) {
                if (!empty($to_address)) {
                    try {
                        // Set from address
                        $mail->SetFrom($sender_mail, $sender_title);
                        // Add to address
                        $mail->AddAddress($to_address);

                        // Set all cc addresses
                        foreach($mail_data['cc'] as $cc_address) {
                            if (!empty($cc_address)) {
                                $mail->AddCC($cc_address);
                            }
                        }
                        // Set all bcc addresses
                        foreach($mail_data['bcc'] as $bcc_address) {
                            if (!empty($bcc_address)) {
                                $mail->AddBcc($bcc_address);
                            }
                        }
                        // Add tracking link to message
                        $mail_data['body'] .= '<img src="' . self::get_track_url($log_id, $to_address)  . '" style="display:none;" />';
                        // Set body and subject
                        $mail->Subject = $mail_data['subject'];
                        $mail->Body = $mail_data['body'];

                        // Set attachments
                        foreach ($mail_data['attachments'] as $attachment) {
                            $mail->addAttachment($attachment, basename($attachment));
                        }

                        // Set timeout
                        $mail->Timeout = 10;

                        $mail->Send();

                        $mail->ClearAllRecipients();

                        $receiver_status[$to_address] = ['opened' => false, 'sent' => true];
                        $success[] = true;
                    } catch(\Exception $ex) {
                        do_action('rtmmail_send_failed', $log_id, $mail->ErrorInfo);
                        $receiver_status[$to_address] = ['opened' => false, 'sent' => false];
                        $success[] = false;
                    }
                }
            }
        } else {
            try {
                // Set from address
                $mail->SetFrom($sender_mail, $sender_title);
                // Add to address
                $mail->AddAddress($single_address);

                // Set all cc addresses
                foreach($mail_data['cc'] as $cc_address) {
                    if (!empty($cc_address)) {
                        $mail->AddCC($cc_address);
                    }
                }
                // Set all bcc addresses
                foreach($mail_data['bcc'] as $bcc_address) {
                    if (!empty($bcc_address)) {
                        $mail->AddBcc($bcc_address);
                    }
                }
                // Add tracking link to message
                $mail_data['body'] .= '<img src="' . self::get_track_url($log_id, $single_address)  . '" style="display:none;" />';
                // Set body and subject
                $mail->Subject = $mail_data['subject'];
                $mail->Body = $mail_data['body'];

                // Set attachments
                foreach ($mail_data['attachments'] as $attachment) {
                    $mail->addAttachment($attachment, basename($attachment));
                }

                // Set timeout
                $mail->Timeout = 10;

                $mail->Send();

                $mail->ClearAllRecipients();

                $receiver_status[$single_address] = ['opened' => false, 'sent' => true];
                $success[] = true;
            } catch(\Exception $ex) {
                do_action('rtmmail_send_failed', $log_id, $mail->ErrorInfo);
                $receiver_status[$single_address] = ['opened' => false, 'sent' => false];
                $success[] = false;
            }
        }

        LogHelper::update($log_id, ['receiver_status' => json_encode($receiver_status)]);

        // Check if sending was successful
        if (in_array(true, $success, true)) {
            // Set status on sent and redirect to the log details page
            LogHelper::update($log_id, ['status' => 'sent', 'sent' => date('Y-m-d H:i:s', time())]);
            do_action('rtmmail_send_success', $log_id);
            return 'success';
        } else {
            // Set the status to failed and redirect to the log details page
            LogHelper::update($log_id, ['status' => 'failed', 'sent' => null]);
            do_action('rtmmail_send_failed', $log_id, $mail->ErrorInfo);
            self::send_text_message($log_id);
            return $mail->ErrorInfo;
        }
    }

    /**
     * send_caught_logs - cron hook callback for sending caught logs
     */
    public function send_caught_logs()
    {
        // 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::send_mail($log['id']);
            }
        }
    }

    /**
     * format - formats the mail data to save as a log item
     * @param $args
     * @return array (formatted mail data)
     */
    private function format($args)
    {
        $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)) {
                    $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
        foreach ($args['attachments'] as $attachment) {
            $date = date('Y_m_d H_i_s', time());
            $file_name = substr($attachment, strrpos($attachment, '/'));
            $extension = explode('.', $file_name)[1];
            // 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);
            }
        }

        return $formatted_data;
    }

    public function set_read_request(&$wp)
    {
        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),
                    ]);
                }
            }
        }
    }

    private static function get_track_url($log_id, $address)
    {
        return home_url() . '/trackmail/' . $log_id . '/' . $address;
    }

    /**
     * send_text_message - sends text message with the twilio API
     * @param $log_id
     */
    private static function send_text_message($log_id)
    {
        // Get all settings
        $settings = get_option('rtm_mail_settings');
        $sms_enabled = $settings['sms_enabled'] ?? false;
        $twilio_settings = $settings['twilio_settings'] ?? null;
        // Check if sms is enabled
        if ($sms_enabled && $twilio_settings !== null) {
            try {
                // Create new client object with twilio authentication settings
                $client = new Client($twilio_settings['sid'], $twilio_settings['auth']);
                $log_link = get_admin_url() . 'admin.php?page=rtm-mail-details&log_id=' . $log_id;
                try {
                    // Try to send the message to the specified phone number
                    $client->messages->create($twilio_settings['phone'], [
                        'from' => $twilio_settings['number'],
                        'body' => sprintf(__('There was an error sending a mail log (%s) caught by WP Mail Logger', 'rtm-mail'), $log_link),
                    ]);
                } catch (TwilioException $tex) {
                    do_action('rtmmail_text_message_failed', $log_id, $tex);
                }
            } catch (ConfigurationException $cex) {
                do_action('rtmmail_text_message_failed', $log_id, $cex);
            }
        }
    }

    /**
     * get_backtrace - trace back function calls until the catch_mail call
     * @return array
     */
    private function get_backtrace()
    {
        $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) {
                    $result[] = [
                        'call' => $call,
                        'line' => $trace_line['line'],
                        'file' => $trace_line['file']
                    ];
                }
            }
        }
        return $result;
    }
}
