<?php
/**
 * Copyright (C) 2026 Plugixa (https://plugixa.com)
 * License: GNU General Public License v2 or later
 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
 */

namespace PlugixaRecipe\Api;

// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.SlowDBQuery, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom-table data layer: table identifiers are built with $wpdb->prefix and are never user input; all request values are bound via $wpdb->prepare(); ORDER BY / LIMIT fragments come out of internal allow-list maps; content reads go through PlugixaRecipe\Cache (the static sniff cannot trace caching through the wrapper); meta_key/meta_value are dedicated plugin columns, not WP_Query.

defined( 'ABSPATH' ) || exit;

use PlugixaRecipe\Activator;
use PlugixaRecipe\Cache;
use PlugixaRecipe\DemoData;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;

/**
 * Settings REST Controller
 */
class SettingsController extends RestController {

    public function __construct() {
        parent::__construct();
        $this->rest_base = 'settings';
    }

    public function register_routes(): void {
        register_rest_route($this->namespace, '/' . $this->rest_base, [
            [
                'methods'             => 'GET',
                'callback'            => [$this, 'get_settings'],
                'permission_callback' => [$this, 'check_admin_permission'],
            ],
            [
                'methods'             => 'POST',
                'callback'            => [$this, 'update_settings'],
                'permission_callback' => [$this, 'check_admin_permission'],
            ],
        ]);

        register_rest_route($this->namespace, '/' . $this->rest_base . '/dashboard', [
            [
                'methods'             => 'GET',
                'callback'            => [$this, 'get_dashboard'],
                'permission_callback' => [$this, 'check_permission'],
            ],
        ]);

        register_rest_route($this->namespace, '/' . $this->rest_base . '/demo-data', [
            [
                'methods'             => 'DELETE',
                'callback'            => [$this, 'reset_demo_data'],
                'permission_callback' => [$this, 'check_admin_permission'],
            ],
        ]);
    }

    /**
     * Get plugin settings.
     */
    public function get_settings($request): WP_REST_Response|WP_Error {
        $settings = get_option('plugixarecipe_settings', []);
        return $this->success($settings);
    }

    /**
     * Update plugin settings.
     */
    public function update_settings($request): WP_REST_Response|WP_Error {
        $params = $request->get_json_params();
        $current = get_option('plugixarecipe_settings', []);

        // Sanitize settings
        $sanitized = [];
        foreach ($params as $key => $value) {
            switch ($key) {
                // Integer fields
                case 'items_per_page':
                case 'excerpt_length':
                case 'default_servings':
                case 'max_upload_size':
                case 'reviews_per_page':
                case 'review_max_images':
                case 'cache_duration':
                case 'thumbnail_quality':
                case 'api_rate_limit':
                    $sanitized[$key] = (int) $value;
                    break;

                // Float fields
                case 'recaptcha_score_threshold':
                    $sanitized[$key] = (float) $value;
                    break;

                // Boolean fields
                case 'show_nutrition':
                case 'show_reviews':
                case 'review_moderation':
                case 'enable_print':
                case 'enable_favorites':
                case 'enable_shopping_lists':
                case 'enable_submissions':
                case 'submission_require_login':
                case 'recaptcha_form_enabled':
                case 'recaptcha_reviews_enabled':
                case 'notification_enabled':
                case 'allow_guest_reviews':
                case 'require_rating':
                case 'review_enable_images':
                case 'review_enable_helpful_votes':
                case 'review_enable_admin_reply':
                case 'enable_sitemap':
                case 'enable_open_graph':
                case 'enable_twitter_cards':
                case 'enable_lazy_load':
                case 'enable_cache':
                case 'image_optimization':
                case 'enable_rest_api':
                case 'enable_public_api':
                case 'delete_data_on_uninstall':
                case 'enable_debug':
                case 'enable_reviews':
                case 'enable_collections':
                case 'enable_nutrition':
                case 'enable_seo':
                case 'enable_unit_conversion':
                case 'show_breadcrumbs':
                    $sanitized[$key] = (bool) $value;
                    break;

                // HTML/code fields
                case 'custom_css':
                case 'custom_js':
                    $sanitized[$key] = wp_strip_all_tags($value);
                    break;

                // Email field
                case 'notification_recipients':
                    $sanitized[$key] = sanitize_text_field($value);
                    break;

                // URL fields
                case 'source_url':
                    $sanitized[$key] = esc_url_raw($value);
                    break;

                // Default: sanitize as text
                default:
                    if (is_string($value)) {
                        $sanitized[$key] = sanitize_text_field($value);
                    } elseif (is_array($value)) {
                        $sanitized[$key] = $value; // Allow arrays (e.g., complex configs)
                    } else {
                        $sanitized[$key] = $value;
                    }
                    break;
            }
        }

        // Only keys the build actually knows about may be persisted. In the free build
        // that is the free defaults; in the premium build the pro overlay has already
        // added its own keys to them through plugixarecipe_settings_defaults. Without
        // this, a stale admin bundle could still write premium keys into a free site.
        $sanitized = array_intersect_key($sanitized, Activator::get_default_settings());

        /**
         * Filter the sanitized settings before they are persisted.
         *
         * The premium overlay re-applies strict casts for its own keys (float, int,
         * bool) here, because the free switch above no longer names them.
         *
         * @param array<string,mixed> $sanitized Sanitized values, already allow-listed.
         * @param array<string,mixed> $params    The raw request payload.
         * @param array<string,mixed> $current   The currently stored settings.
         */
        $sanitized = apply_filters('plugixarecipe_sanitize_settings', $sanitized, $params, $current);

        $merged = array_merge($current, $sanitized);
        update_option('plugixarecipe_settings', $merged);

        return $this->success($merged, __('Settings updated.', 'plugixarecipe'));
    }

    /**
     * Get dashboard statistics.
     */
    public function get_dashboard($request): WP_REST_Response|WP_Error {
        global $wpdb;
        $prefix = $wpdb->prefix . 'plugixarecipe_';

        // Recipe counts by status
        $status_counts = $wpdb->get_results(
            "SELECT status, COUNT(*) AS count FROM {$prefix}recipes GROUP BY status",
            ARRAY_A
        );
        $statuses = [];
        $total_recipes = 0;
        foreach ($status_counts as $sc) {
            $statuses[$sc['status']] = (int) $sc['count'];
            $total_recipes += (int) $sc['count'];
        }

        // Featured count
        $featured = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$prefix}recipes WHERE is_featured = 1");

        // Average rating
        $avg_rating = (float) $wpdb->get_var(
            "SELECT AVG(rating_average) FROM {$prefix}recipes WHERE rating_average > 0"
        );

        // Rating distribution
        $rating_dist = $wpdb->get_results(
            "SELECT rating, COUNT(*) AS count FROM {$prefix}reviews WHERE status = 'approved' GROUP BY rating ORDER BY rating DESC",
            ARRAY_A
        );
        $rating_distribution = [];
        foreach ($rating_dist as $rd) {
            $rating_distribution[(int) $rd['rating']] = (int) $rd['count'];
        }

        // Recipes by category
        $recipes_by_category = $wpdb->get_results(
            "SELECT c.name, COUNT(rc.recipe_id) AS count
            FROM {$prefix}categories c
            LEFT JOIN {$prefix}recipe_categories rc ON c.id = rc.category_id
            GROUP BY c.id
            ORDER BY count DESC
            LIMIT 10",
            ARRAY_A
        );
        foreach ($recipes_by_category as &$rbc) {
            $rbc['count'] = (int) $rbc['count'];
        }

        // Recent recipes
        $recent_recipes = $wpdb->get_results(
            "SELECT id, title, status, difficulty, rating_average, created_at, image_id
            FROM {$prefix}recipes
            ORDER BY created_at DESC
            LIMIT 5",
            ARRAY_A
        );
        foreach ($recent_recipes as &$rr) {
            $rr['rating_average'] = (float) $rr['rating_average'];
            $rr['average_rating'] = $rr['rating_average'];
            if (!empty($rr['image_id'])) {
                $rr['image_url'] = wp_get_attachment_image_url((int) $rr['image_id'], 'thumbnail');
                $rr['image_thumbnail'] = $rr['image_url'];
            }
        }

        // Reviews stats
        $total_reviews = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$prefix}reviews");
        $pending_reviews = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$prefix}reviews WHERE status = 'pending'");

        // Categories count
        $total_categories = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$prefix}categories");

        // Cuisines count
        $total_cuisines = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$prefix}cuisines");

        // Collections count
        $total_collections = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$prefix}collections");

        // Total views (sum of view_count from recipes)
        $total_views = (int) $wpdb->get_var("SELECT COALESCE(SUM(view_count), 0) FROM {$prefix}recipes");

        // Total favorites
        $total_favorites = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$prefix}favorites");

        // Recent reviews
        $recent_reviews = $wpdb->get_results(
            "SELECT r.id, r.rating, r.review_text, r.author_name, r.status, r.created_at, rec.title AS recipe_title
            FROM {$prefix}reviews r
            LEFT JOIN {$prefix}recipes rec ON r.recipe_id = rec.id
            ORDER BY r.created_at DESC
            LIMIT 5",
            ARRAY_A
        );
        foreach ($recent_reviews as &$rr2) {
            $rr2['rating'] = (int) $rr2['rating'];
        }

        // Recipes by difficulty
        $recipes_by_difficulty = $wpdb->get_results(
            "SELECT difficulty, COUNT(*) AS count FROM {$prefix}recipes GROUP BY difficulty ORDER BY count DESC",
            ARRAY_A
        );
        foreach ($recipes_by_difficulty as &$rbd) {
            $rbd['count'] = (int) $rbd['count'];
        }

        // Recipes by cuisine (top 10)
        $recipes_by_cuisine = $wpdb->get_results(
            "SELECT cu.name, COUNT(rcu.recipe_id) AS count
            FROM {$prefix}cuisines cu
            LEFT JOIN {$prefix}recipe_cuisines rcu ON cu.id = rcu.cuisine_id
            GROUP BY cu.id
            ORDER BY count DESC
            LIMIT 10",
            ARRAY_A
        );
        foreach ($recipes_by_cuisine as &$rbc2) {
            $rbc2['count'] = (int) $rbc2['count'];
        }

        return $this->success([
            'total_recipes'         => $total_recipes,
            'published_recipes'     => $statuses['published'] ?? 0,
            'draft_recipes'         => $statuses['draft'] ?? 0,
            'pending_recipes'       => $statuses['pending'] ?? 0,
            'featured'              => $featured,
            'average_rating'        => round($avg_rating, 2),
            'total_categories'      => $total_categories,
            'total_cuisines'        => $total_cuisines,
            'total_collections'     => $total_collections,
            'total_views'           => $total_views,
            'total_favorites'       => $total_favorites,
            'rating_distribution'   => $rating_distribution,
            'recipes_by_category'   => $recipes_by_category,
            'recipes_by_cuisine'    => $recipes_by_cuisine,
            'recipes_by_difficulty' => $recipes_by_difficulty,
            'recent_recipes'        => $recent_recipes,
            'recent_reviews'        => $recent_reviews,
            'total_reviews'         => $total_reviews,
            'pending_reviews'       => $pending_reviews,
        ]);
    }

    /**
     * Reset all demo data and re-seed.
     */
    public function reset_demo_data($request): WP_REST_Response|WP_Error {
        // DemoData lives in pro/includes/ and is absent from the free build, so
        // class_exists() is the only gate there is. The free admin registers no UI that
        // calls this route, so in the free build it is simply unreachable.
        if (!class_exists(DemoData::class)) {
            return new WP_Error(
                'plugixarecipe_not_available',
                __('Demo data is not available.', 'plugixarecipe'),
                ['status' => 404]
            );
        }

        DemoData::reset();

        Cache::flush(...Cache::ENTITIES);

        return $this->success(null, __('Demo data reset successfully.', 'plugixarecipe'));
    }
}
