<?php
/**
 * Advanced Analytics Service
 * 
 * Features:
 * - Conversion funnels (visitor → cart → checkout → purchase)
 * - Customer cohort analysis
 * - Customer lifetime value (CLV)
 * - Product performance metrics
 * - Revenue attribution
 * - Churn analysis
 * - RFM segmentation
 * - Real-time dashboard
 * 
 * @package Instinctor
 * @since 1.4.0
 */

declare(strict_types=1);

namespace Instinctor\Services;

use Instinctor\Core\Config;
use Instinctor\Core\DB;
use Instinctor\Core\Auth;
use Instinctor\Core\Session;

if ( ! defined( 'ABSPATH' ) ) exit;

class AdvancedAnalyticsService
{
    private static ?self $instance = null;

    public static function instance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct()
    {
        $this->registerAjax('instinctor_analytics_data', [$this, 'ajax_get_analytics']);
        add_action('instinctor_track_event', [$this, 'track_event'], 10, 3);
    }

    /**
     * Get conversion funnel data
     */
    public function get_conversion_funnel(string $start_date, string $end_date): array
    {
        $events_table = DB::prefix() . 'instinctor_analytics_events';
        $orders_table = DB::prefix() . 'instinctor_orders';

        // Visitors (unique sessions with page views)
        $visitors = (int) DB::get_var(DB::prepare("
            SELECT COUNT(DISTINCT session_id) FROM {$events_table}
            WHERE event_type = 'page_view'
            AND created_at BETWEEN %s AND %s
        ", $start_date, $end_date . ' 23:59:59'));

        // Product views
        $product_views = (int) DB::get_var(DB::prepare("
            SELECT COUNT(DISTINCT session_id) FROM {$events_table}
            WHERE event_type = 'product_view'
            AND created_at BETWEEN %s AND %s
        ", $start_date, $end_date . ' 23:59:59'));

        // Add to cart
        $add_to_cart = (int) DB::get_var(DB::prepare("
            SELECT COUNT(DISTINCT session_id) FROM {$events_table}
            WHERE event_type = 'add_to_cart'
            AND created_at BETWEEN %s AND %s
        ", $start_date, $end_date . ' 23:59:59'));

        // Checkout initiated
        $checkout_started = (int) DB::get_var(DB::prepare("
            SELECT COUNT(DISTINCT session_id) FROM {$events_table}
            WHERE event_type = 'checkout_start'
            AND created_at BETWEEN %s AND %s
        ", $start_date, $end_date . ' 23:59:59'));

        // Purchases
        $purchases = (int) DB::get_var(DB::prepare("
            SELECT COUNT(*) FROM {$orders_table}
            WHERE status IN ('completed', 'processing')
            AND created_at BETWEEN %s AND %s
        ", $start_date, $end_date . ' 23:59:59'));

        return [
            'steps' => [
                ['name' => 'Visitors', 'count' => $visitors],
                ['name' => 'Product Views', 'count' => $product_views],
                ['name' => 'Add to Cart', 'count' => $add_to_cart],
                ['name' => 'Checkout Started', 'count' => $checkout_started],
                ['name' => 'Purchased', 'count' => $purchases]
            ],
            'conversion_rates' => [
                'overall' => $visitors > 0 ? round(($purchases / $visitors) * 100, 2) : 0,
                'view_to_cart' => $product_views > 0 ? round(($add_to_cart / $product_views) * 100, 2) : 0,
                'cart_to_checkout' => $add_to_cart > 0 ? round(($checkout_started / $add_to_cart) * 100, 2) : 0,
                'checkout_to_purchase' => $checkout_started > 0 ? round(($purchases / $checkout_started) * 100, 2) : 0
            ]
        ];
    }

    /**
     * Get cohort analysis (retention by signup month)
     */
    public function get_cohort_analysis(int $months = 6): array
    {
        $customers_table = DB::prefix() . 'instinctor_customers';
        $orders_table = DB::prefix() . 'instinctor_orders';

        $cohorts = [];

        for ($i = $months - 1; $i >= 0; $i--) {
            // phpcs:disable WordPress.DateTime.RestrictedFunctions -- date() used for local-timezone formatting, gmdate would force UTC
            $cohort_month = date('Y-m', strtotime("-{$i} months"));
            // phpcs:enable WordPress.DateTime.RestrictedFunctions
            $cohort_start = $cohort_month . '-01';
            // phpcs:disable WordPress.DateTime.RestrictedFunctions -- date() used for local-timezone formatting, gmdate would force UTC
            $cohort_end = date('Y-m-t', strtotime($cohort_start));
            // phpcs:enable WordPress.DateTime.RestrictedFunctions

            // Get customers who made first purchase in this month
            $cohort_customers = DB::get_col(DB::prepare("
                SELECT DISTINCT customer_id FROM {$orders_table}
                WHERE customer_id IS NOT NULL
                AND status IN ('completed', 'processing')
                GROUP BY customer_id
                HAVING MIN(created_at) BETWEEN %s AND %s
            ", $cohort_start, $cohort_end . ' 23:59:59'));

            if (empty($cohort_customers)) {
                $cohorts[] = [
                    'month' => $cohort_month,
                    'size' => 0,
                    'retention' => []
                ];
                continue;
            }

            $customer_ids = implode(',', array_map('intval', $cohort_customers));
            $retention = [];

            // Calculate retention for each subsequent month
            for ($m = 0; $m <= $months - $i - 1; $m++) {
                // phpcs:disable WordPress.DateTime.RestrictedFunctions -- date() used for local-timezone formatting, gmdate would force UTC
                $check_month = date('Y-m', strtotime($cohort_month . " +{$m} months"));
                // phpcs:enable WordPress.DateTime.RestrictedFunctions
                $check_start = $check_month . '-01';
                // phpcs:disable WordPress.DateTime.RestrictedFunctions -- date() used for local-timezone formatting, gmdate would force UTC
                $check_end = date('Y-m-t', strtotime($check_start));
                // phpcs:enable WordPress.DateTime.RestrictedFunctions

                $active = (int) DB::get_var("
                    SELECT COUNT(DISTINCT customer_id) FROM {$orders_table}
                    WHERE customer_id IN ({$customer_ids})
                    AND status IN ('completed', 'processing')
                    AND created_at BETWEEN '{$check_start}' AND '{$check_end} 23:59:59'
                ");

                $retention[] = count($cohort_customers) > 0 
                    ? round(($active / count($cohort_customers)) * 100, 1) 
                    : 0;
            }

            $cohorts[] = [
                'month' => $cohort_month,
                'size' => count($cohort_customers),
                'retention' => $retention
            ];
        }

        return $cohorts;
    }

    /**
     * Calculate Customer Lifetime Value
     */
    public function get_customer_ltv(int $customer_id = null): array
    {
        $orders_table = DB::prefix() . 'instinctor_orders';

        if ($customer_id) {
            // Individual customer LTV
            $data = DB::get_row(DB::prepare("
                SELECT 
                    COUNT(*) as order_count,
                    SUM(total) as total_revenue,
                    AVG(total) as avg_order_value,
                    DATEDIFF(MAX(created_at), MIN(created_at)) as customer_lifespan_days
                FROM {$orders_table}
                WHERE customer_id = %d
                AND status IN ('completed', 'processing')
            ", $customer_id), ARRAY_A);

            return [
                'customer_id' => $customer_id,
                'total_revenue' => (float) ($data['total_revenue'] ?? 0),
                'order_count' => (int) ($data['order_count'] ?? 0),
                'avg_order_value' => (float) ($data['avg_order_value'] ?? 0),
                'lifespan_days' => (int) ($data['customer_lifespan_days'] ?? 0)
            ];
        }

        // Store-wide LTV metrics
        $metrics = DB::get_row("
            SELECT 
                COUNT(DISTINCT customer_id) as total_customers,
                AVG(customer_total) as avg_ltv,
                MAX(customer_total) as max_ltv,
                AVG(order_count) as avg_orders_per_customer
            FROM (
                SELECT 
                    customer_id,
                    SUM(total) as customer_total,
                    COUNT(*) as order_count
                FROM {$orders_table}
                WHERE customer_id IS NOT NULL
                AND status IN ('completed', 'processing')
                GROUP BY customer_id
            ) as customer_totals
        ", ARRAY_A);

        // Get LTV distribution
        $distribution = DB::get_results("
            SELECT 
                CASE 
                    WHEN customer_total < 100 THEN '< $100'
                    WHEN customer_total < 500 THEN '$100-$500'
                    WHEN customer_total < 1000 THEN '$500-$1000'
                    WHEN customer_total < 5000 THEN '$1000-$5000'
                    ELSE '$5000+'
                END as bracket,
                COUNT(*) as count
            FROM (
                SELECT customer_id, SUM(total) as customer_total
                FROM {$orders_table}
                WHERE customer_id IS NOT NULL
                AND status IN ('completed', 'processing')
                GROUP BY customer_id
            ) as totals
            GROUP BY bracket
            ORDER BY MIN(customer_total)
        ", ARRAY_A);

        return [
            'total_customers' => (int) ($metrics['total_customers'] ?? 0),
            'avg_ltv' => round((float) ($metrics['avg_ltv'] ?? 0), 2),
            'max_ltv' => (float) ($metrics['max_ltv'] ?? 0),
            'avg_orders_per_customer' => round((float) ($metrics['avg_orders_per_customer'] ?? 0), 1),
            'distribution' => $distribution
        ];
    }

    /**
     * RFM Segmentation (Recency, Frequency, Monetary)
     */
    public function get_rfm_segments(): array
    {
        $orders_table = DB::prefix() . 'instinctor_orders';

        // Get RFM scores for each customer
        $customers = DB::get_results("
            SELECT 
                customer_id,
                DATEDIFF(NOW(), MAX(created_at)) as recency_days,
                COUNT(*) as frequency,
                SUM(total) as monetary
            FROM {$orders_table}
            WHERE customer_id IS NOT NULL
            AND status IN ('completed', 'processing')
            GROUP BY customer_id
        ", ARRAY_A);

        if (empty($customers)) {
            return [];
        }

        // Calculate RFM scores (1-5 scale)
        $recency_values = array_column($customers, 'recency_days');
        $frequency_values = array_column($customers, 'frequency');
        $monetary_values = array_column($customers, 'monetary');

        $segments = [
            'champions' => [],
            'loyal' => [],
            'potential' => [],
            'new' => [],
            'at_risk' => [],
            'lost' => []
        ];

        foreach ($customers as $customer) {
            $r_score = $this->calculate_percentile_score($customer['recency_days'], $recency_values, true);
            $f_score = $this->calculate_percentile_score($customer['frequency'], $frequency_values);
            $m_score = $this->calculate_percentile_score($customer['monetary'], $monetary_values);

            $rfm_score = ($r_score + $f_score + $m_score) / 3;
            $customer['rfm_score'] = round($rfm_score, 1);

            // Segment based on RFM
            if ($r_score >= 4 && $f_score >= 4 && $m_score >= 4) {
                $segments['champions'][] = $customer;
            } elseif ($f_score >= 4 && $m_score >= 3) {
                $segments['loyal'][] = $customer;
            } elseif ($r_score >= 4 && $f_score <= 2) {
                $segments['new'][] = $customer;
            } elseif ($r_score >= 3 && $f_score >= 2 && $m_score >= 2) {
                $segments['potential'][] = $customer;
            } elseif ($r_score <= 2 && $f_score >= 2) {
                $segments['at_risk'][] = $customer;
            } else {
                $segments['lost'][] = $customer;
            }
        }

        return [
            'segments' => array_map(fn($s) => count($s), $segments),
            'details' => $segments
        ];
    }

    /**
     * Calculate percentile-based score
     */
    private function calculate_percentile_score($value, array $all_values, bool $reverse = false): int
    {
        sort($all_values);
        $count = count($all_values);
        $position = array_search($value, $all_values);
        $percentile = ($position + 1) / $count * 100;

        if ($reverse) {
            $percentile = 100 - $percentile;
        }

        if ($percentile >= 80) return 5;
        if ($percentile >= 60) return 4;
        if ($percentile >= 40) return 3;
        if ($percentile >= 20) return 2;
        return 1;
    }

    /**
     * Product performance metrics
     */
    public function get_product_performance(string $start_date, string $end_date, int $limit = 20): array
    {
        $orders_table = DB::prefix() . 'instinctor_orders';
        $items_table = DB::prefix() . 'instinctor_order_items';
        $products_table = DB::prefix() . 'instinctor_products';
        $views_table = DB::prefix() . 'instinctor_product_views';

        $products = DB::get_results(DB::prepare("
            SELECT 
                p.id,
                p.name,
                p.price,
                COALESCE(SUM(oi.quantity), 0) as units_sold,
                COALESCE(SUM(oi.subtotal), 0) as revenue,
                COUNT(DISTINCT o.id) as order_count
            FROM {$products_table} p
            LEFT JOIN {$items_table} oi ON oi.product_id = p.id
            LEFT JOIN {$orders_table} o ON o.id = oi.order_id 
                AND o.status IN ('completed', 'processing')
                AND o.created_at BETWEEN %s AND %s
            WHERE p.status = 'published'
            GROUP BY p.id
            ORDER BY revenue DESC
            LIMIT %d
        ", $start_date, $end_date . ' 23:59:59', $limit), ARRAY_A);

        // Add view data and conversion rates
        foreach ($products as &$product) {
            $views = (int) DB::get_var(DB::prepare("
                SELECT COUNT(*) FROM {$views_table}
                WHERE product_id = %d
                AND viewed_at BETWEEN %s AND %s
            ", $product['id'], $start_date, $end_date . ' 23:59:59'));

            $product['views'] = $views;
            $product['conversion_rate'] = $views > 0 
                ? round(($product['order_count'] / $views) * 100, 2) 
                : 0;
        }

        return $products;
    }

    /**
     * Revenue by traffic source
     */
    public function get_revenue_by_source(string $start_date, string $end_date): array
    {
        $orders_table = DB::prefix() . 'instinctor_orders';

        return DB::get_results(DB::prepare("
            SELECT 
                COALESCE(traffic_source, 'Direct') as source,
                COUNT(*) as orders,
                SUM(total) as revenue,
                AVG(total) as avg_order_value
            FROM {$orders_table}
            WHERE status IN ('completed', 'processing')
            AND created_at BETWEEN %s AND %s
            GROUP BY traffic_source
            ORDER BY revenue DESC
        ", $start_date, $end_date . ' 23:59:59'), ARRAY_A);
    }

    /**
     * Track analytics event
     */
    public function track_event(string $event_type, array $data = [], ?int $customer_id = null): void
    {
        $table = DB::prefix() . 'instinctor_analytics_events';

        DB::insert($table, [
            'event_type' => $event_type,
            'session_id' => $this->get_session_id(),
            'customer_id' => $customer_id,
            'event_data' => json_encode($data),
            'page_url' => isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : '',
            'referrer' => isset($_SERVER['HTTP_REFERER']) ? esc_url_raw(wp_unslash($_SERVER['HTTP_REFERER'])) : '',
            'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '',
            'ip_address' => $this->get_client_ip(),
            'created_at' => current_time('mysql')
        ]);
    }

    /**
     * Get real-time stats
     */
    public function get_realtime_stats(): array
    {
        $events_table = DB::prefix() . 'instinctor_analytics_events';
        $orders_table = DB::prefix() . 'instinctor_orders';

        // Active visitors (last 5 minutes)
        $active_visitors = (int) DB::get_var("
            SELECT COUNT(DISTINCT session_id) FROM {$events_table}
            WHERE created_at >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)
        ");

        // Orders today
        $orders_today = DB::get_row("
            SELECT COUNT(*) as count, COALESCE(SUM(total), 0) as revenue
            FROM {$orders_table}
            WHERE DATE(created_at) = CURDATE()
            AND status IN ('completed', 'processing')
        ", ARRAY_A);

        // Top pages right now
        $top_pages = DB::get_results("
            SELECT page_url, COUNT(*) as views
            FROM {$events_table}
            WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 MINUTE)
            AND event_type = 'page_view'
            GROUP BY page_url
            ORDER BY views DESC
            LIMIT 5
        ", ARRAY_A);

        return [
            'active_visitors' => $active_visitors,
            'orders_today' => (int) ($orders_today['count'] ?? 0),
            'revenue_today' => (float) ($orders_today['revenue'] ?? 0),
            'top_pages' => $top_pages
        ];
    }

    /**
     * Get dashboard summary
     */
    public function get_dashboard_summary(string $period = '30days'): array
    {
        $dates = $this->get_date_range($period);

        return [
            'funnel' => $this->get_conversion_funnel($dates['start'], $dates['end']),
            'ltv' => $this->get_customer_ltv(),
            'rfm' => $this->get_rfm_segments(),
            'products' => $this->get_product_performance($dates['start'], $dates['end'], 10),
            'sources' => $this->get_revenue_by_source($dates['start'], $dates['end']),
            'realtime' => $this->get_realtime_stats()
        ];
    }

    /**
     * AJAX handler
     */
    public function ajax_get_analytics(): void
    {
        check_ajax_referer('instinctor_admin', 'nonce');
        
        if (!Auth::can('manage_options')) {
            wp_send_json_error('Unauthorized');
        }

        // phpcs:disable WordPress.Security.NonceVerification -- request authenticated via the plugin Session::check/verify HMAC nonce (WPCS cannot recognise it), inputs sanitized
        $type = isset($_POST['type']) ? sanitize_text_field(wp_unslash($_POST['type'])) : 'summary';
        // phpcs:enable WordPress.Security.NonceVerification
        // phpcs:disable WordPress.Security.NonceVerification -- request authenticated via the plugin Session::check/verify HMAC nonce (WPCS cannot recognise it), inputs sanitized
        $period = isset($_POST['period']) ? sanitize_text_field(wp_unslash($_POST['period'])) : '30days';
        // phpcs:enable WordPress.Security.NonceVerification
        $dates = $this->get_date_range($period);

        switch ($type) {
            case 'funnel':
                $data = $this->get_conversion_funnel($dates['start'], $dates['end']);
                break;
            case 'cohort':
                $data = $this->get_cohort_analysis();
                break;
            case 'ltv':
                $data = $this->get_customer_ltv();
                break;
            case 'rfm':
                $data = $this->get_rfm_segments();
                break;
            case 'products':
                $data = $this->get_product_performance($dates['start'], $dates['end']);
                break;
            case 'sources':
                $data = $this->get_revenue_by_source($dates['start'], $dates['end']);
                break;
            case 'realtime':
                $data = $this->get_realtime_stats();
                break;
            default:
                $data = $this->get_dashboard_summary($period);
        }

        wp_send_json_success($data);
    }

    /**
     * Get date range from period string
     */
    private function get_date_range(string $period): array
    {
        // phpcs:disable WordPress.DateTime.RestrictedFunctions -- date() used for local-timezone formatting, gmdate would force UTC
        $end = date('Y-m-d');
        // phpcs:enable WordPress.DateTime.RestrictedFunctions

        switch ($period) {
            case '7days':
                // phpcs:disable WordPress.DateTime.RestrictedFunctions -- date() used for local-timezone formatting, gmdate would force UTC
                $start = date('Y-m-d', strtotime('-7 days'));
                // phpcs:enable WordPress.DateTime.RestrictedFunctions
                break;
            case '30days':
                // phpcs:disable WordPress.DateTime.RestrictedFunctions -- date() used for local-timezone formatting, gmdate would force UTC
                $start = date('Y-m-d', strtotime('-30 days'));
                // phpcs:enable WordPress.DateTime.RestrictedFunctions
                break;
            case '90days':
                // phpcs:disable WordPress.DateTime.RestrictedFunctions -- date() used for local-timezone formatting, gmdate would force UTC
                $start = date('Y-m-d', strtotime('-90 days'));
                // phpcs:enable WordPress.DateTime.RestrictedFunctions
                break;
            case 'year':
                // phpcs:disable WordPress.DateTime.RestrictedFunctions -- date() used for local-timezone formatting, gmdate would force UTC
                $start = date('Y-m-d', strtotime('-1 year'));
                // phpcs:enable WordPress.DateTime.RestrictedFunctions
                break;
            default:
                // phpcs:disable WordPress.DateTime.RestrictedFunctions -- date() used for local-timezone formatting, gmdate would force UTC
                $start = date('Y-m-d', strtotime('-30 days'));
                // phpcs:enable WordPress.DateTime.RestrictedFunctions
        }

        return ['start' => $start, 'end' => $end];
    }

    /**
     * Get session ID
     */
    private function get_session_id(): string
    {
        if (!isset($_COOKIE['instinctor_session'])) {
            $session_id = wp_generate_uuid4();
            setcookie('instinctor_session', $session_id, time() + DAY_IN_SECONDS * 30, '/');
            return $session_id;
        }
        return sanitize_text_field(wp_unslash((string)$_COOKIE['instinctor_session']));
    }

    /**
     * Get client IP
     */
    private function get_client_ip(): string
    {
        $ip_keys = ['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR'];
        
        foreach ($ip_keys as $key) {
            if (isset($_SERVER[$key]) && !empty($_SERVER[$key])) {
                $ip = explode(',', sanitize_text_field(wp_unslash((string)($_SERVER[$key] ?? ''))))[0];
                if (filter_var(trim($ip), FILTER_VALIDATE_IP)) {
                    return trim($ip);
                }
            }
        }
        
        return '0.0.0.0';
    }

    /**
     * Register an AJAX handler for both WordPress and standalone modes.
     * On WordPress: add_action('wp_ajax_' . $action)
     * On standalone: AjaxDispatcher::register($action)
     */
    private function registerAjax(string $action, callable $handler): void
    {
        if (!defined('INSTINCTOR_ENV') || INSTINCTOR_ENV === 'wordpress') {
            add_action('wp_ajax_' . $action, $handler);
        } else {
            \Instinctor\Core\AjaxDispatcher::register($action, $handler);
        }
    }

}
