<?php
namespace DevOwl\RealCookieBanner\settings;

use DevOwl\CookieConsentManagement\settings\BannerLink as SettingsBannerLink;
use DevOwl\Multilingual\None;
use DevOwl\RealCookieBanner\base\UtilsProvider;
use DevOwl\RealCookieBanner\comp\language\Hooks;
use DevOwl\RealCookieBanner\Core;
use DevOwl\RealCookieBanner\Localization;
use DevOwl\RealCookieBanner\Utils;
use DevOwl\RealCookieBanner\view\checklist\PrivacyPolicy;
use DevOwl\RealCookieBanner\view\checklist\PrivacyPolicyMentionUsage;
use WP_Error;
use WP_Post;
use WP_REST_Posts_Controller;

// @codeCoverageIgnoreStart
defined('ABSPATH') or die('No script kiddies please!'); // Avoid direct file request
// @codeCoverageIgnoreEnd

/**
 * Register banner link (currently only footer links) custom post type.
 */
class BannerLink {
    use UtilsProvider;

    const CPT_NAME = 'rcb-banner-link';

    const META_NAME_PAGE_TYPE = 'pageType';

    const META_NAME_IS_EXTERNAL_URL = 'isExternalUrl';

    const META_NAME_PAGE_ID = 'pageId';

    const META_NAME_EXTERNAL_URL = 'externalUrl';

    const META_NAME_HIDE_COOKIE_BANNER = 'hideCookieBanner';

    /**
     * Should the link be opened in a new window?
     *
     * @deprecated Just used for backwards-compatibility
     */
    const META_NAME_IS_TARGET_BLANK = 'isTargetBlank';

    const SYNC_META_COPY = [
        BannerLink::META_NAME_PAGE_TYPE,
        BannerLink::META_NAME_IS_EXTERNAL_URL,
        BannerLink::META_NAME_PAGE_ID,
        BannerLink::META_NAME_HIDE_COOKIE_BANNER,
        BannerLink::META_NAME_IS_TARGET_BLANK,
    ];

    const SYNC_META_COPY_ONCE = [BannerLink::META_NAME_EXTERNAL_URL];

    const SYNC_OPTIONS = [
        'data' => ['menu_order'],
        'meta' => [
            'copy' => BannerLink::SYNC_META_COPY,
            'copy-once' => BannerLink::SYNC_META_COPY_ONCE,
        ],
    ];

    const META_KEYS = [
        BannerLink::META_NAME_PAGE_TYPE,
        BannerLink::META_NAME_IS_EXTERNAL_URL,
        BannerLink::META_NAME_PAGE_ID,
        BannerLink::META_NAME_EXTERNAL_URL,
        BannerLink::META_NAME_HIDE_COOKIE_BANNER,
        BannerLink::META_NAME_IS_TARGET_BLANK,
    ];

    /**
     * Singleton instance.
     *
     * @var BannerLink
     */
    private static $me = null;

    private $cacheGetOrdered = [];

    /**
     * C'tor.
     */
    private function __construct() {
        // Silence is golden.
    }

    /**
     * Register custom post type.
     */
    public function register() {
        // Keep this array for translation purposes, so it can be used with `@devowl-wp/multilingual` and auto translate
        [
            SettingsBannerLink::PAGE_TYPE_GENERAL_TERMS_AND_CONDITIONS => __(
                'General terms and conditions',
                'real-cookie-banner'
            ),
            SettingsBannerLink::PAGE_TYPE_TERMS_OF_USE => __('Terms of use', 'real-cookie-banner'),
            SettingsBannerLink::PAGE_TYPE_CANCELLATION_POLICY => __('Cancellation policy', 'real-cookie-banner'),
            SettingsBannerLink::PAGE_TYPE_COOKIE_POLICY => __('Cookie policy', 'real-cookie-banner'),
            SettingsBannerLink::PAGE_TYPE_DATA_PROCESSING_AGREEMENT => __(
                'Data processing agreement',
                'real-cookie-banner'
            ),
            SettingsBannerLink::PAGE_TYPE_DISPUTE_RESOLUTION => __('Dispute resolution', 'real-cookie-banner'),
        ];

        $labels = [
            'name' => __('Banner links', 'real-cookie-banner'),
            'singular_name' => __('Banner link', 'real-cookie-banner'),
        ];

        $args = [
            'label' => $labels['name'],
            'labels' => $labels,
            'description' => '',
            'public' => false,
            'publicly_queryable' => false,
            'show_ui' => true,
            'show_in_rest' => true,
            'rest_base' => self::CPT_NAME,
            'rest_controller_class' => WP_REST_Posts_Controller::class,
            'has_archive' => false,
            'show_in_menu' => false,
            'show_in_nav_menus' => false,
            'delete_with_user' => false,
            'exclude_from_search' => true,
            'capabilities' => Cookie::CAPABILITIES,
            'map_meta_cap' => false,
            'hierarchical' => false,
            'rewrite' => false,
            'query_var' => true,
            'supports' => ['title', 'editor', 'custom-fields', 'page-attributes'],
        ];
        register_post_type(self::CPT_NAME, $args);

        register_meta('post', self::META_NAME_PAGE_TYPE, [
            'object_subtype' => self::CPT_NAME,
            'type' => 'boolean',
            'single' => true,
            'show_in_rest' => [
                'schema' => [
                    'type' => 'string',
                    'enum' => [
                        SettingsBannerLink::PAGE_TYPE_LEGAL_NOTICE,
                        SettingsBannerLink::PAGE_TYPE_PRIVACY_POLICY,
                        SettingsBannerLink::PAGE_TYPE_GENERAL_TERMS_AND_CONDITIONS,
                        SettingsBannerLink::PAGE_TYPE_TERMS_OF_USE,
                        SettingsBannerLink::PAGE_TYPE_CANCELLATION_POLICY,
                        SettingsBannerLink::PAGE_TYPE_COOKIE_POLICY,
                        SettingsBannerLink::PAGE_TYPE_DATA_PROCESSING_AGREEMENT,
                        SettingsBannerLink::PAGE_TYPE_DISPUTE_RESOLUTION,
                        SettingsBannerLink::PAGE_TYPE_OTHER,
                    ],
                ],
            ],
        ]);

        register_meta('post', self::META_NAME_IS_EXTERNAL_URL, [
            'object_subtype' => self::CPT_NAME,
            'type' => 'boolean',
            'single' => true,
            'show_in_rest' => true,
        ]);

        register_meta('post', self::META_NAME_PAGE_ID, [
            'object_subtype' => self::CPT_NAME,
            'type' => 'number',
            'single' => true,
            'show_in_rest' => true,
        ]);

        register_meta('post', self::META_NAME_EXTERNAL_URL, [
            'object_subtype' => self::CPT_NAME,
            'type' => 'string',
            'single' => true,
            'show_in_rest' => true,
        ]);

        register_meta('post', self::META_NAME_HIDE_COOKIE_BANNER, [
            'object_subtype' => self::CPT_NAME,
            'type' => 'boolean',
            'single' => true,
            'show_in_rest' => true,
        ]);

        register_meta('post', self::META_NAME_IS_TARGET_BLANK, [
            'object_subtype' => self::CPT_NAME,
            'type' => 'boolean',
            'single' => true,
            'show_in_rest' => true,
        ]);
    }

    /**
     * Get all available banner links ordered.
     *
     * @param boolean $force
     * @param WP_Post[] $usePosts If set, only meta is applied to the passed posts
     * @return WP_Post[]|WP_Error
     */
    public function getOrdered($force = false, $usePosts = null) {
        $context = Revision::getInstance()->getContextVariablesString();
        if ($force === false && isset($this->cacheGetOrdered[$context]) && $usePosts === null) {
            return $this->cacheGetOrdered[$context];
        }

        $posts =
            $usePosts === null
                ? get_posts(
                    Core::getInstance()->queryArguments(
                        [
                            'post_type' => self::CPT_NAME,
                            'orderby' => [
                                'menu_order' => 'ASC',
                                'ID' => 'DESC',
                            ],
                            'numberposts' => -1,
                            'nopaging' => true,
                            'post_status' => 'publish',
                        ],
                        'bannerLinkGetOrdered'
                    )
                )
                : $usePosts;

        foreach ($posts as &$post) {
            $post->metas = [];
            foreach (self::META_KEYS as $meta_key) {
                $metaValue = get_post_meta($post->ID, $meta_key, true);
                switch ($meta_key) {
                    case self::META_NAME_PAGE_ID:
                        $metaValue = intval($metaValue);
                        break;
                    case self::META_NAME_IS_EXTERNAL_URL:
                    case self::META_NAME_HIDE_COOKIE_BANNER:
                    case self::META_NAME_IS_TARGET_BLANK:
                        $metaValue = boolval($metaValue);
                        break;
                    default:
                        break;
                }

                $post->metas[$meta_key] = $metaValue;
            }
        }

        if ($usePosts === null) {
            $this->cacheGetOrdered[$context] = $posts;
        }
        return $posts;
    }

    /**
     * Get legal link (legal notice or privacy policy) as URL or page ID.
     *
     * @param string $pageType
     * @param string $returnType Can be `url` or `id`
     */
    public function getLegalLink($pageType, $returnType) {
        $compLanguage = Core::getInstance()->getCompLanguage();

        foreach ($this->getOrdered() as $bannerLink) {
            $bPageType = $bannerLink->metas[self::META_NAME_PAGE_TYPE] ?? '';
            if ($bPageType === $pageType) {
                $isExternalUrl = $bannerLink->metas[self::META_NAME_IS_EXTERNAL_URL] ?? false;
                $externalUrl = $bannerLink->metas[self::META_NAME_EXTERNAL_URL] ?? false;
                $pageId = $bannerLink->metas[self::META_NAME_PAGE_ID] ?? false;
                $url = '';

                if ($isExternalUrl) {
                    $url = $externalUrl;
                } elseif ($pageId > 0) {
                    $pageId = $compLanguage->getCurrentPostId($pageId, 'page');

                    if ($returnType === 'id') {
                        return $pageId;
                    } else {
                        $permalink = Utils::getPermalink($pageId);
                        if ($permalink !== false) {
                            $url = $permalink;
                        }
                    }
                }

                if ($returnType === 'url' && !empty($url)) {
                    return $url;
                }
            }
        }

        return $returnType === 'url' ? '' : 0;
    }

    /**
     * Localize available banner links for frontend.
     */
    public function toJson() {
        $output = [];
        $bannerLinks = $this->getOrdered();
        $compLanguage = Core::getInstance()->getCompLanguage();

        foreach ($bannerLinks as $bannerLink) {
            $url = '';
            $pageId = $bannerLink->metas[self::META_NAME_PAGE_ID];

            if ($bannerLink->metas[self::META_NAME_IS_EXTERNAL_URL]) {
                $url = $bannerLink->metas[self::META_NAME_EXTERNAL_URL];
            } elseif ($pageId > 0) {
                $pageId = $compLanguage->getCurrentPostId($pageId, 'page');
                $permalink = Utils::getPermalink($pageId);
                if ($permalink !== false) {
                    $url = $permalink;
                }
            }

            if (!empty($url)) {
                $output[] = [
                    'id' => $bannerLink->ID,
                    'label' => $bannerLink->post_title,
                    'pageType' => $bannerLink->metas[self::META_NAME_PAGE_TYPE],
                    'isExternalUrl' => $bannerLink->metas[self::META_NAME_IS_EXTERNAL_URL],
                    'pageId' => $pageId,
                    'url' => $url,
                    'hideCookieBanner' => $bannerLink->metas[self::META_NAME_HIDE_COOKIE_BANNER],
                    'isTargetBlank' => $bannerLink->metas[self::META_NAME_IS_TARGET_BLANK],
                ];
            }
        }

        return Core::getInstance()
            ->getCompLanguage()
            ->translateArray($output, Localization::COMMON_SKIP_KEYS, null, ['legal-text']);
    }

    /**
     * Create default banner links (legal notice, privacy policy) from options known
     * from older Real Cookie Banner versions or just blank links, so the user does not need
     * to click on "Add another link" in the Settings UI.
     *
     * @param boolean $backwardsCompatibility If `true`, it will try to restore the links from previous installed version
     */
    public function createDefaults($backwardsCompatibility = false) {
        $fn = function () use ($backwardsCompatibility) {
            global $wpdb;

            $compLanguage = Core::getInstance()->getCompLanguage();
            $activeLangauges = $compLanguage->getActiveLanguages();

            $td = Hooks::getInstance()->createTemporaryTextDomain();

            if (count($this->getOrdered(true)) > 0) {
                // There are already links created, skip creation
                return;
            }

            $entries = $td->translate(function () {
                return [
                    'privacyPolicy' => [
                        'post_type' => self::CPT_NAME,
                        'post_title' => _x('Privacy policy', 'legal-text', 'real-cookie-banner'),
                        'post_content' => '',
                        'menu_order' => 0,
                        'meta_input' => [
                            self::META_NAME_PAGE_TYPE => SettingsBannerLink::PAGE_TYPE_PRIVACY_POLICY,
                            self::META_NAME_IS_EXTERNAL_URL => false,
                            self::META_NAME_PAGE_ID => General::getInstance()->getDefaultPrivacyPolicy(),
                            self::META_NAME_EXTERNAL_URL => '',
                            self::META_NAME_HIDE_COOKIE_BANNER => true,
                            self::META_NAME_IS_TARGET_BLANK => true,
                        ],
                        'post_status' => 'publish',
                    ],
                ];
            });

            $backwardsCompatibilityFilters = [];

            // Add legal notice only when the blog language is from DACH
            $wpLanguages =
                $compLanguage instanceof None
                    ? [get_locale()]
                    : array_map([$compLanguage, 'getWordPressCompatibleLanguageCode'], $activeLangauges);
            $dachLanguages = array_filter($wpLanguages, function ($l) {
                return Utils::startsWith($l, 'de');
            });

            if ($backwardsCompatibility || count($dachLanguages) > 0) {
                $entries['legalNotice'] = $td->translate(function () {
                    return [
                        'post_type' => self::CPT_NAME,
                        'post_title' => _x('Legal notice', 'legal-text', 'real-cookie-banner'),
                        'post_content' => '',
                        'menu_order' => 1,
                        'meta_input' => [
                            self::META_NAME_PAGE_TYPE => SettingsBannerLink::PAGE_TYPE_LEGAL_NOTICE,
                            self::META_NAME_IS_EXTERNAL_URL => false,
                            self::META_NAME_PAGE_ID => 0,
                            self::META_NAME_EXTERNAL_URL => '',
                            self::META_NAME_HIDE_COOKIE_BANNER => true,
                            self::META_NAME_IS_TARGET_BLANK => true,
                        ],
                        'post_status' => 'publish',
                    ];
                });
            }

            if ($backwardsCompatibility) {
                // Load from `wp_options` to get also the ones with `LanguageDependingOption`
                // phpcs:disable WordPress.DB
                $options = $wpdb->get_results(
                    sprintf(
                        "SELECT option_name, option_value FROM $wpdb->options WHERE option_name LIKE '%1\$s-banner-legal-%%' OR option_name = '%1\$s-banner-footer-design-link-target'",
                        RCB_OPT_PREFIX
                    ),
                    ARRAY_A
                );
                // phpcs:enable WordPress.DB

                if (count($options) > 1) {
                    $currentLanguage = $compLanguage->getCurrentLanguageFallback();
                    $optionPrefix = RCB_OPT_PREFIX . '-banner-legal-';
                    $options = array_combine(
                        array_map(function ($key) use ($optionPrefix) {
                            return strpos($key, $optionPrefix) === 0 ? substr($key, strlen($optionPrefix)) : $key;
                        }, array_column($options, 'option_name')),
                        array_column($options, 'option_value')
                    );
                    $isTargetBlank =
                        ($options[RCB_OPT_PREFIX . '-banner-footer-design-link-target'] ?? '_blank') === '_blank';

                    foreach (['imprint', 'privacy-policy'] as $migrateType) {
                        $entryRef = &$entries[$migrateType === 'imprint' ? 'legalNotice' : 'privacyPolicy'];
                        $metaInputRef = &$entryRef['meta_input'];
                        $metaInputRef[self::META_NAME_PAGE_ID] = intval($options[$migrateType] ?? 0);
                        $metaInputRef[self::META_NAME_EXTERNAL_URL] = $options[$migrateType . '-external-url'] ?? '';
                        $metaInputRef[self::META_NAME_IS_EXTERNAL_URL] = boolval(
                            $options[$migrateType . '-is-external-url'] ?? false
                        );
                        $metaInputRef[self::META_NAME_HIDE_COOKIE_BANNER] = boolval(
                            $options[$migrateType . '-hide'] ?? true
                        );
                        $metaInputRef[self::META_NAME_IS_TARGET_BLANK] = $isTargetBlank;

                        $labelTranslations = [];
                        if (count($activeLangauges) > 0) {
                            foreach ($activeLangauges as $activeLangauge) {
                                $labelTranslations[$compLanguage->getWordPressCompatibleLanguageCode($activeLangauge)] =
                                    $options[$migrateType . '-label-' . $activeLangauge] ?? $entryRef['post_title'];
                            }

                            $backwardsCompatibilityFilter = function (
                                $translation,
                                $input,
                                $sourceLocale,
                                $targetLocale,
                                $domain
                            ) use ($labelTranslations) {
                                if (
                                    $domain === 'real-cookie-banner' &&
                                    $translation === null &&
                                    !empty($input) &&
                                    in_array($input, $labelTranslations, true) &&
                                    isset($labelTranslations[$targetLocale])
                                ) {
                                    return [$input, $labelTranslations[$targetLocale]];
                                }

                                return $translation;
                            };
                            $backwardsCompatibilityFilters[] = $backwardsCompatibilityFilter;
                            add_filter('DevOwl/Multilingual/TranslateInput', $backwardsCompatibilityFilter, 10, 5);

                            $entryRef['post_title'] =
                                $options[$migrateType . '-label-' . $currentLanguage] ?? $entryRef['post_title'];
                        } else {
                            $entryRef['post_title'] = $options[$migrateType . '-label'] ?? $entryRef['post_title'];
                        }
                    }
                }
            }

            foreach ($entries as $entry) {
                wp_insert_post($entry, true);
            }

            $privacyPolicyId = $entries['privacyPolicy']['meta_input'][self::META_NAME_PAGE_ID];
            if ($privacyPolicyId > 0) {
                PrivacyPolicyMentionUsage::recalculate($privacyPolicyId);
                PrivacyPolicy::recalculate($privacyPolicyId);
            }

            $td->teardown();

            foreach ($backwardsCompatibilityFilters as $f) {
                remove_filter('DevOwl/Multilingual/TranslateInput', $f, 10);
            }
        };

        if (did_action('init')) {
            $fn();
        } else {
            add_action('init', $fn, 9);
        }
    }

    /**
     * When Settings > Privacy got adjusted, apply the new privacy policy to the banner links, too.
     *
     * @param int $old_value
     * @param int $new_value
     */
    public function update_option_wp_page_for_privacy_policy($old_value, $new_value) {
        $compLanguage = Core::getInstance()->getCompLanguage();
        if ($compLanguage !== null) {
            $new_value = $compLanguage->getOriginalPostId($new_value, 'page');
        }

        if (get_post_status($new_value) === 'publish') {
            foreach ($this->getOrdered() as $bannerLink) {
                if (
                    $bannerLink->metas[self::META_NAME_PAGE_TYPE] === SettingsBannerLink::PAGE_TYPE_PRIVACY_POLICY &&
                    !$bannerLink->metas[self::META_NAME_IS_EXTERNAL_URL]
                ) {
                    update_post_meta($bannerLink->ID, self::META_NAME_PAGE_ID, $new_value);
                    break;
                }
            }
        }
    }

    /**
     * When a page gets deleted, check if the value is our configured banner links and reset the value accordingly.
     *
     * @param number $postId
     */
    public function delete_post($postId) {
        foreach ($this->getOrdered() as $bannerLink) {
            if (
                $bannerLink->metas[self::META_NAME_PAGE_ID] === $postId &&
                !$bannerLink->metas[self::META_NAME_IS_EXTERNAL_URL]
            ) {
                update_post_meta($bannerLink->ID, self::META_NAME_PAGE_ID, 0);
            }
        }
    }

    /**
     * When the privacy policy page gets adjusted, let's update the checklist if Real Cookie Banner
     * is mentioned in the privacy policy and if a privacy policy page is set.
     *
     * @param int $meta_id
     * @param int $object_id
     * @param string $meta_key
     * @param mixed $meta_value
     */
    public function added_or_updated_post_meta($meta_id, $object_id, $meta_key, $meta_value) {
        if ($meta_key === self::META_NAME_PAGE_ID && get_post_type($object_id) === self::CPT_NAME) {
            $compLanguage = Core::getInstance()->getCompLanguage();
            $postId = $meta_value;
            if ($compLanguage !== null) {
                $postId = $compLanguage->getCurrentPostId($postId, 'page');
            }

            add_action('shutdown', function () use ($postId) {
                if (
                    get_post_meta($postId, self::META_NAME_PAGE_TYPE, true) ===
                    SettingsBannerLink::PAGE_TYPE_PRIVACY_POLICY
                ) {
                    PrivacyPolicyMentionUsage::recalculate($postId);
                    PrivacyPolicy::recalculate($postId);
                }
            });
        }
    }

    /**
     * A page got updated, check if it is our privacy policy and recalculate the checklist.
     *
     * @param int $postId
     */
    public function save_post_page($postId) {
        foreach ($this->getOrdered() as $bannerLink) {
            if (
                $bannerLink->metas[self::META_NAME_PAGE_TYPE] === SettingsBannerLink::PAGE_TYPE_PRIVACY_POLICY &&
                $bannerLink->metas[self::META_NAME_PAGE_ID] === $postId &&
                !$bannerLink->metas[self::META_NAME_IS_EXTERNAL_URL]
            ) {
                PrivacyPolicyMentionUsage::recalculate($postId);
                PrivacyPolicy::recalculate($postId);
            }
        }
    }

    /**
     * Get singleton instance.
     *
     * @return BannerLink
     * @codeCoverageIgnore
     */
    public static function getInstance() {
        return self::$me === null ? (self::$me = new BannerLink()) : self::$me;
    }
}
