<?php

namespace RTMCustomMails\Schedule;

use ActionScheduler_Action;
use ActionScheduler_Store;
use DateTime;
use DateTimeInterface;
use RTMCustomMails\Email\RTMCustomEmail;
use RTMCustomMails\Util;
use Throwable;
use WC_Abstract_Order;
use WC_Email;
use WC_Emails;
use WC_Order;
use WC_Order_Refund;
use WordpressModels\Traits\SingletonTrait;

/**
 *
 */
class ScheduledEmails
{

    use SingletonTrait;

    private int $currentActionId = -1;

    private function __construct()
    {
        add_action('action_scheduler_before_execute', [$this, 'setActionId']);
        add_action('action_scheduler_after_execute', [$this, 'unsetActionId']);
        add_action('action_scheduler_before_execute', [$this, 'loadWcMailerOnScheduledAction'], 11);

        add_action('wrce_schedule_email', [self::class, 'scheduleEmail'], 10, 3);

        add_action('wrce_send_scheduled_email', [$this, 'sendScheduledEmail'], 10, 2);

        add_filter('wrce_scheduled_emails_textualize_args_new_order', [self::class, 'textualizeOrders']);
        add_filter('wrce_scheduled_emails_textualize_args_cancelled_order', [self::class, 'textualizeOrders']);
        add_filter('wrce_scheduled_emails_textualize_args_failed_order', [self::class, 'textualizeOrders']);
        add_filter('wrce_scheduled_emails_textualize_args_customer_on_hold_order', [self::class, 'textualizeOrders']);
        add_filter('wrce_scheduled_emails_textualize_args_customer_processing_order', [self::class, 'textualizeOrders']);
        add_filter('wrce_scheduled_emails_textualize_args_customer_completed_order', [self::class, 'textualizeOrders']);
        add_filter('wrce_scheduled_emails_textualize_args_customer_refunded_order', [self::class, 'textualizeOrders']);
        add_filter('wrce_scheduled_emails_textualize_args_customer_invoice', [self::class, 'textualizeOrders']);
        add_filter('wrce_scheduled_emails_textualize_args_customer_note', [self::class, 'textualizeOrders']);
        add_filter('wrce_scheduled_emails_textualize_args_customer_new_order', [self::class, 'textualizeOrders']);
        add_filter('wrce_scheduled_emails_textualize_args_customer_bank_reminder', [self::class, 'textualizeOrders']);
        add_filter('wrce_scheduled_emails_textualize_args_customer_on_hold_order_no_pdf', [self::class, 'textualizeOrders']);
        add_filter('wrce_scheduled_emails_textualize_args_customer_processing_order_no_pdf', [self::class, 'textualizeOrders']);
    }

    /**
     * @return WC_Email[]
     */
    public static function getSchedulableEmails(): array
    {
        $classNames = apply_filters('wrce_scheduled_emails', []);
        $emails = WC_Emails::instance()->get_emails();
        return array_reduce($classNames, function ($c, $clz) use ($emails) {
            if ($email = ($emails[$clz] ?? false)) {
                $c[$email->id] = $email;
            }
            return $c;
        }, []);
    }

    /**
     * @param $args_array
     * @return array
     */
    public static function textualizeOrders($args_array): array
    {
        $texts = [];
        foreach ($args_array as $args) {
            $order = wc_get_order(current($args));
            $texts[] = sprintf("Order <a href=\"%s\">%s</a>", get_edit_post_link($order->get_id()), $order->get_order_number());
        }

        return $texts;
    }

    /**
     * @param string $emailId
     * @param WC_Abstract_Order|numeric $o
     * @return int|string|null
     */
    public static function unscheduleOrderEmail(string $emailId, $o): int|string|null
    {
        if (!$o) {
            return null;
        }
        $unscheduleEmail = self::unscheduleEmail($emailId, ['order_id' => is_numeric($o) ? $o : $o->get_id()]);
        if ($unscheduleEmail) {
            $emailTitle = Util::getEmailById($emailId)->get_title();

            wc_get_order($o)->add_order_note("Email <strong>$emailTitle</strong> was unscheduled.");
        }

        return $unscheduleEmail;
    }


    /**
     * Trigger function for 'wrce_send_scheduled_email'.
     * @param string $email_id
     * @param array $args
     * @return void
     */
    public function sendScheduledEmail($args, $email_id): void
    {
        $email = Util::getEmailById($email_id);

        if (method_exists($email, 'trigger')) {
            call_user_func_array([$email, 'trigger'], $args);
        } else {
            // todo: log invalid email trigger
        }
    }

    /**
     * Get ActionScheduler actions for the `wrce_send_scheduled_email` hook of the given email and args
     *
     * @param $email
     * @param $args
     * @param $status
     * @return ActionScheduler_Action[]
     */
    public static function getScheduledEmailActions($email, $args = [], $status = ActionScheduler_Store::STATUS_PENDING): array
    {
        if ($email instanceof WC_Email) {
            $email = $email->id;
        }

        $actionArgs = ['hook' => 'wrce_send_scheduled_email', 'status' => $status];

        ksort($args);
        $actionArgs['args'] = ['args' => $args, 'email_id' => $email];

        return as_get_scheduled_actions($actionArgs);
    }

    /**
     * Get ActionScheduler actions for the `wrce_send_scheduled_email` hook of the given email and args
     *
     * @param $email
     * @param $order
     * @param string $status
     * @return ActionScheduler_Action[]
     */
    public static function getScheduledOrderEmailActions($email, $order, $status = ActionScheduler_Store::STATUS_PENDING): array
    {
        if ($email instanceof WC_Email) {
            $email = $email->id;
        }

        $actionArgs = ['hook' => 'wrce_send_scheduled_email', 'status' => $status];

        $actionArgs['args'] = [
            'args' => [
                'order_id' => is_numeric($order) ? $order : $order->get_id()
            ],
            'email_id' => $email
        ];

        return as_get_scheduled_actions($actionArgs);
    }

    /**
     * @param $order
     * @param $status
     * @return ActionScheduler_Action[]
     */
    public static function getScheduledEmailActionsForOrder($order, $status = ActionScheduler_Store::STATUS_PENDING): array
    {
        $actions = [];
        foreach (self::getSchedulableEmails() as $id => $email) {
            $actions = array_merge($actions, self::getScheduledOrderEmailActions($id, $order, $status));
        }

        return $actions;
    }

    /**
     * @param string|WC_Email $email
     * @param array $args
     * @return int|string|null
     */
    public static function unscheduleEmail($email, $args): int|string|null
    {
        $email = is_string($email) ? $email : $email->id;
        ksort($args);

        return as_unschedule_action('wrce_send_scheduled_email', ['args' => $args, 'email_id' => $email]);
    }

    /**
     * Schedule an email for a specific time.
     * @param string|WC_Email $email
     * @param array $args
     * @param int|string|DateTime $time
     * @return int
     */
    public static function scheduleEmail($email, $args, $time, $group = 'wrce_scheduled_mail'): int
    {
        // ensure wc_emails is loaded
        WC_Emails::instance();

        $email = Util::getEmailById($email);

        if (!$email instanceof RTMCustomEmail || !method_exists($email, 'trigger')) {
            // todo: log invalid
            return -1;
        }

        // sort keys to ensure arg search is correct
        // call_user_func_array works on array keys in >=8.0, but not <8.0
        ksort($args);

        $queryArgs = ['args' => $args, 'email_id' => $email->id];
        if (as_get_scheduled_actions([
            'hook' => 'wrce_send_scheduled_email',
            'status' => [ActionScheduler_Store::STATUS_PENDING, ActionScheduler_Store::STATUS_RUNNING, ActionScheduler_Store::STATUS_COMPLETE],
            'args' => $queryArgs,
            'group' => $group
        ])) {
            // todo: log email already scheduled
            return 0;
        }

        if (is_int($time)) {
            $time = new DateTime("@$time");
        } elseif (is_string($time)) {
            try {
                $time = new DateTime($time);
            } catch (Throwable $exception) {
                $time = null;
            }
        }

        if (!$time instanceof DateTime) {
            // todo: log invalid time
            return -1;
        } elseif (!is_array($args)) {
            // todo: log invalid args
            return -1;
        }
        // ensure sorted args
        ksort($args);

        $actionId = as_schedule_single_action($time->getTimestamp(), 'wrce_send_scheduled_email', $queryArgs, $group);

        if ($actionId === 0) {
            // todo: log scheduling failed.
        } else {
            // todo: log scheduling succeeded
        }

        return $actionId;
    }

    /**
     * @param string $emailId
     * @param WC_Order|WC_Order_Refund|WC_Abstract_Order $order
     * @param DateTimeInterface $scheduleDate
     * @return int|mixed
     */
    public static function scheduleOrderEmail(string $emailId, $order, DateTimeInterface $scheduleDate): mixed
    {
        $email = Util::getEmailById($emailId);
        if (!$email) {
            return 0;
        }

        $actionId = ScheduledEmails::scheduleEmail($emailId,
            ['order_id' => $order->get_id()],
            $scheduleDate->getTimestamp());

        if ($order instanceof WC_Order) {
            if ($actionId > 0) {
                $order->add_order_note("Email <strong>\"{$email->get_title()}\"</strong> was scheduled for " . $scheduleDate->format('Y-m-d H:i:s') . ' to ' . $order->get_billing_email() . '.');
            } elseif ($actionId === -1) {
                $order->add_order_note("Scheduling <strong>\"{$email->get_title()}\"</strong> for " . $scheduleDate->format('Y-m-d H:i:s') . ' failed.');
            } else { // actionId is 0 or less than -1
                // already scheduled/completed
            }
        } else {
            error_log('[WRCE] Error scheduling email for order ' . $order->get_id() . ', order already refunded.');
        }

        return $actionId;
    }

    public static function doScheduleEmails(): void
    {
        $emails = WC_Emails::instance()->get_emails();
        $type_arguments = apply_filters('wrce_get_scheduled_emails_and_args', []);

        foreach ($type_arguments as $type => $args) {
            if (!($email_instance = $emails[$type])) {
                continue;
            }
            foreach ($args as [$arg, $date]) {
                if (is_array($arg)) {
                    do_action('wrce_schedule_email', $email_instance, $arg, $date);
                }
            }
        }
    }

    /**
     * @return int
     */
    public function getCurrentActionId(): int
    {
        return $this->currentActionId;
    }

    /**
     * @param $actionId
     * @return void
     */
    public function loadWcMailerOnScheduledAction($actionId): void
    {
        $init_for_hooks = ['wrce_send_scheduled_email'];

        $action = ActionScheduler_Store::instance()->fetch_action($actionId);

        if (in_array($action->get_hook(), $init_for_hooks)) {
            WC()->mailer();
        }
    }

    /**
     * @param $actionId
     * @return void
     */
    public function setActionId($actionId): void
    {
        $this->currentActionId = $actionId;
    }

    /**
     * @param $actionId
     * @return void
     */
    public function unsetActionId($actionId): void
    {
        unset($this->currentActionId);
    }

}