<?php

declare(strict_types=1);

namespace App\Business\Product;

use App\Models\DberpProduct;
use App\Models\DberpProductSku;
use App\Models\DberpProductSkuSpecValue;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

/**
 * Filament 商品规格组合表单的新增与更新（含规格取值选择）。
 */
final class ProductSkuFormService
{
    public function __construct(
        private readonly ProductSkuCombinationGenerator $combinationGenerator,
    ) {}

    /**
     * @param  array<string, mixed>  $data
     */
    public function createSkuFromRelationForm(DberpProduct $product, array $data): DberpProductSku
    {
        return DB::transaction(function () use ($product, $data): DberpProductSku {
            [$payload, $picks] = $this->extractPicks($product, $data);

            if ($product->spec_enabled) {
                if ($product->specGroups()->doesntExist()) {
                    throw ValidationException::withMessages([
                        'pick_spec' => (string) __('dberp.errors.product_sku_spec_groups_missing'),
                    ]);
                }
                $this->assertPicksComplete($product, $picks);
                $valueIds = array_values($picks);
                if ($this->combinationGenerator->findSkuWithExactSpecValueIds($product, $valueIds) !== null) {
                    throw ValidationException::withMessages([
                        'pick_spec' => (string) __('dberp.errors.product_sku_duplicate_spec_combo'),
                    ]);
                }
            }

            $fillable = (new DberpProductSku)->getFillable();
            $payload = Arr::only($payload, $fillable);
            unset($payload['product_id']);

            $sku = DberpProductSku::query()->create(array_merge($payload, [
                'product_id' => $product->getKey(),
            ]));

            if ($product->spec_enabled && $picks !== []) {
                $this->syncSkuSpecPicks($sku, $picks);
            }

            return $sku->fresh();
        });
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function updateSkuFromRelationForm(DberpProductSku $sku, DberpProduct $product, array $data): void
    {
        DB::transaction(function () use ($sku, $product, $data): void {
            [$payload, $picks] = $this->extractPicks($product, $data);

            if ($product->spec_enabled) {
                $this->assertPicksComplete($product, $picks);
                $valueIds = array_values($picks);
                $conflict = $this->combinationGenerator->findSkuWithExactSpecValueIds($product, $valueIds, (int) $sku->getKey());
                if ($conflict !== null) {
                    throw ValidationException::withMessages([
                        'pick_spec' => (string) __('dberp.errors.product_sku_duplicate_spec_combo'),
                    ]);
                }
            }

            $fillable = (new DberpProductSku)->getFillable();
            $payload = Arr::only($payload, $fillable);
            unset($payload['product_id']);

            $sku->update($payload);

            if ($product->spec_enabled) {
                DberpProductSkuSpecValue::query()->where('product_sku_id', $sku->getKey())->delete();
                $this->syncSkuSpecPicks($sku->fresh(), $picks);
            }
        });
    }

    /**
     * @param  array<string, mixed>  $data
     * @return array{0: array<string, mixed>, 1: array<int, int>}
     */
    private function extractPicks(DberpProduct $product, array $data): array
    {
        $picks = [];
        $prefix = 'pick_spec_g_';
        foreach (array_keys($data) as $key) {
            if (! is_string($key) || ! str_starts_with($key, $prefix)) {
                continue;
            }

            $groupId = (int) substr($key, strlen($prefix));
            if ($groupId <= 0) {
                continue;
            }

            $picks[$groupId] = (int) $data[$key];
            unset($data[$key]);
        }

        unset($data['id'], $data['created_at'], $data['updated_at']);

        return [$data, $picks];
    }

    /**
     * @param  array<int, int>  $picks  group_id => spec_value_id
     */
    private function assertPicksComplete(DberpProduct $product, array $picks): void
    {
        $groupIds = $product->specGroups()->orderBy('sort_order')->orderBy('id')->pluck('id')->all();
        foreach ($groupIds as $gid) {
            if (! array_key_exists((int) $gid, $picks)) {
                throw ValidationException::withMessages([
                    'pick_spec' => (string) __('dberp.errors.product_sku_spec_picks_incomplete'),
                ]);
            }
        }
    }

    /**
     * @param  array<int, int>  $picks  group_id => spec_value_id
     */
    private function syncSkuSpecPicks(DberpProductSku $sku, array $picks): void
    {
        foreach ($picks as $groupId => $valueId) {
            DberpProductSkuSpecValue::query()->create([
                'product_sku_id' => $sku->getKey(),
                'product_spec_group_id' => $groupId,
                'product_spec_value_id' => $valueId,
            ]);
        }
    }
}
