<?php

/**
 * Content Gating Service
 *
 * Handles post/page/CPT access restrictions.
 * Manages taxonomy-based and individual post restrictions.
 *
 * @package ArraySubs\Features\MembersAccess\Services
 * @since 1.0.0
 */

namespace ArraySubs\Features\MembersAccess\Services;

defined('ABSPATH') || exit;

/**
 * ContentGating class
 */
class ContentGating
{
    /**
     * Check if user can access a specific post
     *
     * @param int $user_id User ID.
     * @param int $post_id Post ID.
     * @return bool True if user can access.
     */
    public function canUserAccessPost(int $user_id, int $post_id): bool
    {
        $post = get_post($post_id);
        if (!$post) {
            return true; // Post doesn't exist
        }

        // Check per-post restriction first
        $post_restriction = $this->getPostRestriction($post_id);
        if ($post_restriction !== null) {
            $evaluator = new ConditionEvaluator();
            return $evaluator->evaluate($user_id, $post_restriction);
        }

        // Check CPT rules
        $cpt_rules = $this->getCptRules();

        foreach ($cpt_rules as $rule) {
            if (!isset($rule['enabled']) || !$rule['enabled']) {
                continue;
            }

            if ($this->ruleAppliesToPost($rule, $post)) {
                $evaluator = new ConditionEvaluator();
                $conditions = $rule['conditions'] ?? ['logic' => 'and', 'rules' => []];

                if (!$evaluator->evaluate($user_id, $conditions)) {
                    return false;
                }

                // Check dripping schedule — conditions passed but access may be delayed
                $schedule_evaluator = new ScheduleEvaluator();
                if (!$schedule_evaluator->isScheduleSatisfied($user_id, $rule)) {
                    return false;
                }
            }
        }

        return true; // No restrictions found
    }

    /**
     * Get per-post restriction conditions
     *
     * @param int $post_id Post ID.
     * @return array|null Conditions array or null if no restriction.
     */
    private function getPostRestriction(int $post_id): ?array
    {
        $restriction_enabled = get_post_meta($post_id, '_arraysubs_restrict_enabled', true);

        if ($restriction_enabled !== '1') {
            return null;
        }

        $conditions_json = get_post_meta($post_id, '_arraysubs_restrict_conditions', true);
        if (empty($conditions_json)) {
            return null;
        }

        $conditions = json_decode($conditions_json, true);
        if (!is_array($conditions)) {
            return null;
        }

        return $conditions;
    }

    /**
     * Get CPT restriction rules
     *
     * @return array
     */
    private function getCptRules(): array
    {
        return arraysubs_get_setting('members_access.cpt_rules', []);
    }

    /**
     * Check if a CPT rule applies to a specific post
     *
     * @param array    $rule Rule configuration.
     * @param \WP_Post $post Post object.
     * @return bool
     */
    private function ruleAppliesToPost(array $rule, \WP_Post $post): bool
    {
        $target_type = $rule['target_type'] ?? 'post_type';

        switch ($target_type) {
            case 'post_type':
                $target_post_type = $rule['post_type'] ?? '';
                return $post->post_type === $target_post_type;

            case 'taxonomy':
                $taxonomy = $rule['taxonomy'] ?? '';
                $term_ids = $rule['term_ids'] ?? [];

                if (empty($taxonomy)) {
                    return false;
                }

                if (empty($term_ids)) {
                    // All posts with any term in taxonomy
                    $post_terms = wp_get_object_terms($post->ID, $taxonomy, ['fields' => 'ids']);
                    return !empty($post_terms) && !is_wp_error($post_terms);
                }

                // Specific terms
                $post_terms = wp_get_object_terms($post->ID, $taxonomy, ['fields' => 'ids']);
                if (is_wp_error($post_terms)) {
                    return false;
                }

                return !empty(array_intersect($post_terms, $term_ids));

            case 'specific_posts':
                $post_ids = $rule['post_ids'] ?? [];
                return in_array($post->ID, $post_ids, true);

            default:
                return false;
        }
    }

    /**
     * Get restricted message for a post
     *
     * @param int $post_id Post ID.
     * @return string
     */
    public function getRestrictedMessage(int $post_id): string
    {
        // Check for per-post custom message
        $custom_message = get_post_meta($post_id, '_arraysubs_restrict_message', true);
        if (!empty($custom_message)) {
            return $this->formatRestrictedMessage($custom_message);
        }

        // Check for CPT rule message
        $post = get_post($post_id);
        if ($post) {
            $cpt_rules = $this->getCptRules();
            foreach ($cpt_rules as $rule) {
                if (!isset($rule['enabled']) || !$rule['enabled']) {
                    continue;
                }

                if ($this->ruleAppliesToPost($rule, $post)) {
                    $rule_message = $rule['message'] ?? '';
                    if (!empty($rule_message)) {
                        return $this->formatRestrictedMessage($rule_message);
                    }
                    break;
                }
            }
        }

        // Default message
        $default_message = arraysubs_get_setting(
            'members_access.default_message',
            __('This content is restricted. Please subscribe to access.', 'arraysubs')
        );

        return $this->formatRestrictedMessage($default_message);
    }

    /**
     * Format restricted message HTML
     *
     * @param string $message Message text.
     * @return string
     */
    private function formatRestrictedMessage(string $message): string
    {
        // Process merge tags
        $message = $this->processMergeTags($message);

        return sprintf(
            '<div class="arraysubs-restricted-content arraysubs-restricted-post">
                <div class="arraysubs-restricted-content__message">
                    <p>%s</p>
                </div>
            </div>',
            wp_kses_post($message)
        );
    }

    /**
     * Process merge tags in message
     *
     * @param string $message Message with possible merge tags.
     * @return string
     */
    private function processMergeTags(string $message): string
    {
        $tags = [
            '{site_name}'    => get_bloginfo('name'),
            '{pricing_link}' => home_url(arraysubs_get_setting('members_access.default_redirect_url', '/pricing')),
            '{login_link}'   => wp_login_url(),
        ];

        return str_replace(array_keys($tags), array_values($tags), $message);
    }

    /**
     * Get all accessible post IDs for a user
     *
     * @param int    $user_id   User ID.
     * @param string $post_type Post type to check.
     * @return array Array of post IDs.
     */
    public function getAccessiblePostIds(int $user_id, string $post_type = 'post'): array
    {
        // Get all published posts of this type
        $posts = get_posts([
            'post_type'      => $post_type,
            'post_status'    => 'publish',
            'posts_per_page' => -1,
            'fields'         => 'ids',
        ]);

        $accessible = [];
        foreach ($posts as $post_id) {
            if ($this->canUserAccessPost($user_id, $post_id)) {
                $accessible[] = $post_id;
            }
        }

        return $accessible;
    }

    /**
     * Get all restricted post IDs for a post type
     *
     * @param string $post_type Post type.
     * @return array Array of restricted post IDs.
     */
    public function getRestrictedPostIds(string $post_type = 'post'): array
    {
        // Posts with per-post restrictions
        $restricted = get_posts([
            'post_type'      => $post_type,
            'post_status'    => 'publish',
            'posts_per_page' => -1,
            'fields'         => 'ids',
            'meta_query'     => [
                [
                    'key'   => '_arraysubs_restrict_enabled',
                    'value' => '1',
                ],
            ],
        ]);

        // Add posts restricted by CPT rules
        $cpt_rules = $this->getCptRules();

        foreach ($cpt_rules as $rule) {
            if (!isset($rule['enabled']) || !$rule['enabled']) {
                continue;
            }

            $target_type = $rule['target_type'] ?? 'post_type';

            if ($target_type === 'post_type' && ($rule['post_type'] ?? '') === $post_type) {
                // All posts of this type are restricted
                $all_posts = get_posts([
                    'post_type'      => $post_type,
                    'post_status'    => 'publish',
                    'posts_per_page' => -1,
                    'fields'         => 'ids',
                ]);
                $restricted = array_merge($restricted, $all_posts);
            }

            if ($target_type === 'taxonomy') {
                $taxonomy = $rule['taxonomy'] ?? '';
                $term_ids = $rule['term_ids'] ?? [];

                if (!empty($taxonomy)) {
                    $query_args = [
                        'post_type'      => $post_type,
                        'post_status'    => 'publish',
                        'posts_per_page' => -1,
                        'fields'         => 'ids',
                        'tax_query'      => [
                            [
                                'taxonomy' => $taxonomy,
                                'field'    => 'term_id',
                                'terms'    => !empty($term_ids) ? $term_ids : get_terms([
                                    'taxonomy' => $taxonomy,
                                    'fields'   => 'ids',
                                ]),
                            ],
                        ],
                    ];

                    $taxonomy_posts = get_posts($query_args);
                    $restricted = array_merge($restricted, $taxonomy_posts);
                }
            }

            if ($target_type === 'specific_posts') {
                $post_ids = $rule['post_ids'] ?? [];
                $restricted = array_merge($restricted, $post_ids);
            }
        }

        return array_unique($restricted);
    }

    /**
     * Filter posts_where to hide restricted posts from archives
     *
     * @param string    $where    WHERE clause.
     * @param \WP_Query $wp_query Query object.
     * @return string
     */
    public function filterArchiveQuery(string $where, \WP_Query $wp_query): string
    {
        // Only filter frontend queries
        if (is_admin() || !$wp_query->is_main_query()) {
            return $where;
        }

        // Don't filter single posts
        if ($wp_query->is_singular()) {
            return $where;
        }

        global $wpdb;
        $user_id = get_current_user_id();

        // Get IDs of posts that should be hidden
        $hidden_ids = $this->getHiddenPostIds($user_id);

        if (!empty($hidden_ids)) {
            $ids_string = implode(',', array_map('intval', $hidden_ids));
            $where .= " AND {$wpdb->posts}.ID NOT IN ({$ids_string})";
        }

        return $where;
    }

    /**
     * Get post IDs that should be hidden from a user
     *
     * @param int $user_id User ID.
     * @return array
     */
    private function getHiddenPostIds(int $user_id): array
    {
        // Try to get from cache for this request
        static $cache = [];
        $cache_key = 'user_' . $user_id;

        if (isset($cache[$cache_key])) {
            return $cache[$cache_key];
        }

        $hidden = [];
        $cpt_rules = $this->getCptRules();

        foreach ($cpt_rules as $rule) {
            if (!isset($rule['enabled']) || !$rule['enabled']) {
                continue;
            }

            $archive_behavior = $rule['archive_behavior'] ?? 'hide';
            if ($archive_behavior !== 'hide') {
                continue;
            }

            // Check if user meets conditions
            $evaluator = new ConditionEvaluator();
            $conditions = $rule['conditions'] ?? ['logic' => 'and', 'rules' => []];

            if ($evaluator->evaluate($user_id, $conditions)) {
                // Conditions pass — check dripping schedule
                $schedule_evaluator = new ScheduleEvaluator();
                if ($schedule_evaluator->isScheduleSatisfied($user_id, $rule)) {
                    continue; // User has access and schedule is met
                }
            }

            // User doesn't have access - get posts to hide
            $target_type = $rule['target_type'] ?? 'post_type';

            switch ($target_type) {
                case 'specific_posts':
                    $hidden = array_merge($hidden, array_map('absint', $rule['post_ids'] ?? []));
                    break;

                case 'post_type':
                    $post_type = $rule['post_type'] ?? '';
                    if (!empty($post_type)) {
                        $posts = get_posts([
                            'post_type'      => $post_type,
                            'post_status'    => 'publish',
                            'posts_per_page' => 100, // Limit for performance
                            'fields'         => 'ids',
                        ]);
                        $hidden = array_merge($hidden, $posts);
                    }
                    break;

                case 'taxonomy':
                    $taxonomy = $rule['taxonomy'] ?? '';
                    $term_ids = $rule['term_ids'] ?? [];

                    if (!empty($taxonomy)) {
                        $args = [
                            'post_status'    => 'publish',
                            'posts_per_page' => 100, // Limit for performance
                            'fields'         => 'ids',
                            'tax_query'      => [
                                [
                                    'taxonomy' => $taxonomy,
                                    'field'    => 'term_id',
                                    'terms'    => !empty($term_ids) ? $term_ids : get_terms([
                                        'taxonomy'   => $taxonomy,
                                        'fields'     => 'ids',
                                        'hide_empty' => false,
                                    ]),
                                ],
                            ],
                        ];

                        $posts = get_posts($args);
                        $hidden = array_merge($hidden, $posts);
                    }
                    break;
            }
        }

        $result = array_unique($hidden);
        $cache[$cache_key] = $result;

        return $result;
    }
}
