<?php
/**
 * GGM Commerce Taxonomy Management
 * Handles categories and tags with ID-based relationships
 */

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
// phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter

class GGM_Commerce_Taxonomy {
    private static $instance = null;

    /** @var array Per-request cache for product categories */
    private static $product_categories_cache = [];

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

    public function __construct() {
        // AJAX handlers
        add_action('wp_ajax_ggm_commerce_create_category', [$this, 'ajax_create_category']);
        add_action('wp_ajax_ggm_commerce_update_category', [$this, 'ajax_update_category']);
        add_action('wp_ajax_ggm_commerce_delete_category', [$this, 'ajax_delete_category']);
        add_action('wp_ajax_ggm_commerce_create_tag', [$this, 'ajax_create_tag']);
        add_action('wp_ajax_ggm_commerce_update_tag', [$this, 'ajax_update_tag']);
        add_action('wp_ajax_ggm_commerce_delete_tag', [$this, 'ajax_delete_tag']);
    }

    /**
     * Clear filter widget caches for categories and tags.
     * Called when categories/tags are created, updated, or deleted.
     *
     * @param string $type Type of cache to clear: 'categories', 'tags', or 'all'.
     * @return void
     */
    public function clear_filter_cache( $type = 'all' ) {
        if ( 'categories' === $type || 'all' === $type ) {
            delete_transient( 'ggmc_filter_categories_options' );
        }
        if ( 'tags' === $type || 'all' === $type ) {
            delete_transient( 'ggmc_filter_tags_options' );
        }
    }

    /**
     * Clear product price range cache.
     * Called when products are created, updated, or deleted.
     *
     * @return void
     */
    public static function clear_price_range_cache() {
        delete_transient( 'ggmc_product_price_range' );
    }

    /**
     * Get all categories
     */
    public function get_categories($args = []) {
        global $wpdb;

        $defaults = [
            'status' => 'active',
            'parent_id' => null,
            'orderby' => 'display_order',
            'order' => 'ASC'
        ];

        $args = wp_parse_args($args, $defaults);

        $sql = "SELECT * FROM {$wpdb->prefix}ggmc_categories WHERE 1=1";

        if ($args['status']) {
            $sql .= $wpdb->prepare(" AND status = %s", $args['status']);
        }

        if ($args['parent_id'] !== null) {
            $sql .= $wpdb->prepare(" AND parent_id = %d", $args['parent_id']);
        }

        $sql .= " ORDER BY {$args['orderby']} {$args['order']}";

        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $sql is built safely with wpdb->prepare, orderby/order are validated
        return $wpdb->get_results($sql);
    }

    /**
     * Get category by ID or slug
     */
    public function get_category($identifier, $by_slug = false) {
        global $wpdb;

        if ($by_slug) {
            return $wpdb->get_row($wpdb->prepare(
                "SELECT * FROM {$wpdb->prefix}ggmc_categories WHERE slug = %s",
                $identifier
            ));
        } else {
            return $wpdb->get_row($wpdb->prepare(
                "SELECT * FROM {$wpdb->prefix}ggmc_categories WHERE id = %d",
                $identifier
            ));
        }
    }

    /**
     * Get uncategorised category
     */
    public function get_uncategorised_category() {
        return $this->get_category('uncategorised', true);
    }

    /**
     * Get default category (either from settings or uncategorised)
     */
    public function get_default_category() {
        $options = get_option('ggm_commerce_catalog_options');
        $default_category_id = isset($options['default_category']) ? intval($options['default_category']) : 0;

        if ($default_category_id > 0) {
            $category = $this->get_category($default_category_id);
            if ($category) {
                return $category;
            }
        }

        // Fallback to uncategorised
        return $this->get_uncategorised_category();
    }

    /**
     * Create category
     */
    public function create_category($data) {
        global $wpdb;

        // Validate required fields
        if (empty($data['name'])) {
            return new WP_Error('missing_name', 'Category name is required');
        }

        // Generate slug if not provided
        if (empty($data['slug'])) {
            $data['slug'] = sanitize_title($data['name']);
        }

        // Check if slug exists
        $existing = $this->get_category($data['slug'], true);
        if ($existing) {
            return new WP_Error('slug_exists', 'Category with this slug already exists');
        }

        $insert_data = [
            'name' => sanitize_text_field($data['name']),
            'slug' => sanitize_title($data['slug']),
            'description' => sanitize_textarea_field($data['description'] ?? ''),
            'parent_id' => intval($data['parent_id'] ?? 0),
            'display_order' => intval($data['display_order'] ?? 0),
            'image_id' => intval($data['image_id'] ?? 0),
            'status' => sanitize_text_field($data['status'] ?? 'active'),
            'created_at' => current_time('mysql'),
            'updated_at' => current_time('mysql')
        ];

        $inserted = $wpdb->insert("{$wpdb->prefix}ggmc_categories", $insert_data);

        if ($inserted) {
            $this->clear_filter_cache( 'categories' );
            return $wpdb->insert_id;
        }

        return new WP_Error('insert_failed', 'Failed to create category');
    }

    /**
     * Update category
     */
    public function update_category($category_id, $data) {
        global $wpdb;

        $category = $this->get_category($category_id);
        if (!$category) {
            return new WP_Error('not_found', 'Category not found');
        }

        // Prevent updating uncategorised category
        if ($category->slug === 'uncategorised') {
            return new WP_Error('protected', 'Cannot update the uncategorised category');
        }

        $update_data = [];

        if (isset($data['name'])) {
            $update_data['name'] = sanitize_text_field($data['name']);
        }

        if (isset($data['slug'])) {
            $new_slug = sanitize_title($data['slug']);
            // Regenerate slug from name if submitted empty
            if (empty($new_slug) && !empty($data['name'])) {
                $new_slug = sanitize_title($data['name']);
            }
            if (!empty($new_slug) && $new_slug !== $category->slug) {
                $existing = $this->get_category($new_slug, true);
                if ($existing) {
                    return new WP_Error('slug_exists', 'Category with this slug already exists');
                }
                $update_data['slug'] = $new_slug;
            }
        }

        if (isset($data['description'])) {
            $update_data['description'] = sanitize_textarea_field($data['description']);
        }

        if (isset($data['parent_id'])) {
            $update_data['parent_id'] = intval($data['parent_id']);
        }

        if (isset($data['display_order'])) {
            $update_data['display_order'] = intval($data['display_order']);
        }

        if (isset($data['image_id'])) {
            $update_data['image_id'] = intval($data['image_id']);
        }

        if (isset($data['status'])) {
            $update_data['status'] = sanitize_text_field($data['status']);
        }

        if (!empty($update_data)) {
            $update_data['updated_at'] = current_time('mysql');

            $updated = $wpdb->update(
                "{$wpdb->prefix}ggmc_categories",
                $update_data,
                ['id' => $category_id]
            );

            if ($updated !== false) {
                $this->clear_filter_cache( 'categories' );
                return true;
            }

            return new WP_Error('update_failed', 'Failed to update category');
        }

        return true;
    }

    /**
     * Delete category (moves products to specified or default category)
     *
     * @param int      $category_id        The category ID to delete.
     * @param int|null $target_category_id Optional. Category ID to move orphaned products to. If null, uses default.
     * @return true|WP_Error True on success, WP_Error on failure.
     */
    public function delete_category( $category_id, $target_category_id = null ) {
        global $wpdb;

        $category = $this->get_category($category_id);
        if (!$category) {
            return new WP_Error('not_found', 'Category not found');
        }

        // Check if this is uncategorised
        $is_uncategorised = ($category->slug === 'uncategorised');

        // Determine the target category for orphaned products
        if ( null !== $target_category_id && $target_category_id > 0 ) {
            // User specified a target category
            $target_category = $this->get_category( $target_category_id );
            if ( ! $target_category ) {
                return new WP_Error( 'invalid_target', 'The selected target category does not exist.' );
            }
            // Make sure we're not targeting the same category being deleted
            if ( intval( $target_category->id ) === intval( $category_id ) ) {
                return new WP_Error( 'invalid_target', 'Cannot move products to the category being deleted.' );
            }
        } elseif ($is_uncategorised) {
            // Check if a default category is set
            $options = get_option('ggm_commerce_catalog_options');
            $default_category_id = isset($options['default_category']) ? intval($options['default_category']) : 0;

            if ($default_category_id <= 0) {
                return new WP_Error('protected', 'Cannot delete Uncategorised. Please set a default category in Settings first.');
            }

            // Verify the default category exists
            $default_category = $this->get_category($default_category_id);
            if (!$default_category) {
                return new WP_Error('protected', 'Cannot delete Uncategorised. The default category no longer exists.');
            }

            // Use the default category for orphaned products
            $target_category = $default_category;
        } else {
            // Get default category (will be uncategorised or custom default)
            $target_category = $this->get_default_category();
        }

        // For products that ONLY have this category, add the target category
        // Get products that only have this category
        $products_only_this_category = $wpdb->get_col($wpdb->prepare(
            "SELECT pc.product_id FROM {$wpdb->prefix}ggmc_product_categories pc
             WHERE pc.category_id = %d
             AND NOT EXISTS (
                 SELECT 1 FROM {$wpdb->prefix}ggmc_product_categories pc2
                 WHERE pc2.product_id = pc.product_id AND pc2.category_id != %d
             )",
            $category_id,
            $category_id
        ));

        // Add target category to products that will be orphaned
        foreach ($products_only_this_category as $product_id) {
            $wpdb->insert(
                "{$wpdb->prefix}ggmc_product_categories",
                [
                    'product_id' => $product_id,
                    'category_id' => $target_category->id
                ],
                ['%d', '%d']
            );
        }

        // Delete all relationships for this category from the relationship table
        $wpdb->delete(
            "{$wpdb->prefix}ggmc_product_categories",
            ['category_id' => $category_id],
            ['%d']
        );

        // Delete the category
        $deleted = $wpdb->delete(
            "{$wpdb->prefix}ggmc_categories",
            ['id' => $category_id]
        );

        if ($deleted) {
            // Also handle child categories - move them to parent or root
            $wpdb->update(
                "{$wpdb->prefix}ggmc_categories",
                ['parent_id' => $category->parent_id],
                ['parent_id' => $category_id]
            );

            $this->clear_filter_cache( 'categories' );
            return true;
        }

        return new WP_Error('delete_failed', 'Failed to delete category');
    }

    /**
     * Get all tags
     * @param string $orderby Column to sort by
     * @param string $order Sort direction (ASC/DESC)
     * @param string|null $status Filter by status (active/inactive or null for all)
     */
    public function get_tags($orderby = 'name', $order = 'ASC', $status = null) {
        global $wpdb;

        $where = '';
        if ($status !== null) {
            $where = $wpdb->prepare(" WHERE status = %s", $status);
        }

        return $wpdb->get_results(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $where, $orderby, $order are all sanitized/validated
            "SELECT * FROM {$wpdb->prefix}ggmc_tags{$where} ORDER BY {$orderby} {$order}"
        );
    }

    /**
     * Get tag by ID or slug
     */
    public function get_tag($identifier, $by_slug = false) {
        global $wpdb;

        if ($by_slug) {
            return $wpdb->get_row($wpdb->prepare(
                "SELECT * FROM {$wpdb->prefix}ggmc_tags WHERE slug = %s",
                $identifier
            ));
        } else {
            return $wpdb->get_row($wpdb->prepare(
                "SELECT * FROM {$wpdb->prefix}ggmc_tags WHERE id = %d",
                $identifier
            ));
        }
    }

    /**
     * Create tag
     */
    public function create_tag($data) {
        global $wpdb;

        if (empty($data['name'])) {
            return new WP_Error('missing_name', 'Tag name is required');
        }

        if (empty($data['slug'])) {
            $data['slug'] = sanitize_title($data['name']);
        }

        // Check if slug exists
        $existing = $this->get_tag($data['slug'], true);
        if ($existing) {
            return new WP_Error('slug_exists', 'Tag with this slug already exists');
        }

        $insert_data = [
            'name' => sanitize_text_field($data['name']),
            'slug' => sanitize_title($data['slug']),
            'description' => sanitize_textarea_field($data['description'] ?? ''),
            'status' => sanitize_text_field($data['status'] ?? 'active'),
            'created_at' => current_time('mysql'),
            'updated_at' => current_time('mysql')
        ];

        $inserted = $wpdb->insert("{$wpdb->prefix}ggmc_tags", $insert_data);

        if ($inserted) {
            $this->clear_filter_cache( 'tags' );
            return $wpdb->insert_id;
        }

        return new WP_Error('insert_failed', 'Failed to create tag');
    }

    /**
     * Update tag
     */
    public function update_tag($tag_id, $data) {
        global $wpdb;

        $tag = $this->get_tag($tag_id);
        if (!$tag) {
            return new WP_Error('not_found', 'Tag not found');
        }

        $update_data = [];

        if (isset($data['name'])) {
            $update_data['name'] = sanitize_text_field($data['name']);
        }

        if (isset($data['slug'])) {
            $new_slug = sanitize_title($data['slug']);
            // Regenerate slug from name if submitted empty
            if (empty($new_slug) && !empty($data['name'])) {
                $new_slug = sanitize_title($data['name']);
            }
            if (!empty($new_slug) && $new_slug !== $tag->slug) {
                $existing = $this->get_tag($new_slug, true);
                if ($existing) {
                    return new WP_Error('slug_exists', 'Tag with this slug already exists');
                }
                $update_data['slug'] = $new_slug;
            }
        }

        if (isset($data['description'])) {
            $update_data['description'] = sanitize_textarea_field($data['description']);
        }

        if (isset($data['status'])) {
            $update_data['status'] = sanitize_text_field($data['status']);
        }

        if (!empty($update_data)) {
            $update_data['updated_at'] = current_time('mysql');

            $updated = $wpdb->update(
                "{$wpdb->prefix}ggmc_tags",
                $update_data,
                ['id' => $tag_id]
            );

            if ($updated !== false) {
                $this->clear_filter_cache( 'tags' );
                return true;
            }

            return new WP_Error('update_failed', 'Failed to update tag');
        }

        return true;
    }

    /**
     * Delete tag (removes from product_tags junction table)
     */
    public function delete_tag($tag_id) {
        global $wpdb;

        $tag = $this->get_tag($tag_id);
        if (!$tag) {
            return new WP_Error('not_found', 'Tag not found');
        }

        // Delete all relationships for this tag from the junction table
        $wpdb->delete(
            "{$wpdb->prefix}ggmc_product_tags",
            ['tag_id' => $tag_id],
            ['%d']
        );

        // Delete the tag
        $deleted = $wpdb->delete(
            "{$wpdb->prefix}ggmc_tags",
            ['id' => $tag_id]
        );

        if ($deleted) {
            $this->clear_filter_cache( 'tags' );
            return true;
        }

        return new WP_Error('delete_failed', 'Failed to delete tag');
    }

    /**
     * Get product categories (from product_categories relationship table)
     * Returns multiple categories for a product
     */
    public function get_product_categories($product_id) {
        if (isset(self::$product_categories_cache[$product_id])) {
            return self::$product_categories_cache[$product_id];
        }

        global $wpdb;

        // Query the relationship table to get all categories for this product
        $categories = $wpdb->get_results($wpdb->prepare(
            "SELECT c.* FROM {$wpdb->prefix}ggmc_categories c
             INNER JOIN {$wpdb->prefix}ggmc_product_categories pc ON c.id = pc.category_id
             WHERE pc.product_id = %d AND c.status = 'active'
             ORDER BY c.name ASC",
            $product_id
        ));

        $result = $categories ?: [];
        self::$product_categories_cache[$product_id] = $result;
        return $result;
    }

    /**
     * Get product tags (from product_tags junction table)
     * Returns multiple tags for a product (active tags only)
     */
    public function get_product_tags($product_id) {
        global $wpdb;

        // Query the relationship table to get all active tags for this product
        $tags = $wpdb->get_results($wpdb->prepare(
            "SELECT t.* FROM {$wpdb->prefix}ggmc_tags t
             INNER JOIN {$wpdb->prefix}ggmc_product_tags pt ON t.id = pt.tag_id
             WHERE pt.product_id = %d AND t.status = 'active'
             ORDER BY t.name ASC",
            $product_id
        ));

        return $tags ?: [];
    }

    /**
     * Set product categories (uses product_categories relationship table)
     * Supports multiple categories per product
     */
    public function set_product_categories($product_id, $category_ids) {
        global $wpdb;

        // Clean and validate category IDs
        if (!empty($category_ids) && is_array($category_ids)) {
            $category_ids = array_map('intval', array_filter($category_ids));
        } else {
            $category_ids = [];
        }

        // If no categories, assign to uncategorised
        if (empty($category_ids)) {
            $uncategorised = $this->get_uncategorised_category();
            $category_ids = [$uncategorised->id];
        }

        // Delete existing category relationships for this product
        $wpdb->delete(
            "{$wpdb->prefix}ggmc_product_categories",
            ['product_id' => $product_id],
            ['%d']
        );

        // Insert new category relationships
        foreach ($category_ids as $category_id) {
            if ($category_id > 0) {
                $wpdb->insert(
                    "{$wpdb->prefix}ggmc_product_categories",
                    [
                        'product_id' => $product_id,
                        'category_id' => $category_id
                    ],
                    ['%d', '%d']
                );
            }
        }

        return true;
    }

    /**
     * Set product tags (uses product_tags junction table)
     * Supports multiple tags per product
     */
    public function set_product_tags($product_id, $tag_ids) {
        global $wpdb;

        // Clean and validate tag IDs
        if (!empty($tag_ids) && is_array($tag_ids)) {
            $tag_ids = array_map('intval', array_filter($tag_ids));
        } else {
            $tag_ids = [];
        }

        // Delete existing tag relationships for this product
        $wpdb->delete(
            "{$wpdb->prefix}ggmc_product_tags",
            ['product_id' => $product_id],
            ['%d']
        );

        // Insert new tag relationships
        foreach ($tag_ids as $tag_id) {
            if ($tag_id > 0) {
                $wpdb->insert(
                    "{$wpdb->prefix}ggmc_product_tags",
                    [
                        'product_id' => $product_id,
                        'tag_id' => $tag_id
                    ],
                    ['%d', '%d']
                );
            }
        }

        return true;
    }

    // AJAX Handlers
    public function ajax_create_category() {
        check_ajax_referer('ggm_commerce_admin', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Insufficient permissions']);
        }

        $result = $this->create_category($_POST);

        if (is_wp_error($result)) {
            wp_send_json_error(['message' => $result->get_error_message()]);
        } else {
            wp_send_json_success([
                'message' => 'Category created successfully',
                'category_id' => $result
            ]);
        }
    }

    public function ajax_update_category() {
        check_ajax_referer('ggm_commerce_admin', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Insufficient permissions']);
        }

        $category_id = intval($_POST['category_id'] ?? 0);
        $result = $this->update_category($category_id, $_POST);

        if (is_wp_error($result)) {
            wp_send_json_error(['message' => $result->get_error_message()]);
        } else {
            wp_send_json_success(['message' => 'Category updated successfully']);
        }
    }

    public function ajax_delete_category() {
        check_ajax_referer('ggm_commerce_admin', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Insufficient permissions']);
        }

        $category_id = intval($_POST['category_id'] ?? 0);
        $result = $this->delete_category($category_id);

        if (is_wp_error($result)) {
            wp_send_json_error(['message' => $result->get_error_message()]);
        } else {
            wp_send_json_success(['message' => 'Category deleted successfully']);
        }
    }

    public function ajax_create_tag() {
        check_ajax_referer('ggm_commerce_admin', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Insufficient permissions']);
        }

        $result = $this->create_tag($_POST);

        if (is_wp_error($result)) {
            wp_send_json_error(['message' => $result->get_error_message()]);
        } else {
            wp_send_json_success([
                'message' => 'Tag created successfully',
                'tag_id' => $result
            ]);
        }
    }

    public function ajax_update_tag() {
        check_ajax_referer('ggm_commerce_admin', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Insufficient permissions']);
        }

        $tag_id = intval($_POST['tag_id'] ?? 0);
        $result = $this->update_tag($tag_id, $_POST);

        if (is_wp_error($result)) {
            wp_send_json_error(['message' => $result->get_error_message()]);
        } else {
            wp_send_json_success(['message' => 'Tag updated successfully']);
        }
    }

    public function ajax_delete_tag() {
        check_ajax_referer('ggm_commerce_admin', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Insufficient permissions']);
        }

        $tag_id = intval($_POST['tag_id'] ?? 0);
        $result = $this->delete_tag($tag_id);

        if (is_wp_error($result)) {
            wp_send_json_error(['message' => $result->get_error_message()]);
        } else {
            wp_send_json_success(['message' => 'Tag deleted successfully']);
        }
    }
}

// Initialize taxonomy system
GGM_Commerce_Taxonomy::get_instance();
