<?php

// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedNamespaceFound -- APD is the established namespace shared with the Pro add-on and retained for backward compatibility.
namespace APD\Support;

use APD\Domain\Product\ProductConfigBinder;
use APD\PostTypes\ProductConfigPostType;
use APD\PostTypes\UserDesignPostType;

defined('ABSPATH') || exit;

class QADiagnostics
{
    public const OPTION = 'apd_qa_checklist_results';

    public function getScenarioGroups() : array
    {
        return [
            [
                'id' => 'simple_product',
                'label' => __('Simple product flow', 'asuka-product-designer'),
                'items' => [
                    ['id' => 'simple_display', 'label' => __('Custom product appears on the single product page', 'asuka-product-designer')],
                    ['id' => 'simple_price', 'label' => __('Price updates live when the canvas changes', 'asuka-product-designer')],
                    ['id' => 'simple_cart', 'label' => __('Add to cart keeps the current design state', 'asuka-product-designer')],
                    ['id' => 'simple_resume', 'label' => __('Opening the saved design from cart restores the exact state', 'asuka-product-designer')],
                ],
            ],
            [
                'id' => 'variable_product',
                'label' => __('Variable product flow', 'asuka-product-designer'),
                'items' => [
                    ['id' => 'variation_parent_fallback', 'label' => __('Parent custom product is used only as fallback', 'asuka-product-designer')],
                    ['id' => 'variation_specific', 'label' => __('Variation-specific custom product overrides the parent binding', 'asuka-product-designer')],
                    ['id' => 'variation_reset', 'label' => __('Resetting the variation restores the native product image when no parent custom product exists', 'asuka-product-designer')],
                    ['id' => 'variation_price', 'label' => __('The active variation price is used as the pricing base', 'asuka-product-designer')],
                ],
            ],
            [
                'id' => 'display_modes',
                'label' => __('Display modes', 'asuka-product-designer'),
                'items' => [
                    ['id' => 'mode_condensed', 'label' => __('Condensed mode replaces the native gallery correctly', 'asuka-product-designer')],
                    ['id' => 'mode_modal', 'label' => __('Modal mode opens, updates the preview, and resumes correctly', 'asuka-product-designer')],
                    ['id' => 'mode_full_width', 'label' => __('Full width mode behaves correctly on the active theme', 'asuka-product-designer')],
                ],
            ],
            [
                'id' => 'pricing',
                'label' => __('Pricing and rules', 'asuka-product-designer'),
                'items' => [
                    ['id' => 'pricing_global_rules', 'label' => __('Global pricing rules attached to the custom product are applied', 'asuka-product-designer')],
                    ['id' => 'pricing_breakdown', 'label' => __('The pricing breakdown is visible and matches the actual total', 'asuka-product-designer')],
                    ['id' => 'pricing_cart_order', 'label' => __('Cart and order totals stay aligned with the product page price', 'asuka-product-designer')],
                ],
            ],
            [
                'id' => 'customer_journey',
                'label' => __('Customer journey', 'asuka-product-designer'),
                'items' => [
                    ['id' => 'customer_cart_card', 'label' => __('The cart shows the premium customized product card', 'asuka-product-designer')],
                    ['id' => 'customer_order_page', 'label' => __('My account / order details show the premium customization section', 'asuka-product-designer')],
                    ['id' => 'customer_reopen', 'label' => __('Saved designs reopen correctly from cart, thank-you page, and account orders', 'asuka-product-designer')],
                ],
            ],
            [
                'id' => 'production',
                'label' => __('Production and workshop', 'asuka-product-designer'),
                'items' => [
                    ['id' => 'production_page', 'label' => __('The Production page lists customized order items with previews', 'asuka-product-designer')],
                    ['id' => 'production_exports', 'label' => __('Production exports and printable worksheet download correctly', 'asuka-product-designer')],
                    ['id' => 'production_notes', 'label' => __('Production status and workshop notes persist correctly', 'asuka-product-designer')],
                ],
            ],
            [
                'id' => 'admin_catalogs',
                'label' => __('Admin libraries', 'asuka-product-designer'),
                'items' => [
                    ['id' => 'catalog_crud', 'label' => __('Design / font / color / shape CRUD works after refresh', 'asuka-product-designer')],
                    ['id' => 'catalog_import', 'label' => __('Bulk JSON import and export work as expected', 'asuka-product-designer')],
                    ['id' => 'catalog_media_import', 'label' => __('Media Library bulk import works for Designs and Shapes', 'asuka-product-designer')],
                ],
            ],
        ];
    }

    public function getStatusOptions() : array
    {
        return [
            'not_tested' => __('Not tested', 'asuka-product-designer'),
            'passed' => __('Passed', 'asuka-product-designer'),
            'failed' => __('Failed', 'asuka-product-designer'),
            'blocked' => __('Blocked', 'asuka-product-designer'),
        ];
    }

    public function getResults() : array
    {
        return $this->normalizeResults(get_option(self::OPTION, []));
    }

    public function normalizeResults(mixed $input) : array
    {
        $input = is_array($input) ? $input : [];
        $normalized = [];
        $statuses = array_keys($this->getStatusOptions());

        foreach ($this->getScenarioGroups() as $group) {
            foreach ((array) ($group['items'] ?? []) as $item) {
                $id = sanitize_key((string) ($item['id'] ?? ''));
                if ($id === '') {
                    continue;
                }

                $entry = is_array($input[$id] ?? null) ? $input[$id] : [];
                $status = sanitize_key((string) ($entry['status'] ?? 'not_tested'));
                if (! in_array($status, $statuses, true)) {
                    $status = 'not_tested';
                }

                $normalized[$id] = [
                    'status' => $status,
                    'notes' => sanitize_textarea_field((string) ($entry['notes'] ?? '')),
                    'updatedAt' => sanitize_text_field((string) ($entry['updatedAt'] ?? '')),
                ];
            }
        }

        return $normalized;
    }

    public function saveResults(array $input) : array
    {
        $results = $this->normalizeResults($input);
        $timestamp = current_time('mysql');
        foreach ($results as $id => $entry) {
            if (($entry['status'] ?? 'not_tested') !== 'not_tested' || ($entry['notes'] ?? '') !== '') {
                $results[$id]['updatedAt'] = $timestamp;
            }
        }
        update_option(self::OPTION, $results, false);
        return $results;
    }

    public function resetResults() : void
    {
        delete_option(self::OPTION);
    }

    public function buildSummary(array $results = []) : array
    {
        $results = $results ?: $this->getResults();
        $summary = [
            'total' => 0,
            'passed' => 0,
            'failed' => 0,
            'blocked' => 0,
            'not_tested' => 0,
        ];

        foreach ($results as $entry) {
            $summary['total'] += 1;
            $status = (string) ($entry['status'] ?? 'not_tested');
            if (! isset($summary[$status])) {
                $status = 'not_tested';
            }
            $summary[$status] += 1;
        }

        return $summary;
    }

    public function buildDiagnostics() : array
    {
        $assetCatalog = new AssetCatalog();
        $readiness = (new ProductionReadiness())->evaluate();
        $commercialService = new CommercialRuntimeConfig();
        $commercialRuntime = $commercialService->build();
        $license = is_array($commercialRuntime['license'] ?? null) ? $commercialRuntime['license'] : [];
        $entitlements = $commercialService->entitlementsFromLicense($license);
        $licensedAddons = DesignerAssets::addons((array) ($entitlements['features'] ?? []));

        return [
            'production_readiness' => [
                'label' => __('Production readiness', 'asuka-product-designer'),
                'summary' => sprintf(
                    /* translators: 1: passed checks, 2: warnings, 3: failed checks. */
                    __('%1$d passed, %2$d warning(s), %3$d failed', 'asuka-product-designer'),
                    $readiness['summary']['passed'],
                    $readiness['summary']['warnings'],
                    $readiness['summary']['failed']
                ),
                'status' => $readiness['ready'] ? ($readiness['summary']['warnings'] > 0 ? 'warning' : 'pass') : 'fail',
                'items' => array_map(static fn(array $check) : array => [
                    'label' => $check['label'],
                    'value' => $check['detail'],
                    'status' => $check['status'],
                ], $readiness['checks']),
            ],
            'environment' => [
                'label' => __('Environment', 'asuka-product-designer'),
                'items' => [
                    ['label' => __('Plugin version', 'asuka-product-designer'), 'value' => defined('ASUKA_PRODUCT_DESIGNER_VERSION') ? ASUKA_PRODUCT_DESIGNER_VERSION : '—'],
                    ['label' => __('WooCommerce active', 'asuka-product-designer'), 'value' => class_exists('WooCommerce') ? __('Yes', 'asuka-product-designer') : __('No', 'asuka-product-designer')],
                    ['label' => __('REST API upload endpoint', 'asuka-product-designer'), 'value' => esc_url_raw(rest_url('asuka-designer/v1/upload'))],
                    ['label' => __('PHP version', 'asuka-product-designer'), 'value' => PHP_VERSION],
                    ['label' => __('GD available', 'asuka-product-designer'), 'value' => extension_loaded('gd') ? __('Yes', 'asuka-product-designer') : __('No', 'asuka-product-designer')],
                    ['label' => __('Imagick available', 'asuka-product-designer'), 'value' => extension_loaded('imagick') ? __('Yes', 'asuka-product-designer') : __('No', 'asuka-product-designer')],
                ],
            ],
            'runtime' => [
                'label' => __('Designer runtime & licensing', 'asuka-product-designer'),
                'items' => [
                    ['label' => __('AsukaDesigner version', 'asuka-product-designer'), 'value' => DesignerAssets::designerVersion()],
                    ['label' => __('Core bundle available', 'asuka-product-designer'), 'value' => DesignerAssets::exists(DesignerAssets::coreFile()) ? __('Yes', 'asuka-product-designer') : __('No', 'asuka-product-designer')],
                    ['label' => __('Fabric renderer available', 'asuka-product-designer'), 'value' => DesignerAssets::exists(DesignerAssets::entrypoint('fabric')) ? __('Yes', 'asuka-product-designer') : __('No', 'asuka-product-designer')],
                    ['label' => __('Babylon renderer available', 'asuka-product-designer'), 'value' => DesignerAssets::exists(DesignerAssets::entrypoint('babylon')) ? __('Yes', 'asuka-product-designer') : __('No', 'asuka-product-designer')],
                    ['label' => __('License token resolved', 'asuka-product-designer'), 'value' => ! empty($license['token']) ? __('Yes', 'asuka-product-designer') : __('No', 'asuka-product-designer')],
                    ['label' => __('Resolved license plan', 'asuka-product-designer'), 'value' => (string) ($entitlements['plan'] ?? 'free')],
                    ['label' => __('Licensed add-on chunks', 'asuka-product-designer'), 'value' => (string) count($licensedAddons)],
                    ['label' => __('Commercial API', 'asuka-product-designer'), 'value' => $commercialService->apiBaseUrl()],
                ],
            ],
            'content' => [
                'label' => __('Content & catalogs', 'asuka-product-designer'),
                'items' => [
                    ['label' => __('Custom products', 'asuka-product-designer'), 'value' => (string) $this->countPosts(ProductConfigPostType::TYPE)],
                    ['label' => __('Saved designs', 'asuka-product-designer'), 'value' => (string) $this->countPosts(UserDesignPostType::TYPE)],
                    ['label' => __('Design library items', 'asuka-product-designer'), 'value' => (string) count($assetCatalog->getCollection('designs'))],
                    ['label' => __('Font library items', 'asuka-product-designer'), 'value' => (string) count($assetCatalog->getCollection('fonts'))],
                    ['label' => __('Colors', 'asuka-product-designer'), 'value' => (string) count($assetCatalog->getCollection('colors'))],
                    ['label' => __('Printing methods', 'asuka-product-designer'), 'value' => (string) count($assetCatalog->getCollection('printing_methods'))],
                    ['label' => __('Shapes', 'asuka-product-designer'), 'value' => (string) count($assetCatalog->getCollection('shapes'))],
                    ['label' => __('Global pricing rules', 'asuka-product-designer'), 'value' => (string) count($assetCatalog->getCollection('pricing_rules'))],
                ],
            ],
            'bindings' => [
                'label' => __('WooCommerce bindings', 'asuka-product-designer'),
                'items' => [
                    ['label' => __('Parent product bindings', 'asuka-product-designer'), 'value' => (string) $this->countMetaBindings('_apd_custom_product_id', 'product')],
                    ['label' => __('Variation bindings', 'asuka-product-designer'), 'value' => (string) $this->countMetaBindings('_apd_custom_product_id', 'product_variation')],
                    ['label' => __('Products using full width mode', 'asuka-product-designer'), 'value' => (string) $this->countConfigModeUsage('full_width')],
                    ['label' => __('Products using modal mode', 'asuka-product-designer'), 'value' => (string) $this->countConfigModeUsage('modal')],
                    ['label' => __('Products using condensed mode', 'asuka-product-designer'), 'value' => (string) $this->countConfigModeUsage('condensed')],
                    ['label' => __('Legacy display mode meta entries', 'asuka-product-designer'), 'value' => (string) $this->countLegacyDisplayModeUsage()],
                ],
            ],
            'config_health' => [
                'label' => __('Config health', 'asuka-product-designer'),
                'items' => [
                    ['label' => __('Configs with initial document', 'asuka-product-designer'), 'value' => (string) $this->countConfigsWithMeta('_apd_initial_document')],
                    ['label' => __('Configs with initial state', 'asuka-product-designer'), 'value' => (string) $this->countConfigsWithMeta('_apd_initial_state')],
                    ['label' => __('Configs with permissions JSON', 'asuka-product-designer'), 'value' => (string) $this->countConfigsWithMeta('_apd_permissions')],
                    ['label' => __('Configs with front behavior JSON', 'asuka-product-designer'), 'value' => (string) $this->countConfigsWithMeta('_apd_front_behavior')],
                    ['label' => __('Configs still using pricingRules alias', 'asuka-product-designer'), 'value' => (string) $this->countConfigsUsingPricingAlias('pricingRules')],
                    ['label' => __('Configs exposing canonical rules key', 'asuka-product-designer'), 'value' => (string) $this->countConfigsUsingPricingAlias('rules')],
                ],
            ],
            'payloads' => [
                'label' => __('Payload & pricing contracts', 'asuka-product-designer'),
                'items' => [
                    ['label' => __('Saved designs with pricing snapshots', 'asuka-product-designer'), 'value' => (string) $this->countPostsWithMeta(UserDesignPostType::TYPE, '_apd_price_snapshot')],
                    ['label' => __('Saved designs with preview snapshots', 'asuka-product-designer'), 'value' => (string) $this->countPostsWithMeta(UserDesignPostType::TYPE, '_apd_preview_urls')],
                    ['label' => __('Custom products with both document and state', 'asuka-product-designer'), 'value' => (string) $this->countConfigsWithBothRestorePayloads()],
                ],
            ],
        ];
    }

    protected function countPosts(string $postType) : int
    {
        $counts = wp_count_posts($postType);
        if (! $counts) {
            return 0;
        }

        $total = 0;
        foreach ((array) $counts as $status => $count) {
            if ($status === 'auto-draft') {
                continue;
            }
            $total += (int) $count;
        }

        return $total;
    }

    protected function countMetaBindings(string $metaKey, string $postType) : int
    {
        global $wpdb;

        $sql = $wpdb->prepare(
            "SELECT COUNT(DISTINCT pm.post_id)
            FROM {$wpdb->postmeta} pm
            INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
            WHERE pm.meta_key = %s
              AND pm.meta_value <> ''
              AND pm.meta_value <> '0'
              AND p.post_type = %s
              AND p.post_status NOT IN ('trash', 'auto-draft')",
            $metaKey,
            $postType
        );

        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Diagnostics deliberately execute this prepared live count so their result is current.
        return (int) $wpdb->get_var($sql);
    }

    protected function countConfigModeUsage(string $mode) : int
    {
        global $wpdb;

        $sql = $wpdb->prepare(
            "SELECT COUNT(DISTINCT post_id)
            FROM {$wpdb->postmeta}
            WHERE meta_key = %s
              AND meta_value = %s",
            ProductConfigBinder::DISPLAY_MODE_META,
            $mode
        );

        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Diagnostics deliberately execute this prepared live count so their result is current.
        return (int) $wpdb->get_var($sql);
    }

    protected function countConfigsWithMeta(string $metaKey) : int
    {
        global $wpdb;

        $sql = $wpdb->prepare(
            "SELECT COUNT(DISTINCT pm.post_id)
            FROM {$wpdb->postmeta} pm
            INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
            WHERE pm.meta_key = %s
              AND pm.meta_value <> ''
              AND p.post_type = %s
              AND p.post_status NOT IN ('trash', 'auto-draft')",
            $metaKey,
            ProductConfigPostType::TYPE
        );

        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Diagnostics deliberately execute this prepared live count so their result is current.
        return (int) $wpdb->get_var($sql);
    }


    protected function countLegacyDisplayModeUsage() : int
    {
        global $wpdb;

        $sql = $wpdb->prepare(
            "SELECT COUNT(DISTINCT post_id)
            FROM {$wpdb->postmeta}
            WHERE meta_key = %s
              AND meta_value <> ''",
            '_apd_display_mode'
        );

        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Diagnostics deliberately execute this prepared live count so their result is current.
        return (int) $wpdb->get_var($sql);
    }

    protected function countPostsWithMeta(string $postType, string $metaKey) : int
    {
        global $wpdb;

        $sql = $wpdb->prepare(
            "SELECT COUNT(DISTINCT pm.post_id)
            FROM {$wpdb->postmeta} pm
            INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
            WHERE pm.meta_key = %s
              AND pm.meta_value <> ''
              AND p.post_type = %s
              AND p.post_status NOT IN ('trash', 'auto-draft')",
            $metaKey,
            $postType
        );

        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Diagnostics deliberately execute this prepared live count so their result is current.
        return (int) $wpdb->get_var($sql);
    }

    protected function countConfigsUsingPricingAlias(string $key) : int
    {
        $posts = get_posts([
            'post_type' => ProductConfigPostType::TYPE,
            'post_status' => ['publish', 'draft', 'private'],
            'posts_per_page' => -1,
            'fields' => 'ids',
            'no_found_rows' => true,
        ]);

        $count = 0;
        foreach ($posts as $postId) {
            $payload = Json::decode((string) get_post_meta((int) $postId, '_apd_pricing_rules', true), []);
            if (is_array($payload) && is_array($payload[$key] ?? null) && $payload[$key] !== []) {
                $count += 1;
            }
        }

        return $count;
    }

    protected function countConfigsWithBothRestorePayloads() : int
    {
        $posts = get_posts([
            'post_type' => ProductConfigPostType::TYPE,
            'post_status' => ['publish', 'draft', 'private'],
            'posts_per_page' => -1,
            'fields' => 'ids',
            'no_found_rows' => true,
        ]);

        $count = 0;
        foreach ($posts as $postId) {
            $document = trim((string) get_post_meta((int) $postId, '_apd_initial_document', true));
            $state = trim((string) get_post_meta((int) $postId, '_apd_initial_state', true));
            if ($document !== '' && $state !== '') {
                $count += 1;
            }
        }

        return $count;
    }

}
