<?php

namespace FluentMail\App\Services\Mailer;

use Exception;
use InvalidArgumentException;
use FluentMail\App\Models\Logger;
use FluentMail\Includes\Support\Arr;
use FluentMail\Includes\Core\Application;
use FluentMail\App\Services\Mailer\Manager;
use FluentMail\App\Services\Mailer\ValidatorTrait;

class BaseHandler
{
    use ValidatorTrait;

    protected $app = null;
    
    protected $params = [];

    protected $manager = null;
    
    protected $phpMailer = null;

    protected $settings = [];

    protected $attributes = [];

    protected $response = null;

    protected $existing_row_id = null;

    public function __construct(?Application $app = null, ?Manager $manager = null)
    {
        $this->app = $app ?: fluentMail();
        $this->manager = $manager ?: fluentMail(Manager::class);
    }

    public function setPhpMailer($phpMailer)
    {
        $this->phpMailer = $phpMailer;

        if(!$this->phpMailer->CharSet) {
            $this->phpMailer->CharSet = 'UTF-8';
        }

        return $this;
    }

    public function setSettings($settings)
    {

        $this->settings = $settings;

        return $this;
    }

    protected function preSend()
    {
        $this->attributes = [];
        
        if ($this->isForced('from_name')) {
            $this->phpMailer->FromName = $this->getSetting('sender_name');
        }

        if ($this->getSetting('return_path') == 'yes') {
            $this->phpMailer->Sender = $this->phpMailer->From;
        }

        $this->attributes = $this->setAttributes();

        return true;
    }

    protected function isForced($key)
    {
        return $this->getSetting("force_{$key}") == 'yes';
    }

    public function isForcedEmail()
    {
        return $this->getSetting("force_from_email") != 'no';
    }

    public function isActive()
    {
        return $this->getSetting('is_active') == 'yes';
    }

    protected function getDefaultParams()
    {
        $timeout = (int)ini_get('max_execution_time');

        return [
            'timeout'     => $timeout ?: 30,
            'httpversion' => '1.1',
            'blocking'    => true,
        ];
    }

    protected function setAttributes()
    {
        $from = $this->setFrom();
        
        $replyTos = $this->setRecipientsArray(array_values(
            $this->phpMailer->getReplyToAddresses()
        ));
        
        $contentType = $this->phpMailer->ContentType;
        
        $customHeaders = $this->setFormattedCustomHeaders();

        $recipients = [
            'to' => $this->setRecipientsArray($this->phpMailer->getToAddresses()),
            'cc' => $this->setRecipientsArray($this->phpMailer->getCcAddresses()),
            'bcc' => $this->setRecipientsArray($this->phpMailer->getBccAddresses())
        ];

        return array_merge($this->attributes, [
            'from' => $from,
            'to' => $recipients['to'],
            'subject' => $this->phpMailer->Subject,
            'message' => $this->phpMailer->Body,
            'alt_body' => $this->phpMailer->AltBody,
            'attachments' => $this->phpMailer->getAttachments(),
            'custom_headers' => $customHeaders,
            'headers' => [
                'reply-to' => $replyTos,
                'cc' => $recipients['cc'],
                'bcc' => $recipients['bcc'],
                'content-type' => $contentType
            ]
        ]);
    }

    protected function setFrom()
    {
        $name = $this->getSetting('sender_name');
        $email = $this->getSetting('sender_email');
        $overrideName = $this->getSetting('force_from_name');

        if ($name && ($overrideName == 'yes' || $this->phpMailer->FromName == 'WordPress')) {
            $this->attributes['sender_name'] = $name;
            $this->attributes['sender_email'] = $email;
            $from = $name . ' <' . $email . '>';
        } elseif ($this->phpMailer->FromName) {
            $this->attributes['sender_email'] = $email;
            $this->attributes['sender_name'] = $this->phpMailer->FromName;
            $from = $this->phpMailer->FromName . ' <' . $email . '>';
        } else {
            $from = $this->attributes['sender_email'] = $email;
        }

        return $from;
    }

    protected function setRecipientsArray($array)
    {
        $recipients = [];

        foreach ($array as $key => $recipient) {
            $recipient = array_filter($recipient);

            if (!$recipient) continue;
            
            $recipients[$key] = [
                'email' => array_shift($recipient)
            ];

            if ($recipient) {
                $recipients[$key]['name'] = array_shift($recipient);
            }
        }

        return $recipients;
    }

    protected function setFormattedCustomHeaders()
    {
        $headers = [];

        $customHeaders = $this->phpMailer->getCustomHeaders();

        foreach ($customHeaders as $key => $header) {
            if ($header[0] == 'Return-Path') {
                if ($this->getSetting('options.return_path') == 'no') {
                    if (!empty($header[1])) {
                        $this->phpMailer->Sender = $header[1];
                    }
                }
                unset($customHeaders[$key]);
            } else {
                $headers[] = [
                    'key' => $header[0],
                    'value' => $header[1]
                ];
            }
        }

        $this->phpMailer->clearCustomHeaders();

        foreach ($customHeaders as $customHeader) {
            $this->phpMailer->addCustomHeader($customHeader[0], $customHeader[1]);
        }

        return $headers;
    }

    public function getSetting($key = null, $default = null)
    {
        try {
            return $key ? Arr::get($this->settings, $key, $default) : $this->settings;
        } catch (Exception $e) {
            return $default;
        }
    }

    protected function getParam($key = null, $default = null)
    {
        try {
            return $key ? Arr::get($this->attributes, $key, $default) : $this->attributes;
        } catch (Exception $e) {
            return $default;
        }
    }

    protected function getHeader($key, $default = null)
    {
        try {
            return Arr::get(
                $this->attributes['headers'], $key, $default
            );
        } catch (Exception $e) {
            return $default;
        }
    }

    public function getSubject()
    {
        $subject = '';

        if (isset($this->attributes['subject'])) {
            $subject = $this->attributes['subject'];
        }

        return $subject;
    }

    protected function getExtraParams()
    {
        $this->attributes['extra']['provider'] = $this->getSetting('provider');

        return $this->attributes['extra'];
    }

    public function handleResponse($response)
    {
        if ( is_wp_error($response) ) {
            $code = $response->get_error_code();

            if (!is_numeric($code)) {
                $code = 400;
            }

            $message = $response->get_error_message();

            $errorResponse = [
                'code'    => $code,
                'message' => $message,
                'errors'  => $response->get_error_data()
            ];

            $status = $this->processResponse($errorResponse, false);

            if ( !$status ) {
                throw new \PHPMailer\PHPMailer\Exception($message, $code); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
            } else {
                return $status;
            }

        } else {
            return $this->processResponse($response, true);
        }
    }

    public function processResponse($response, $status)
    {
        if ($this->shouldBeLogged($status)) {
            $data = [
                'to' => $this->serialize($this->attributes['to']),
                'from' => $this->attributes['from'],
                'subject' => sanitize_text_field($this->attributes['subject']),
                'body' => $this->attributes['message'],
                'attachments' => $this->serialize($this->attributes['attachments']),
                'status'   => $status ? 'sent' : 'failed',
                'response' => $this->serialize($response),
                'headers'  => $this->serialize($this->getParam('headers')),
                'extra'    => $this->serialize($this->getExtraParams())
            ];

            if($this->existing_row_id) {
                $row = (new Logger())->find($this->existing_row_id);
                if($row) {
                    $row['response'] = (array) $row['response'];
                    if($status) {
                        $row['response']['fallback'] = __('Sent using fallback connection ', 'fluent-smtp') . $this->attributes['from'];
                        $row['response']['fallback_response'] = $response;
                    } else {
                        $row['response']['fallback'] = __('Tried to send using fallback but failed. ', 'fluent-smtp') . $this->attributes['from'];
                        $row['response']['fallback_response'] = $response;
                    }

                    $data['response'] = $this->serialize( $row['response']);
                    $data['retries'] = $row['retries'] + 1;
                    (new Logger())->updateLog($data, ['id' => $row['id']]);

                    if(!$status) {
                        do_action('fluentmail_email_sending_failed_no_fallback', $row['id'], $this, $data);
                    }
                }
            } else {
                $logId = (new Logger)->add($data);
                if(!$status) {
                    // We have to fire an action for this failed job
                    $status = apply_filters('fluentmail_email_sending_failed', $status, $logId, $this, $data);
                }
            }
        }

        return $status;
    }

    protected function shouldBeLogged($status)
    {
        if($this->existing_row_id) {
            return true;
        }
        if (defined('FLUENTMAIL_LOG_OFF') && FLUENTMAIL_LOG_OFF) {
            return false;
        }

        if (!$status) {
            return true;
        }

        $miscSettings = $this->manager->getConfig('misc');
        $isLogOn = $miscSettings['log_emails'] == 'yes';

        return apply_filters('fluentmail_will_log_email', $isLogOn, $miscSettings, $this);
    }

    protected function serialize(array $data)
    {
        foreach ($data as $key => $item) {

            if (is_array($item)) {
                $this->serialize($item);
            }

            if (is_object($item) || is_resource($item)) {
                throw new InvalidArgumentException(
                    "Invalid Data: Array cannot contain an object or resource."
                );
            }

            if (is_string($item)) {
                if (is_serialized($item)) {
                    throw new InvalidArgumentException(
                        "Invalid Data: Array cannot contain serialized data."
                    );
                }

                if (filter_var($item, FILTER_VALIDATE_EMAIL)) {
                    $data[$key] = sanitize_email($item);
                } elseif (filter_var($item, FILTER_VALIDATE_URL)) {
                    $data[$key] = esc_url_raw($item);
                } else {
                    $data[$key] = sanitize_text_field($item);
                }
            }
        }

        return serialize($data);
    }

    protected function fireWPMailFailedAction($data)
    {
        $code = is_numeric($data['code']) ? $data['code'] : 400;
        $code = strlen($code) < 3 ? 400 : $code;

        $mail_error_data['phpmailer_exception_code'] = $code;
        $mail_error_data['errors'] = $data['errors'];

        $error = new \WP_Error(
            $code, $data['message'], $mail_error_data
        );

        $this->app->doAction('wp_mail_failed', $error);
    }

    protected function updatedLog($id, $data)
    {
        try {
            $data['updated_at'] = current_time('mysql');
            (new Logger)->updateLog($data, ['id' => $id]);
        } catch (Exception $e) {
            error_log($e->getMessage());
        }
    }

    public function getValidSenders($connection)
    {
        return [$connection['sender_email']];
    }

    public function checkConnection($connection)
    {
        return true;
    }

    public function getConnectionInfo($connection)
    {
        return [
            'info' => (string) fluentMail('view')->make('admin.general_connection_info', [
                'connection' => $connection
            ])
        ];
    }

    public function getPhpMailer()
    {
        return $this->phpMailer;
    }

    public function setRowId($id)
    {
        $this->existing_row_id = $id;
    }

    public function addNewSenderEmail($connection, $email)
    {
        return new \WP_Error('not_implemented', __('Not implemented', 'fluent-smtp'));
    }

    public function removeSenderEmail($connection, $email)
    {
        return new \WP_Error('not_implemented', __('Not implemented', 'fluent-smtp'));
    }

}
