<?php
/**
 * Report Registry Class
 *
 * Centralized registry for email report sections. Defines all available
 * report sections and queries data directly from module tables.
 *
 * @package Switchboard
 * @since 1.0.0
 */

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

class Switchboard_Report_Registry {

    /**
     * Singleton instance
     */
    private static $instance = null;

    /**
     * Available report sections (defined internally)
     */
    private $sections = [];

    /**
     * Whether the registry has been initialized
     */
    private $initialized = false;

    /**
     * Get singleton instance
     *
     * @return Switchboard_Report_Registry
     */
    public static function get_instance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Private constructor for singleton
     */
    private function __construct() {
        // Initialize on first access
    }

    /**
     * Initialize the registry and define available sections
     */
    public function init() {
        if ($this->initialized) {
            return;
        }

        $this->initialized = true;
        $this->define_sections();
    }

    /**
     * Force initialization (useful for AJAX/API contexts)
     */
    public function maybe_init() {
        if (!$this->initialized) {
            $this->init();
        }
    }

    /**
     * Define all available report sections
     * Each section has metadata and a data callback that queries the database directly
     */
    private function define_sections() {
        $this->sections = [
            'lean-analytics' => [
                'id'          => 'lean-analytics',
                'title'       => __('Site Analytics', 'wpswitchboard'),
                'description' => __('Pageviews, visits, bounce rate, top pages, and referrers', 'wpswitchboard'),
                'module'      => 'lean-analytics',
                'priority'    => 10,
                'icon'        => 'chart-bar',
            ],
            'activity-log' => [
                'id'          => 'activity-log',
                'title'       => __('Activity Log', 'wpswitchboard'),
                'description' => __('User logins, content changes, and security events', 'wpswitchboard'),
                'module'      => 'activity-log',
                'priority'    => 20,
                'icon'        => 'list-alt',
            ],
            'security-summary' => [
                'id'          => 'security-summary',
                'title'       => __('Limit Logins', 'wpswitchboard'),
                'description' => __('Failed login attempts, lockouts, and blocked IPs', 'wpswitchboard'),
                'module'      => 'limit-login-attempts',
                'priority'    => 15,
                'icon'        => 'shield',
            ],
            'track-404s' => [
                'id'          => 'track-404s',
                'title'       => __('404 Errors', 'wpswitchboard'),
                'description' => __('Broken links and missing pages', 'wpswitchboard'),
                'module'      => 'track-404s',
                'priority'    => 30,
                'icon'        => 'warning',
            ],
        ];

        /**
         * Filter to allow adding custom sections
         *
         * @param array $sections Defined sections
         */
        $this->sections = apply_filters('switchboard_report_sections', $this->sections);
    }

    /**
     * Get all sections
     *
     * @param bool $check_availability Whether to check if source modules are active
     * @return array Sections sorted by priority
     */
    public function get_sections($check_availability = false) {
        $this->maybe_init();

        $sections = $this->sections;

        if ($check_availability) {
            $active_modules = get_option('switchboard_active_modules', []);

            foreach ($sections as $id => &$section) {
                $section['available'] = in_array($section['module'], $active_modules, true);
            }
        }

        // Sort by priority
        uasort($sections, function ($a, $b) {
            return ($a['priority'] ?? 10) - ($b['priority'] ?? 10);
        });

        return $sections;
    }

    /**
     * Get a specific section by ID
     *
     * @param string $section_id Section identifier
     * @return array|null Section data or null if not found
     */
    public function get_section($section_id) {
        $this->maybe_init();
        return $this->sections[$section_id] ?? null;
    }

    /**
     * Check if a section is available (source module is active)
     *
     * @param string $section_id Section identifier
     * @return bool Whether the section is available
     */
    public function is_section_available($section_id) {
        $section = $this->get_section($section_id);

        if (!$section) {
            return false;
        }

        $active_modules = get_option('switchboard_active_modules', []);
        return in_array($section['module'], $active_modules, true);
    }

    /**
     * Get data for a specific section
     * Queries data directly from database tables
     *
     * @param string $section_id Section identifier
     * @param array  $date_range Date range ['from' => 'Y-m-d', 'to' => 'Y-m-d']
     * @return array|null Section data or null if not available
     */
    public function get_section_data($section_id, array $date_range) {
        if (!$this->is_section_available($section_id)) {
            return null;
        }

        try {
            $data = null;

            switch ($section_id) {
                case 'lean-analytics':
                    $data = $this->get_lean_analytics_data($date_range);
                    break;

                case 'activity-log':
                    $data = $this->get_activity_log_data($date_range);
                    break;

                case 'security-summary':
                    $data = $this->get_security_summary_data($date_range);
                    break;

                case 'track-404s':
                    $data = $this->get_track_404s_data($date_range);
                    break;
            }

            /**
             * Filter section data before returning
             *
             * @param array  $data       The section data
             * @param string $section_id Section identifier
             * @param array  $date_range Date range
             */
            return apply_filters('switchboard_report_section_data', $data, $section_id, $date_range);

        } catch (Exception $e) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('Switchboard Report Registry: Error getting data for section ' . $section_id . ': ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            }
            return null;
        }
    }

    /**
     * Get data for multiple sections
     *
     * @param array $section_ids Array of section identifiers
     * @param array $date_range  Date range ['from' => 'Y-m-d', 'to' => 'Y-m-d']
     * @return array Section data keyed by section ID
     */
    public function get_sections_data(array $section_ids, array $date_range) {
        $data = [];

        foreach ($section_ids as $section_id) {
            $section_data = $this->get_section_data($section_id, $date_range);

            if ($section_data !== null) {
                $section = $this->get_section($section_id);
                $data[$section_id] = [
                    'title'       => $section['title'],
                    'description' => $section['description'],
                    'icon'        => $section['icon'] ?? 'chart-bar',
                    'data'        => $section_data,
                ];
            }
        }

        return $data;
    }

    /**
     * Get available sections (where source module is active)
     *
     * @return array Available sections
     */
    public function get_available_sections() {
        $sections = $this->get_sections(true);

        return array_filter($sections, function ($section) {
            return !empty($section['available']);
        });
    }

    /**
     * Get section count
     *
     * @return int Number of defined sections
     */
    public function get_section_count() {
        $this->maybe_init();
        return count($this->sections);
    }

    /**
     * Check if registry has any sections
     *
     * @return bool Whether any sections are defined
     */
    public function has_sections() {
        $this->maybe_init();
        return !empty($this->sections);
    }

    // =========================================================================
    // DATA FETCHERS - Query data directly from module tables
    // =========================================================================

    /**
     * Get Lean Analytics report data
     *
     * @param array $date_range
     * @return array
     */
    private function get_lean_analytics_data($date_range) {
        global $wpdb;

        $table_name = $wpdb->prefix . 'lean_events';
        $from = $date_range['from'];
        $to = $date_range['to'];

        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name safely constructed from $wpdb->prefix

        // Check if table exists
        if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") !== $table_name) {
            // phpcs:enable
            return [];
        }

        // Total pageviews
        $pageviews = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table_name} WHERE event_type = 'pageview' AND day BETWEEN %s AND %s",
            $from,
            $to
        ));

        // Total visits (unique visitor-day sessions)
        $visits = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(DISTINCT visitor_day_key) FROM {$table_name} WHERE day BETWEEN %s AND %s",
            $from,
            $to
        ));

        // Bounce rate (sessions with only 1 pageview)
        $single_page_visits = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM (
                SELECT visitor_day_key FROM {$table_name}
                WHERE day BETWEEN %s AND %s
                GROUP BY visitor_day_key
                HAVING COUNT(*) = 1
            ) as bounced",
            $from,
            $to
        ));
        $bounce_rate = $visits > 0 ? round(($single_page_visits / $visits) * 100, 1) : 0;

        // Top pages
        $top_pages = $wpdb->get_results($wpdb->prepare(
            "SELECT path, path as title, COUNT(*) as pageviews
             FROM {$table_name}
             WHERE event_type = 'pageview' AND day BETWEEN %s AND %s
             GROUP BY path
             ORDER BY pageviews DESC
             LIMIT 5",
            $from,
            $to
        ), ARRAY_A);

        // Top referrers
        $top_referrers = $wpdb->get_results($wpdb->prepare(
            "SELECT ref_domain as url, COUNT(*) as visits
             FROM {$table_name}
             WHERE ref_domain IS NOT NULL AND ref_domain != '' AND day BETWEEN %s AND %s
             GROUP BY ref_domain
             ORDER BY visits DESC
             LIMIT 5",
            $from,
            $to
        ), ARRAY_A);

        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter

        return [
            'pageviews'       => intval($pageviews),
            'visits'          => intval($visits),
            'unique_visitors' => intval($visits),
            'bounce_rate'     => $bounce_rate,
            'top_pages'       => $top_pages,
            'top_referrers'   => $top_referrers,
        ];
    }

    /**
     * Get Activity Log report data
     *
     * @param array $date_range
     * @return array
     */
    private function get_activity_log_data($date_range) {
        global $wpdb;

        $table_name = $wpdb->prefix . 'switchboard_activity_log';
        $from = $date_range['from'];
        $to = $date_range['to'];

        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name safely constructed from $wpdb->prefix

        // Check if table exists
        if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") !== $table_name) {
            // phpcs:enable
            return [];
        }

        // Total events
        $total_events = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table_name} WHERE DATE(created_at) BETWEEN %s AND %s",
            $from,
            $to
        ));

        // User logins
        $user_logins = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table_name} WHERE action = 'user_login' AND DATE(created_at) BETWEEN %s AND %s",
            $from,
            $to
        ));

        // Failed logins
        $failed_logins = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table_name} WHERE action = 'login_failed' AND DATE(created_at) BETWEEN %s AND %s",
            $from,
            $to
        ));

        // Content changes
        $content_changes = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table_name} WHERE action IN ('post_published', 'post_updated', 'post_deleted', 'post_trashed') AND DATE(created_at) BETWEEN %s AND %s",
            $from,
            $to
        ));

        // Critical events
        $critical_events = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$table_name}
             WHERE action IN ('login_failed', 'user_deleted', 'plugin_deactivated', 'plugin_deleted', 'theme_switched')
             AND DATE(created_at) BETWEEN %s AND %s
             ORDER BY created_at DESC LIMIT 5",
            $from,
            $to
        ), ARRAY_A);

        // Recent events
        $recent_events = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$table_name}
             WHERE DATE(created_at) BETWEEN %s AND %s
             ORDER BY created_at DESC LIMIT 10",
            $from,
            $to
        ), ARRAY_A);

        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter

        return [
            'total_events'    => intval($total_events),
            'user_logins'     => intval($user_logins),
            'failed_logins'   => intval($failed_logins),
            'content_changes' => intval($content_changes),
            'critical_events' => $critical_events,
            'recent_events'   => $recent_events,
        ];
    }

    /**
     * Get Security Summary report data (from Limit Login Attempts)
     *
     * @param array $date_range
     * @return array
     */
    private function get_security_summary_data($date_range) {
        // Get lockout data from options (Limit Login Attempts stores data in options, not a table)
        $lockouts = get_option('switchboard_login_lockouts', []);
        $logged = get_option('switchboard_login_logged', []);
        $total_lockouts = get_option('switchboard_login_lockouts_total', 0);

        // Convert date_range to timestamps for filtering
        $range_from = isset($date_range['from']) ? strtotime($date_range['from'] . ' 00:00:00') : 0;
        $range_to = isset($date_range['to']) ? strtotime($date_range['to'] . ' 23:59:59') : time();

        // Count current lockouts active within the date range
        $current_lockouts = 0;
        $now = time();
        foreach ($lockouts as $ip => $expiry) {
            if ($expiry > $now && $expiry >= $range_from) {
                $current_lockouts++;
            }
        }

        // Count blocked IPs within the date range
        $blocked_ips = 0;
        foreach ($lockouts as $ip => $expiry) {
            if ($expiry >= $range_from) {
                $blocked_ips++;
            }
        }

        // Count failed attempts from logged data within the date range
        $failed_attempts = 0;
        $top_blocked_ips = [];

        foreach ($logged as $ip => $users) {
            $ip_total = 0;
            foreach ($users as $user => $data) {
                $last_date = isset($data['date']) ? intval($data['date']) : 0;
                if ($last_date >= $range_from && $last_date <= $range_to) {
                    $ip_total += isset($data['counter']) ? intval($data['counter']) : 0;
                }
            }
            $failed_attempts += $ip_total;

            if ($ip_total > 0) {
                $top_blocked_ips[$ip] = $ip_total;
            }
        }

        // Sort top blocked IPs by attempt count
        arsort($top_blocked_ips);
        $top_blocked_ips = array_slice($top_blocked_ips, 0, 5, true);

        // Format for template
        $formatted_top_ips = [];
        foreach ($top_blocked_ips as $ip => $count) {
            $formatted_top_ips[] = [
                'ip'    => $ip,
                'count' => $count,
            ];
        }

        return [
            'lockouts'        => $current_lockouts,
            'blocked_ips'     => $blocked_ips,
            'failed_attempts' => $failed_attempts,
            'top_blocked_ips' => $formatted_top_ips,
            'total_lockouts'  => intval($total_lockouts),
        ];
    }

    /**
     * Get Track 404s report data
     *
     * @param array $date_range
     * @return array
     */
    private function get_track_404s_data($date_range) {
        global $wpdb;

        $table_name = $wpdb->prefix . 'switchboard_404_log';
        $from = $date_range['from'];
        $to = $date_range['to'];

        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name safely constructed from $wpdb->prefix

        // Check if table exists
        if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") !== $table_name) {
            // phpcs:enable
            return [];
        }

        // Filter to pages only (exclude file-type URLs like .css, .js, .php, .ico, etc.)
        $pages_filter = "AND url NOT REGEXP '\\\\.(css|js|map|jpg|jpeg|png|gif|svg|webp|avif|ico|woff2?|ttf|eot|pdf|zip|xml|json|txt|php)(\\\\?|$)'";

        // Total 404 hits (table is aggregated: one row per URL with hit_count)
        $total_404s = $wpdb->get_var($wpdb->prepare(
            "SELECT COALESCE(SUM(hit_count), 0) FROM {$table_name} WHERE DATE(last_seen) BETWEEN %s AND %s {$pages_filter}",
            $from,
            $to
        ));

        // Unique URLs (already one row per URL, just count rows)
        $unique_urls = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table_name} WHERE DATE(last_seen) BETWEEN %s AND %s {$pages_filter}",
            $from,
            $to
        ));

        // Top 404 URLs (use hit_count directly, no GROUP BY needed)
        $top_urls = $wpdb->get_results($wpdb->prepare(
            "SELECT url, hit_count as hits
             FROM {$table_name}
             WHERE DATE(last_seen) BETWEEN %s AND %s {$pages_filter}
             ORDER BY hit_count DESC
             LIMIT 5",
            $from,
            $to
        ), ARRAY_A);

        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter

        return [
            'total_404s'   => intval($total_404s),
            'unique_urls'  => intval($unique_urls),
            'top_404_urls' => $top_urls,
        ];
    }
}
