<?php

namespace App\Console\Commands;

use App\CentralLogics\Helpers;
use App\Models\OrderDetail;
use App\Models\Review;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class RepairProductDisplayData extends Command
{
    protected $signature = 'repair:product-display-data {--chunk=200} {--dry-run}';

    protected $description = 'Repair broken order detail and review product references/display snapshots.';

    private ?array $orderDetailColumnAvailabilityCache = null;

    public function handle(): int
    {
        $chunk = max((int) $this->option('chunk'), 1);
        $dryRun = (bool) $this->option('dry-run');

        $this->info($dryRun
            ? 'Dry run শুরু হচ্ছে: broken product display data scan করা হবে, কিন্তু DB update হবে না।'
            : 'Broken product display data repair শুরু হচ্ছে...');

        $orderDetailStats = $this->repairOrderDetails($chunk, $dryRun);
        $reviewStats = $this->repairReviews($chunk, $dryRun);

        $this->newLine();
        $this->table(
            ['Section', 'Scanned', 'Updated'],
            [
                ['order_details', $orderDetailStats['scanned'], $orderDetailStats['updated']],
                ['reviews', $reviewStats['scanned'], $reviewStats['updated']],
            ]
        );

        $this->info($dryRun ? 'Dry run শেষ হয়েছে।' : 'Repair complete.');

        return self::SUCCESS;
    }

    private function repairOrderDetails(int $chunk, bool $dryRun): array
    {
        $stats = ['scanned' => 0, 'updated' => 0];

        OrderDetail::withoutGlobalScopes()
            ->with([
                'item' => fn ($query) => $query->withoutGlobalScopes(),
                'campaign' => fn ($query) => $query->withoutGlobalScopes(),
                'order',
            ])
            ->orderBy('id')
            ->chunk($chunk, function ($details) use (&$stats, $dryRun) {
                foreach ($details as $detail) {
                    $stats['scanned']++;

                    $formattedDetail = Helpers::order_details_data_formatting([$detail])[0] ?? [];
                    $resolvedDetails = data_get($formattedDetail, 'item_details', []);
                    if (! is_array($resolvedDetails) || empty($resolvedDetails)) {
                        continue;
                    }

                    $updates = [];
                    $currentSnapshot = $this->resolveCurrentOrderDetailSnapshotValue($detail);
                    $resolvedSnapshot = json_encode($resolvedDetails, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

                    if ($resolvedSnapshot !== $currentSnapshot) {
                        foreach ($this->resolveWritableOrderDetailSnapshotColumns($detail) as $snapshotColumn) {
                            $updates[$snapshotColumn] = $resolvedSnapshot;
                        }
                    }

                    $resolvedItemId = (int) data_get($resolvedDetails, 'item_id', 0);
                    if ($this->orderDetailsHasColumn('item_id') && (int) ($detail->item_id ?? 0) <= 0 && $resolvedItemId > 0) {
                        $updates['item_id'] = $resolvedItemId;
                    }

                    $resolvedCampaignId = (int) data_get($resolvedDetails, 'item_campaign_id', 0);
                    if ($this->orderDetailsHasColumn('item_campaign_id')
                        && (int) ($detail->item_campaign_id ?? 0) <= 0
                        && $resolvedCampaignId > 0) {
                        $updates['item_campaign_id'] = $resolvedCampaignId;
                    }

                    $resolvedPrice = (float) data_get($formattedDetail, 'display_price', 0);
                    if ($this->orderDetailsHasColumn('price') && (float) ($detail->price ?? 0) <= 0 && $resolvedPrice > 0) {
                        $updates['price'] = $resolvedPrice;
                    }

                    $resolvedQuantity = (int) data_get($formattedDetail, 'display_quantity', 0);
                    if ($this->orderDetailsHasColumn('quantity') && (int) ($detail->quantity ?? 0) <= 0 && $resolvedQuantity > 0) {
                        $updates['quantity'] = $resolvedQuantity;
                    }

                    if (empty($updates)) {
                        continue;
                    }

                    if (! $dryRun) {
                        DB::table('order_details')
                            ->where('id', $detail->id)
                            ->update($updates);
                    }

                    $stats['updated']++;
                }
            });

        return $stats;
    }

    private function repairReviews(int $chunk, bool $dryRun): array
    {
        $stats = ['scanned' => 0, 'updated' => 0];

        Review::withoutGlobalScopes()
            ->with('order')
            ->orderBy('id')
            ->chunk($chunk, function ($reviews) use (&$stats, $dryRun) {
                foreach ($reviews as $review) {
                    $stats['scanned']++;

                    $matchedOrderDetail = $this->findMatchingOrderDetailForReview($review);
                    if (! $matchedOrderDetail) {
                        continue;
                    }

                    $updates = [];

                    if ((int) ($review->item_id ?? 0) <= 0 && (int) ($matchedOrderDetail->item_id ?? 0) > 0) {
                        $updates['item_id'] = (int) $matchedOrderDetail->item_id;
                    }

                    if (Review::hasColumn('item_campaign_id')
                        && (int) ($review->item_campaign_id ?? 0) <= 0
                        && (int) ($matchedOrderDetail->item_campaign_id ?? 0) > 0) {
                        $updates['item_campaign_id'] = (int) $matchedOrderDetail->item_campaign_id;
                    }

                    if (Review::hasColumn('store_id')
                        && (int) ($review->store_id ?? 0) <= 0
                        && (int) ($matchedOrderDetail->order?->store_id ?? 0) > 0) {
                        $updates['store_id'] = (int) $matchedOrderDetail->order->store_id;
                    }

                    if (Review::hasColumn('module_id')
                        && (int) ($review->module_id ?? 0) <= 0
                        && (int) ($matchedOrderDetail->order?->module_id ?? 0) > 0) {
                        $updates['module_id'] = (int) $matchedOrderDetail->order->module_id;
                    }

                    if (empty($updates)) {
                        continue;
                    }

                    if (! $dryRun) {
                        DB::table('reviews')
                            ->where('id', $review->id)
                            ->update($updates);
                    }

                    $stats['updated']++;
                }
            });

        return $stats;
    }

    private function findMatchingOrderDetailForReview(Review $review): ?OrderDetail
    {
        if (! $review->order_id) {
            return null;
        }

        $query = OrderDetail::withoutGlobalScopes()
            ->with([
                'item' => fn ($builder) => $builder->withoutGlobalScopes(),
                'campaign' => fn ($builder) => $builder->withoutGlobalScopes(),
                'order',
            ])
            ->where('order_id', $review->order_id);

        $itemId = (int) ($review->item_id ?? 0);
        if ($this->orderDetailsHasColumn('item_id') && $itemId > 0) {
            $matchedByItem = (clone $query)->where('item_id', $itemId)->first();
            if ($matchedByItem) {
                return $matchedByItem;
            }
        }

        $campaignId = (int) ($review->item_campaign_id ?? 0);
        if ($this->orderDetailsHasColumn('item_campaign_id')
            && $campaignId > 0
            && Review::hasColumn('item_campaign_id')) {
            $matchedByCampaign = (clone $query)->where('item_campaign_id', $campaignId)->first();
            if ($matchedByCampaign) {
                return $matchedByCampaign;
            }
        }

        return $query->first();
    }

    private function normalizeJsonValue($value): string
    {
        if (is_array($value)) {
            return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        }

        if (is_string($value) && trim($value) !== '') {
            $decoded = json_decode($value, true);
            if (json_last_error() === JSON_ERROR_NONE) {
                return json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            }

            return trim($value);
        }

        return '';
    }

    private function orderDetailsHasColumn(string $column): bool
    {
        if ($this->orderDetailColumnAvailabilityCache === null) {
            $this->orderDetailColumnAvailabilityCache = [];
        }

        if (! array_key_exists($column, $this->orderDetailColumnAvailabilityCache)) {
            $this->orderDetailColumnAvailabilityCache[$column] = Schema::hasColumn('order_details', $column);
        }

        return (bool) $this->orderDetailColumnAvailabilityCache[$column];
    }

    private function resolveWritableOrderDetailSnapshotColumns(OrderDetail $detail): array
    {
        $availableColumns = array_values(array_filter(
            ['item_details', 'product_details', 'food_details'],
            fn (string $column) => $this->orderDetailsHasColumn($column)
        ));

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

        $populatedColumns = [];
        foreach ($availableColumns as $column) {
            $currentValue = $this->normalizeJsonValue($detail->getAttribute($column));
            if ($currentValue !== '') {
                $populatedColumns[] = $column;
            }
        }

        if (! empty($populatedColumns)) {
            return $populatedColumns;
        }

        return [reset($availableColumns)];
    }

    private function resolveCurrentOrderDetailSnapshotValue(OrderDetail $detail): string
    {
        foreach ($this->resolveWritableOrderDetailSnapshotColumns($detail) as $column) {
            $normalizedValue = $this->normalizeJsonValue($detail->getAttribute($column));
            if ($normalizedValue !== '') {
                return $normalizedValue;
            }
        }

        return '';
    }
}
