<?php

namespace RTMCustomMails\Rest;

use ActionScheduler;
use RTMCustomMails\ConditionSchema\ConditionSchemaBuilder;
use RTMCustomMails\Email\RTMCustomEmail;
use RTMCustomMails\EmailSections\SectionValidation;
use RTMCustomMails\PDFInvoices\WoocommercePDFInvoices;
use RTMCustomMails\Preview\EmailObjects;
use RTMCustomMails\Preview\PreviewEmails;
use RTMCustomMails\Preview\PreviewTriggerArguments;
use RTMCustomMails\Rest\Model\EmailModel;
use RTMCustomMails\Rest\Model\ScheduledEmail;
use RTMCustomMails\Schedule\ScheduledEmails;
use RTMCustomMails\SerializerFactory;
use RTMCustomMails\Util;
use RTMMailComposer\Model\EmailSection;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Serializer\Serializer;
use WC_Emails;
use WordpressModels\Cache\CacheFactory;
use WordpressModels\Rest\AbstractRestApi;
use WordpressModels\Rest\Attribute\RestRoute;
use WP_Error;
use WP_Http;
use WP_REST_Request;
use WP_REST_Response;

/**
 * REST API Endpoints for modifying emails.
 */
#[RestRoute('email')]
class EmailApi extends AbstractRestApi
{

    const ARGS_EMAIL_SECTION = [
        'id' => [
            'required' => true,
            'type' => 'string'
        ],
        'section' => [
            'type' => 'string'
        ],
        'sort-order' => [
            'required' => true,
            'type' => 'integer'
        ],
        'title' => [
            'type' => 'string'
        ],
        'enable' => [
            'type' => 'boolean'
        ],
        'show-title' => [
            'type' => 'boolean'
        ],
        'condition' => [
            'type' => 'array',
        ],
        'text' => [
            'type' => 'string'
        ]
    ];

    private Serializer $serializer;
    private AdapterInterface $cache;

    public function __construct()
    {
        parent::__construct('wrce/v1');
        $this->serializer = SerializerFactory::create();
        $this->cache = (new CacheFactory('wrce'))->createCache(
            WP_CONTENT_DIR . '/cache/wrce',
            'wrce_rest',
            [
                'persistent_id' => 'wrce_' . WRCE_VERSION,
                'prefix_key' => 'wrce_rest'
            ]
        );
    }

    #[RestRoute(
        route: '',
        methods: ['GET'],
        permission: "role('administrator')",
        arguments: [],
        public: false
    )]
    public function listEmails(WP_REST_Request $request)
    {
        $expiration = 600;
        $item = $this->cache->getItem('email_types');
        if ($item->isHit()) {
            [$timestamp, $cachedValues] = $item->get();
            $modifiedSince = $request->get_header('If-Modified-Since');
            $isValidCache = $modifiedSince && $timestamp <= strtotime($modifiedSince);
            if ($isValidCache) {
                return new WP_REST_Response(status: WP_Http::NOT_MODIFIED);
            }

            return new WP_REST_Response($cachedValues, WP_Http::OK, $this->getCacheHeaders($timestamp, $expiration));
        }

        $emails = array_values(WC_Emails::instance()->get_emails());
        $normalizedEmails = $this->serializer->normalize(array_map([EmailModel::class, 'fromCustomEmail'], $emails));

        $timestamp = time();
        $item->set([$timestamp, $normalizedEmails]);
        $item->expiresAfter($expiration);
        $this->cache->save($item);

        return new WP_REST_Response($normalizedEmails, WP_Http::OK, $this->getCacheHeaders($timestamp, $expiration));
    }

    #[RestRoute(
        route: 'object-types',
        methods: ['GET'],
        permission: "role('administrator')",
        public: false
    )]
    public function getSupportedObjectTypes()
    {
        return EmailObjects::instance()->getSupported();
    }

    #[RestRoute(
        route: 'conditions',
        methods: ['GET'],
        permission: "role('administrator')",
        arguments: [
            'id' => [
                'required' => true,
                'type' => 'string',
            ]
        ],
        public: false
    )]
    public function getConditionFunctions(WP_REST_Request $request)
    {
        $emailId = $request->get_param('id');
        $email = Util::getEmailById($emailId);

        if (!$email) {
            return new WP_Error('email_not_found', 'Email not found', ['status' => 404]);
        }

        $item = $this->cache->getItem("email_condition_functions--$emailId");
        if ($item->isHit()) {
            [$timestamp, $cachedValues] = $item->get();
            $modifiedSince = $request->get_header('If-Modified-Since');
            $isValidCache = $modifiedSince && $timestamp <= strtotime($modifiedSince);
            if ($isValidCache) {
                return new WP_REST_Response(status: WP_Http::NOT_MODIFIED);
            }
        }

        $schemaBuilder = new ConditionSchemaBuilder();
        $schema = $schemaBuilder->getSchema($email);

        $timestamp = time();
        $item->set([$timestamp, $schema]);
        // one hour
        $expiration = 3600;
        $item->expiresAfter($expiration);
        $this->cache->save($item);

        return new WP_REST_Response($schema, WP_Http::OK, $this->getCacheHeaders($timestamp, $expiration));
    }

    #[RestRoute(
        route: 'preview',
        methods: ['GET'],
        permission: "role('administrator')",
        arguments: [
            'id' => [
                'required' => true,
                'type' => 'string',
            ],
            'objectId' => [
                'required' => true,
                'type' => 'number',
            ]
        ],
        public: false
    ),
    ]
    public function preview(WP_REST_Request $request): array
    {
        PreviewTriggerArguments::instance();

        $emailId = $request->get_param('id');
        $objectId = $request->get_param('objectId');

        $e = array_filter(WC_Emails::instance()->get_emails(), fn($e) => $e->id === $emailId);
        $key = current(array_keys($e));
        $email = RTMCustomEmail::asCustomEmail(current($e));
        $content = PreviewEmails::instance()->renderEmail($key, $objectId, $email);

        return [
            'content' => $content,
            'context' => $email->getRenderContext()
        ];
    }

    #[RestRoute(
        route: 'object-search',
        methods: ['GET'],
        permission: "role('administrator')",
        arguments: [
            'type' => [
                'required' => true,
                'type' => 'string'
            ],
            'search' => [
                'required' => true,
                'type' => 'string'
            ]
        ],
        public: false
    )]
    public function searchObject(WP_REST_Request $request)
    {
        $type = $request->get_param('type');
        $search = $request->get_param('search');

        return $this->serializer->normalize(apply_filters('wrce_preview_find_objects_' . $type, [], $search));
    }

    #[RestRoute(
        route: 'documents',
        methods: ['GET'],
        permission: "role('administrator')",
        arguments: [
            'id' => ['type' => 'string', 'required' => true]
        ],
        public: false
    )]
    public function getDocuments(WP_REST_Request $request)
    {
        $emailId = $request->get_param('id');
        $documents = WoocommercePDFInvoices::getAvailableDocumentsForEmail($emailId);
        return $this->serializer->normalize($documents);
    }

    #[RestRoute(
        route: 'toggle-document',
        methods: ['POST'],
        permission: 'role("administrator")',
        arguments: [
            'id' => [
                'required' => true,
                'type' => 'string'
            ],
            'document' => [
                'required' => true,
                'type' => 'string'
            ]
        ],
        public: false
    )]
    public function toggleDocument(WP_REST_Request $request): WP_Error|array
    {
        $emailId = $request->get_param('id');
        $document = $request->get_param('document');
//        $propable_slug = str_replace('-', '_', $document);
        $options = get_option('wpo_wcpdf_documents_settings_' . $document);
        if (!$options) {
            return new WP_Error("Could not find settings for document \"$document\".");
        }
        $attached = $options['attach_to_email_ids'] ?? [];

        if ($attached[$emailId] ?? false) {
            unset($attached[$emailId]);
        } else {
            $attached[$emailId] = '1';
        }

        $options['attach_to_email_ids'] = $attached;

        if (!update_option('wpo_wcpdf_documents_settings_' . $document, $options)) {
            return new WP_Error("Could not update settings for document \"$document\".");
        }

        return ['result' => true];
    }

    #[RestRoute(
        route: '(?P<id>[\d\w_-]+)',
        methods: ['PATCH'],
        permission: "role('administrator')",
        arguments: [
            'id' => [
                'type' => 'string',
                'required' => true
            ],
            'sections' => [
                'type' => 'array',
                'items' => [
                    'type' => 'object',
                ]
            ],
            'settings' => [
                'type' => 'object'
            ]
        ],
        public: false)]
    public function setSections(WP_REST_Request $request): array|WP_REST_Response
    {
        $emailId = $request->get_param('id');
        $sections = $request->get_param('sections');

        $sections = array_map(function ($section) {
            $section['text'] = trim($section['text']);
            $section['condition'] = $section['condition'] ?? [];
            return $this->serializer->denormalize($section, EmailSection::class);
        }, $sections);

        $errors = SectionValidation::instance()->validateEmailWithSections($emailId, $sections);
        if ($errors) {
            return new WP_REST_Response($errors, WP_Http::BAD_REQUEST);
        }

        $changes = $this->saveSections($sections, $emailId);

        $settings = $request->get_param('settings');
        $changes = $changes || $this->saveSettings($settings, $emailId);

        // invalidate caches
        $this->cache->deleteItem('email_types');

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

        return $this->serializer
            ->normalize(EmailModel::fromCustomEmail($email));
    }

    #[RestRoute(
        route: 'validate-template',
        methods: ['POST'],
        permission: "role('administrator')",
        arguments: [
            'id' => [
                'type' => 'string',
                'required' => true
            ],
            'sections' => [
                'type' => 'array',
                'required' => true
            ]
        ],
        public: false)]
    public function validateEmailSections(WP_REST_Request $request): array
    {
        $emailId = $request->get_param('id');
        $sections = $request->get_param('sections');
        $sections = array_map(fn($s) => $this->serializer->denormalize($s, EmailSection::class), $sections);

        if ($errors = SectionValidation::instance()->validateEmailWithSections($emailId, $sections)) {
            return [
                'result' => false,
                'errors' => $errors
            ];
        }

        return [
            'result' => true
        ];
    }

    #[RestRoute(
        route: 'export/(?P<id>[\d\w_-]+)',
        methods: ['GET'],
        permission: "role('administrator')",
        arguments: [
            'id' => [
                'type' => 'string',
                'required' => true
            ]
        ],
        public: false)]
    public function exportEmailSettings(WP_REST_Request $request)
    {
        $emailId = $request->get_param('id');
        if (!$email = Util::getEmailById($emailId)) {
            return new WP_REST_Response([
                'result' => false,
                'message' => 'Invalid Email ID'
            ], WP_Http::BAD_REQUEST);
        }

        $optionKey = $email->get_option_key();
        return get_option($optionKey);
    }

    #[RestRoute(
        route: 'import/(?P<id>[\d\w_-]+)',
        methods: ['POST'],
        permission: "role('administrator')",
        arguments: [
            'id' => [
                'type' => 'string',
                'required' => true
            ],
            'settings' => [
                'type' => 'object',
                'required' => true
            ]
        ],
        public: false)]
    public function importEmailSettings(WP_REST_Request $request): WP_REST_Response|array
    {
        $emailId = $request->get_param('id');
        $settings = $request->get_param('settings');

        if (!$email = Util::getEmailById($emailId)) {
            return new WP_REST_Response([
                'result' => false,
                'message' => 'Invalid Email ID'
            ], WP_Http::BAD_REQUEST);
        }

        $optionKey = $email->get_option_key();
        $options = update_option($optionKey, $settings);

        return ['result' => $options];
    }

    /**
     * Get the settings schema for a specific email type.
     * @param WP_REST_Request $request
     * @return object|WP_REST_Response
     */
    #[RestRoute(
        route: 'settings-schema',
        methods: ['GET'],
        permission: "role('administrator')",
        arguments: [
            'id' => [
                'type' => 'string',
                'required' => true
            ]
        ],
        public: false
    )]
    public function getSettingsSchema(WP_REST_Request $request)
    {
        $emailId = $request->get_param('id');

        // check cache
        $item = $this->cache->getItem("email_settings_schema--$emailId");
        if ($item->isHit()) {
            [$timestamp, $cachedValues] = $item->get();
            $modifiedSince = $request->get_header('If-Modified-Since');
            $isValidCache = $modifiedSince && $timestamp <= strtotime($modifiedSince);
            if ($isValidCache) {
                return new WP_REST_Response(status: WP_Http::NOT_MODIFIED);
            }
        }

        if (!$email = Util::getEmailById($emailId)) {
            return new WP_REST_Response([
                'result' => false,
                'message' => 'Invalid Email ID'
            ], WP_Http::BAD_REQUEST);
        }

        $data = (object)Util::getEmailSettingsSchema($email);
        $time = time();
        $expiration = 600;
        $item->expiresAfter($expiration);
        $item->set([$time, $data]);

        $this->cache->save($item);
        $this->cache->commit();

        return new WP_REST_Response($data, WP_Http::OK, $this->getCacheHeaders($time, $expiration));
    }

    #[RestRoute(
        route: 'builtin-sections',
        methods: ['GET'],
        permission: "role('administrator')",
        public: false
    )]
    public function getBuiltInSections(WP_REST_Request $request)
    {
        return array_keys(apply_filters('wrce_email_section_hooks', []));
    }

    /**
     * @return false|RTMCustomEmail
     */
    public function saveSettings(array $postedSettings, string $emailId): bool
    {
        // set booleans to yes/no
        $settings = array_map(fn($value) => is_bool($value) ? ($value ? 'yes' : null) : $value, $postedSettings);
        // save the settings
        $email = Util::getEmailById($emailId);

        $existingSettings = $email->settings;
        unset($existingSettings['sections']);

        $diff = array_diff_assoc_recursive($settings, $existingSettings);
        if (empty($diff)) {
            return false;
        }

        // convert setting keys to woocommerce format
        foreach ($settings as $key => $value) {
            $settings[$email->get_field_key($key)] = $value;
            unset($settings[$key]);
        }

        $email->set_post_data($settings);
        $email->process_admin_options();

        return true;
    }

    private function sanitizeSections(array $sections)
    {
        return array_map(function ($section) {
            $section['text'] = trim($section['text']);
            return $section;
        }, $sections);
    }

    /**
     * Check for changes in the sections and save them if they exist.
     *
     * @param EmailSection[] $sections
     * @param string $emailId
     * @return bool
     */
    private function saveSections(array $sections, mixed $emailId)
    {
        $email = Util::getEmailById($emailId);
        $existingSections = $email->getSections();

        // remap sort order in-order of array
        foreach ($sections as $index => $section) {
            $section->setSortOrder($index);
        }

        $normalizedSections = array_map([$this->serializer, 'normalize'], $sections);
        $normalizedExistingSections = array_map([$this->serializer, 'normalize'], $existingSections);
        $diff = array_diff_assoc_recursive($normalizedSections, $normalizedExistingSections);

        if ($diff) {
            $email->setSections($sections);
            return true;
        }

        return false;
    }

    #[RestRoute(
        route: 'schedule',
        methods: ['GET'],
        permission: "role('administrator')",
        arguments: [
            'page' => [
                'type' => 'integer',
                'default' => 0,
                'minimum' => 0
            ],
            'limit' => [
                'type' => 'integer',
                'default' => 20,
                'minimum' => 1,
                'maximum' => 100
            ],
            'filter' => [
                'type' => 'object',
                'default' => []
            ],
            'sort' => [
                'type' => 'object',
                'default' => [
                    'date' => 'ASC'
                ]
            ]
        ],
        public: false
    )]
    public function getScheduledEmails(WP_REST_Request $request)
    {
        $page = $request->get_param('page');
        $limit = $request->get_param('limit');
        $filter = $request->get_param('filter');
        $sort = $request->get_param('sort');

        $validFilters = [];
        $filterKeys = ['date', 'status'];
        foreach ($filter as $key => $value) {
            if (in_array($key, $filterKeys)) {
                $validFilters[$key] = $value;
            }
        }

        $sortKeys = ['date', 'none'];
        $sortKey = 'none';
        $sortDirection = 'DESC';
        foreach ($sort as $key => $value) {
            if (in_array($key, $sortKeys)) {
                $sortKey = $key;
                $sortDirection = $value === 'DESC' ?: 'ASC';
                break;
            }
        }

        if (isset($filter['emailId'])) {
            $validFilters['args'] = [
                'email_id' => $filter['emailId']
            ];
            $validFilters['partial_args_matching'] = 'json';
        }

        $args = [
                'offset' => $page * $limit,
                'per_page' => $limit,
                'orderby' => $sortKey,
                'order' => $sortDirection,
            ] + $validFilters;
        $store = ActionScheduler::store();
        $key = 'date';
        if (isset($args[$key])) {
            $args[$key] = as_get_datetime_object($args[$key]);
        }
        $args = $args + [
                'hook' => 'wrce_send_scheduled_email',
            ];

        $ids = $store->query_actions($args);
        $actions = [];
        $statuses = [];
        foreach ($ids as $action_id) {
            $statuses[$action_id] = $store->get_status($action_id);
            $action = $store->fetch_action($action_id);
            $actions[$action_id] = $action;
        }

        unset($args['offset']);
        unset($args['per_page']);
        $totalCount = $store->query_actions($args, 'count');

        $models = [];
        foreach ($actions as $actionId => $action) {
            $models[] = ScheduledEmail::fromAsAction($actionId, $statuses[$actionId], $action);
        }

        return [
            'data' => $this->serializer->normalize($models),
            'total' => $totalCount
        ];
    }

    #[RestRoute(
        route: 'schedulable',
        methods: ['GET'],
        permission: "role('administrator')",
        public: false
    )]
    public function getSchedulableEmails()
    {
        return array_keys(ScheduledEmails::getSchedulableEmails());
    }

    #[RestRoute(
        route: 'schedule/(?P<id>\d+)',
        methods: ['DELETE'],
        permission: "role('administrator')",
        arguments: [
            'id' => [
                'type' => 'integer',
                'required' => true
            ],
        ],
        public: false
    )]
    public function cancelAction(WP_REST_Request $request)
    {
        $id = $request->get_param('id');
        $store = ActionScheduler::store();

        // verify action is a scheduled email
        $action = $store->fetch_action($id);
        if ($action->get_hook() !== 'wrce_send_scheduled_email') {
            return new WP_REST_Response([
                'message' => 'Action is not a scheduled email action'
            ], WP_Http::BAD_REQUEST);
        }

        try {
            $store->cancel_action($id);
        } catch (\InvalidArgumentException $e) {
            return new WP_Error('action_not_found', $e->getMessage(), WP_Http::NOT_FOUND);
        }

        return ['result' => true];
    }

    #[RestRoute(
        route: 'schedule/(?P<id>\d+)',
        methods: ['POST'],
        permission: "role('administrator')",
        arguments: [
            'id' => [
                'type' => 'integer',
                'required' => true
            ],
        ],
        public: false
    )]
    public function sendScheduledEmail(WP_REST_Request $request)
    {
        $id = $request->get_param('id');
        $store = ActionScheduler::store();

        // verify action is a scheduled email
        $action = $store->fetch_action($id);
        if ($action->get_hook() !== 'wrce_send_scheduled_email') {
            return new WP_Error('invalid_action', 'Action is not a scheduled email action', WP_Http::BAD_REQUEST);
        }

        try {
            do_action('wrce_send_scheduled_email', ...$action->get_args());
        } catch (\Throwable $e) {
            return new WP_Error('error', $e->getMessage(), WP_Http::INTERNAL_SERVER_ERROR);
        }

        return ['result' => true];
    }

    /**
     * Get Last-Modified and Cache-Control headers based on the timestamp and the cache expiry time.
     *
     * The Cache-Control header is set to no-cache, private, must-revalidate, max-age=expiryTime
     * where expiryTime is the time left for the cache to expire.
     *
     * The no-cache directive forces caches to submit the request to the origin server for validation before releasing a cached copy.
     * The private directive indicates that the response is intended for a single user and must not be stored by a shared cache.
     * The must-revalidate directive indicates that once it has become stale, a cache must not use the response to satisfy subsequent requests without successful validation on the origin server.
     *
     * @param int $timestamp The timestamp of the cache item insertion
     * @return array The Last-Modified and Cache-Control headers
     */
    public function getCacheHeaders(int $timestamp, int $expiration = 600)
    {
        return [
            'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $timestamp),
            'Cache-Control' => 'no-cache, private, must-revalidate, max-age=' . $timestamp + $expiration - time()
        ];
    }

}
