<?php

namespace RecastPaywall\Services;

use RecastPaywall\Api\RecastClient;

/**
 * Entitlement service
 */
class EntitlementService {
    /**
     * Cache TTL in seconds
     */
    private const CACHE_TTL = 300; // 5 minutes (override via filter recast_paywall_entitlement_ttl)

    /**
     * Recast client
     */
    private RecastClient $recastClient;

    /**
     * Constructor
     */
    public function __construct(RecastClient $recastClient) {
        $this->recastClient = $recastClient;
    }

    /**
     * Check if user is entitled to access content
     */
    public function isEntitled(int $postId, ?int $userId = null, bool $bypassCache = false): bool {
        static $requestMemo = [];
        static $postEntitlementCache = [];
        
        // Get current user ID if not provided
        if ($userId === null) {
            $userId = get_current_user_id();
        }
        
        // For anonymous users (userId = 0), we'll still try to check entitlement via email
        // The getExternalUserId method handles this case
        
        // Create a unique cache key for this post/user combination
        // For anonymous users, use the external user ID (email) for cache key
        $externalUserId = $this->getExternalUserId($userId, $postId);
        $cacheKey = $postId . '_' . ($userId ?: 'anon_' . md5($externalUserId));
        
        // Check if we already have the result for this post/user
        if (isset($postEntitlementCache[$cacheKey])) {
            return $postEntitlementCache[$cacheKey];
        }
        
        // Check if post is paywalled first (no API call needed)
        if (!$this->isPostPaywalled($postId)) {
            $postEntitlementCache[$cacheKey] = true;
            return true;
        }

        // Check if it's a bot request and teaser is enabled
        if ($this->isBotRequest() && $this->shouldShowTeaserToSearchEngines()) {
            $postEntitlementCache[$cacheKey] = true;
            return true;
        }

        // External user ID already retrieved above for cache key
        if (empty($externalUserId)) {
            $postEntitlementCache[$cacheKey] = false;
            return false;
        }

        // Check if there was a recent purchase - if so, bypass cache for fresh check
        if (!$bypassCache && $this->hasRecentPurchaseActivity($postId, $userId)) {
            $bypassCache = true;
        }

        // Check request-level memo first
        $apiCacheKey = $this->getCacheKey($externalUserId, $postId);
        if (!$bypassCache && array_key_exists($apiCacheKey, $requestMemo)) {
            $result = (bool) $requestMemo[$apiCacheKey];
            $postEntitlementCache[$cacheKey] = $result;
            return $result;
        }
        
        // Limit request memo size to prevent memory issues
        if (count($requestMemo) > 100) {
            // Keep only the most recent 50 entries
            $requestMemo = array_slice($requestMemo, -50, 50, true);
        }

        // Check persistent cache (transient) next
        if (!$bypassCache) {
            $cached = get_transient($apiCacheKey);
            if ($cached !== false) {
                $this->logInfo('Entitlement cache hit', [ 'post_id' => $postId, 'external_user_id' => $externalUserId ]);
                $requestMemo[$apiCacheKey] = (bool) $cached;
                $postEntitlementCache[$cacheKey] = (bool) $cached;
                return (bool) $cached;
            }
        }

        // Prefix external IDs with brandID for proper namespacing
        // Note: Use hyphen separator (not underscore) - API requires /^[0-9a-zA-Z-]+$/
        $brandId = $this->getBrandId();
        $prefixedUserId = $brandId ? $brandId . '-' . $externalUserId : $externalUserId;
        $prefixedProductId = $brandId ? $brandId . '-' . $postId : (string) $postId;
        
        $this->logInfo('Entitlement cache miss; querying API', [
            'post_id' => $postId,
            'external_user_id' => $externalUserId,
            'prefixed_user_id' => $prefixedUserId,
            'prefixed_product_id' => $prefixedProductId,
            'brand_id' => $brandId,
        ]);
        // Check with Recast API - THIS IS THE ONLY PLACE API CALLS ARE MADE
        // Note: externalProductId = brandId_postId, externalUserId = brandId_userId
        $result = $this->recastClient->checkEntitlement($prefixedUserId, $prefixedProductId);
        
        if (is_wp_error($result)) {
            // On API failure, default to not entitled
            $this->logError('Entitlement check failed', [
                'post_id' => $postId,
                'user_id' => $userId,
                'external_user_id' => $externalUserId,
                'error' => $result->get_error_message(),
            ]);
            $postEntitlementCache[$cacheKey] = false;
            return false;
        }

        // Log the actual API response for debugging
        $this->logInfo('Entitlement API response', [
            'post_id' => $postId,
            'user_id' => $userId,
            'external_user_id' => $externalUserId,
            'response' => $result,
        ]);

        // Check if user has access based on RecastPay API response
        // API may return hasAccess at root level or nested under item/data
        $hasAccess = $result['hasAccess'] 
            ?? $result['item']['hasAccess'] 
            ?? $result['data']['hasAccess'] 
            ?? false;
        
        $isEntitled = !empty($hasAccess);
        
        $this->logInfo('Entitlement decision', [
            'post_id' => $postId,
            'user_id' => $userId,
            'external_user_id' => $externalUserId,
            'has_access_value' => $hasAccess,
            'has_access_source' => isset($result['hasAccess']) ? 'root' : (isset($result['item']['hasAccess']) ? 'item' : (isset($result['data']['hasAccess']) ? 'data' : 'none')),
            'is_entitled' => $isEntitled,
        ]);
        
        // Cache the result
        $ttl = apply_filters('recast_paywall_entitlement_ttl', self::CACHE_TTL);
        if (!$bypassCache) {
            set_transient($apiCacheKey, $isEntitled, $ttl);
        }
        $requestMemo[$apiCacheKey] = $isEntitled;
        $postEntitlementCache[$cacheKey] = $isEntitled;
        $this->logInfo('Entitlement decision cached', [ 'post_id' => $postId, 'result' => $isEntitled, 'ttl' => $ttl ]);

        return $isEntitled;
    }

    /**
     * Get brand ID from settings
     */
    private function getBrandId(): string {
        $settings = get_option('recast_paywall_settings', []);
        return trim($settings['brand_id'] ?? '');
    }

    /**
     * Get external user ID
     */
    public function getExternalUserId(?int $userId, int $postId): string {
        if ($userId) {
            $user = get_user_by('ID', $userId);
            if ($user) {
                // Prefer email for stable cross-system identity, then login, then numeric ID
                // $email = sanitize_email($user->user_email ?? '');
                // if (!empty($email)) {
                //     $externalUserId = $email;
                // } elseif (!empty($user->user_login)) {
                //     $externalUserId = (string) $user->user_login;
                // } else {
                //     $externalUserId = (string) $user->ID;
                // }
                $externalUserId = (string) $user->ID;
            } else {
                $externalUserId = '';
            }
        } else {
            // Try to get user email from various sources
            $externalUserId = $this->getUserEmail();
        }

        // Allow filtering
        return apply_filters('recast_paywall_external_user_id', $externalUserId, $userId, $postId);
    }

    /**
     * Get user email from various sources
     */
    private function getUserEmail(): string {
        // Check if user is logged in
        if (is_user_logged_in()) {
            $user = wp_get_current_user();
            return $user->user_email;
        }

        // Check for email in cookies/session
        $email = isset($_COOKIE['recast_user_email']) ? sanitize_email(wp_unslash($_COOKIE['recast_user_email'])) : '';
        
        if (empty($email) && isset($_SESSION['recast_user_email'])) {
            $email = sanitize_email($_SESSION['recast_user_email']);
        }

        return sanitize_email($email);
    }

    /**
     * Check if post is paywalled
     */
    public function isPostPaywalled(int $postId): bool {
        // Check if post has paywall enabled
        $isPaywalled = get_post_meta($postId, '_recast_paywall_enabled', true);
        
        if ($isPaywalled) {
            return true;
        }

        // Check if post has Recast Product ID (legacy support)
        $itemId = get_post_meta($postId, '_recast_inventory_item_id', true);
        
        return !empty($itemId);
    }

    /**
     * Check if request is from a bot
     */
    public function isBotRequest(): bool {
        $userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';
        
        // Common bot user agents
        $botPatterns = [
            'bot', 'crawler', 'spider', 'scraper', 'googlebot', 'bingbot', 'slurp',
            'duckduckbot', 'facebookexternalhit', 'twitterbot', 'linkedinbot',
            'whatsapp', 'telegrambot', 'discordbot', 'slackbot'
        ];

        $isBot = false;
        foreach ($botPatterns as $pattern) {
            if (stripos($userAgent, $pattern) !== false) {
                $isBot = true;
                break;
            }
        }

        return apply_filters('recast_paywall_is_bot_request', $isBot, $userAgent);
    }

    /**
     * Check if teaser should be shown to search engines
     */
    private function shouldShowTeaserToSearchEngines(): bool {
        $settings = get_option('recast_paywall_settings', []);
        return !empty($settings['show_teaser_to_search_engines']);
    }

    /**
     * Get cache key for entitlement
     */
    private function getCacheKey(string $externalUserId, int $postId): string {
        return 'recast_entitlement_' . md5($externalUserId . '_' . $postId);
    }

    /**
     * Mark that a purchase was initiated for polling/cache bypass
     * 
     * @param int $postId Post ID
     * @param int|null $userId User ID (null for current user)
     */
    public function markPurchaseActivity(int $postId, ?int $userId = null): void {
        if ($userId === null) {
            $userId = get_current_user_id();
        }

        $key = $this->getPurchaseActivityKey($postId, $userId);
        // Store for 2 minutes - enough time for webhook/polling to catch up
        set_transient($key, time(), 120);
    }

    /**
     * Check if there was recent purchase activity
     * 
     * @param int $postId Post ID
     * @param int|null $userId User ID (null for current user)
     * @return bool True if there was recent purchase activity
     */
    private function hasRecentPurchaseActivity(int $postId, ?int $userId = null): bool {
        if ($userId === null) {
            $userId = get_current_user_id();
        }

        $key = $this->getPurchaseActivityKey($postId, $userId);
        $timestamp = get_transient($key);
        
        if ($timestamp === false) {
            return false;
        }

        // Consider it recent if within last 2 minutes
        $elapsed = time() - (int) $timestamp;
        return $elapsed < 120;
    }

    /**
     * Get purchase activity transient key
     * 
     * @param int $postId Post ID
     * @param int $userId User ID
     * @return string Transient key
     */
    private function getPurchaseActivityKey(int $postId, int $userId): string {
        return 'recast_purchase_activity_' . $postId . '_' . $userId;
    }

    /**
     * Clear entitlement cache
     */
    public function clearCache(?int $postId = null, ?int $userId = null): void {
        if ($postId && $userId) {
            // Clear specific cache
            $externalUserId = $this->getExternalUserId($userId, $postId);
            $cacheKey = $this->getCacheKey($externalUserId, $postId);
            delete_transient($cacheKey);
        } else {
            // Clear all entitlement caches
            global $wpdb;
            
            // Direct database queries are necessary for bulk transient cleanup
            // WordPress doesn't provide efficient bulk transient deletion APIs
            // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
            // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
            
            if ($postId) {
                // Clear all caches for this post
                $wpdb->query($wpdb->prepare(
                    "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
                    $wpdb->esc_like('_transient_recast_entitlement_') . '%'
                ));
            } else {
                // Clear all entitlement caches
                $wpdb->query(
                    "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_recast_entitlement_%'"
                );
            }
            
            // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery
            // phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching
        }
    }

    /**
     * Clean up old transients to prevent database bloat
     */
    public function cleanupOldTransients(): void {
        global $wpdb;
        
        // Direct database queries are necessary for transient cleanup
        // WordPress doesn't provide efficient bulk transient management APIs
        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
        // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
        
        // Delete expired transients
        $wpdb->query(
            "DELETE FROM {$wpdb->options} 
             WHERE option_name LIKE '_transient_timeout_recast_entitlement_%' 
             AND option_value < UNIX_TIMESTAMP()"
        );
        
        // Delete orphaned transients (without timeout)
        $wpdb->query(
            "DELETE t1 FROM {$wpdb->options} t1
             LEFT JOIN {$wpdb->options} t2 ON t1.option_name = REPLACE(t2.option_name, '_transient_timeout_', '_transient_')
             WHERE t1.option_name LIKE '_transient_recast_entitlement_%'
             AND t2.option_name IS NULL"
        );
        
        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery
        // phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching
    }

    /**
     * Log error
     */
    private function logError(string $message, array $context = []): void {
        // Debug logging removed for production
    }

    /**
     * Log info messages when debugging is enabled
     */
    private function logInfo(string $message, array $context = []): void {
        // Debug logging removed for production
    }
}
