<?php

namespace App\Services;

use App\Models\Materials\Material;
use App\Models\Products\Product;
use App\Models\Products\ProductComponent;
use App\Services\Products\ProductComponentResolver;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class ProductBomService extends Service
{
    /**
     * 목록: 제품/자재를 통합 반환
     * - 반환 형태 예:
     *   [
     *     { "id": 10, "ref_type": "PRODUCT", "ref_id": 3, "code": "P-003", "name": "모듈A", "quantity": "2.0000", "sort_order": 1, "is_default": 1 },
     *     { "id": 11, "ref_type": "MATERIAL", "ref_id": 5, "code": "M-005", "name": "알루미늄판", "unit":"EA", "quantity": "4.0000", "sort_order": 2 }
     *   ]
     */
    public function index(int $parentProductId, array $params)
    {
        $tenantId = $this->tenantId();

        // 부모 제품 유효성
        $this->assertProduct($tenantId, $parentProductId);

        $items = ProductComponent::query()
            ->where('tenant_id', $tenantId)
            ->where('parent_product_id', $parentProductId)
            ->orderBy('sort_order')
            ->get();

        // 리졸브(제품/자재) - ref_id 기준
        $productIds = $items->where('ref_type', 'PRODUCT')->pluck('ref_id')->filter()->unique()->values();
        $materialIds = $items->where('ref_type', 'MATERIAL')->pluck('ref_id')->filter()->unique()->values();

        $products = $productIds->isNotEmpty()
            ? Product::query()->where('tenant_id', $tenantId)->whereIn('id', $productIds)->get(['id', 'code', 'name', 'product_type', 'category_id'])->keyBy('id')
            : collect();

        $materials = $materialIds->isNotEmpty()
            ? Material::query()->where('tenant_id', $tenantId)->whereIn('id', $materialIds)->get(['id', 'material_code as code', 'name', 'unit', 'category_id'])->keyBy('id')
            : collect();

        return $items->map(function ($row) use ($products, $materials) {
            $base = [
                'id' => (int) $row->id,
                'ref_type' => $row->ref_type,
                'ref_id' => (int) $row->ref_id,
                'quantity' => $row->quantity,
                'sort_order' => (int) $row->sort_order,
                'is_default' => (int) $row->is_default,
            ];

            if ($row->ref_type === 'PRODUCT') {
                $p = $products->get($row->ref_id);

                return $base + [
                    'code' => $p?->code,
                    'name' => $p?->name,
                    'product_type' => $p?->product_type,
                    'category_id' => $p?->category_id,
                ];
            } else { // MATERIAL
                $m = $materials->get($row->ref_id);

                return $base + [
                    'code' => $m?->code,
                    'name' => $m?->name,
                    'unit' => $m?->unit,
                    'category_id' => $m?->category_id,
                ];
            }
        })->values();
    }

    /**
     * 일괄 업서트
     * items[]: { id?, ref_type: PRODUCT|MATERIAL, ref_id: int, quantity: number, sort_order?: int, is_default?: 0|1 }
     */
    public function bulkUpsert(int $parentProductId, array $items): array
    {
        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();

        $this->assertProduct($tenantId, $parentProductId);

        if (! is_array($items) || empty($items)) {
            throw new BadRequestHttpException(__('error.empty_items'));
        }

        $created = 0;
        $updated = 0;
        $createdIds = [];
        $updatedIds = [];

        DB::transaction(function () use ($tenantId, $userId, $parentProductId, $items, &$created, &$updated, &$createdIds, &$updatedIds) {
            foreach ($items as $it) {
                $payload = $this->validateItem($it);

                // ref 확인 & 자기참조 방지
                $this->assertReference($tenantId, $parentProductId, $payload['ref_type'], (int) $payload['ref_id']);

                if (! empty($it['id'])) {
                    $pc = ProductComponent::query()
                        ->where('tenant_id', $tenantId)
                        ->where('parent_product_id', $parentProductId)
                        ->find((int) $it['id']);
                    if (! $pc) {
                        throw new BadRequestHttpException(__('error.not_found'));
                    }

                    $pc->update([
                        'ref_type' => $payload['ref_type'],
                        'ref_id' => (int) $payload['ref_id'],
                        'quantity' => $payload['quantity'],
                        'sort_order' => $payload['sort_order'] ?? $pc->sort_order,
                        'is_default' => $payload['is_default'] ?? $pc->is_default,
                        'updated_by' => $userId,
                    ]);
                    $updated++;
                    $updatedIds[] = $pc->id;
                } else {
                    // 신규
                    $pc = ProductComponent::create([
                        'tenant_id' => $tenantId,
                        'parent_product_id' => $parentProductId,
                        'ref_type' => $payload['ref_type'],
                        'ref_id' => (int) $payload['ref_id'],
                        'quantity' => $payload['quantity'],
                        'sort_order' => $payload['sort_order'] ?? 0,
                        'is_default' => $payload['is_default'] ?? 0,
                        'created_by' => $userId,
                    ]);
                    $created++;
                    $createdIds[] = $pc->id;
                }
            }
        });

        return [
            'created' => $created,
            'updated' => $updated,
            'created_ids' => $createdIds,
            'updated_ids' => $updatedIds,
        ];
    }

    // 단건 수정
    public function update(int $parentProductId, int $itemId, array $data)
    {
        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();
        $this->assertProduct($tenantId, $parentProductId);

        $pc = ProductComponent::query()
            ->where('tenant_id', $tenantId)
            ->where('parent_product_id', $parentProductId)
            ->find($itemId);

        if (! $pc) {
            throw new BadRequestHttpException(__('error.not_found'));
        }

        $v = Validator::make($data, [
            'ref_type' => 'sometimes|in:PRODUCT,MATERIAL',
            'ref_id' => 'sometimes|integer',
            'quantity' => 'sometimes|numeric|min:0.0001',
            'sort_order' => 'sometimes|integer|min:0',
            'is_default' => 'sometimes|in:0,1',
        ]);
        $payload = $v->validate();

        if (isset($payload['ref_type']) || isset($payload['ref_id'])) {
            $refType = $payload['ref_type'] ?? $pc->ref_type;
            $refId = isset($payload['ref_id'])
                ? (int) $payload['ref_id']
                : (int) $pc->ref_id;

            $this->assertReference($tenantId, $parentProductId, $refType, $refId);

            $pc->ref_type = $refType;
            $pc->ref_id = $refId;
        }

        if (isset($payload['quantity'])) {
            $pc->quantity = $payload['quantity'];
        }
        if (isset($payload['sort_order'])) {
            $pc->sort_order = $payload['sort_order'];
        }
        if (isset($payload['is_default'])) {
            $pc->is_default = $payload['is_default'];
        }

        $pc->updated_by = $userId;
        $pc->save();

        return $pc->refresh();
    }

    // 삭제
    public function destroy(int $parentProductId, int $itemId): void
    {
        $tenantId = $this->tenantId();
        $this->assertProduct($tenantId, $parentProductId);

        $pc = ProductComponent::query()
            ->where('tenant_id', $tenantId)
            ->where('parent_product_id', $parentProductId)
            ->find($itemId);

        if (! $pc) {
            throw new BadRequestHttpException(__('error.not_found'));
        }
        $pc->delete();
    }

    // 정렬 변경
    public function reorder(int $parentProductId, array $items): void
    {
        $tenantId = $this->tenantId();
        $this->assertProduct($tenantId, $parentProductId);

        if (! is_array($items)) {
            throw new BadRequestHttpException(__('error.invalid_payload'));
        }

        DB::transaction(function () use ($tenantId, $parentProductId, $items) {
            foreach ($items as $row) {
                if (! isset($row['id'], $row['sort_order'])) {
                    continue;
                }
                ProductComponent::query()
                    ->where('tenant_id', $tenantId)
                    ->where('parent_product_id', $parentProductId)
                    ->where('id', (int) $row['id'])
                    ->update(['sort_order' => (int) $row['sort_order']]);
            }
        });
    }

    // 요약(간단 합계/건수)
    public function summary(int $parentProductId): array
    {
        $tenantId = $this->tenantId();
        $this->assertProduct($tenantId, $parentProductId);

        $items = ProductComponent::query()
            ->where('tenant_id', $tenantId)
            ->where('parent_product_id', $parentProductId)
            ->get();

        $cnt = $items->count();
        $cntP = $items->where('ref_type', 'PRODUCT')->count();
        $cntM = $items->where('ref_type', 'MATERIAL')->count();
        $qtySum = (string) $items->sum('quantity');

        return [
            'count' => $cnt,
            'count_product' => $cntP,
            'count_material' => $cntM,
            'quantity_sum' => $qtySum,
        ];
    }

    // 유효성 검사(중복/자기참조/음수 등)
    public function validateBom(int $parentProductId): array
    {
        $tenantId = $this->tenantId();
        $this->assertProduct($tenantId, $parentProductId);

        $items = ProductComponent::query()
            ->where('tenant_id', $tenantId)
            ->where('parent_product_id', $parentProductId)
            ->orderBy('sort_order')
            ->get();

        $errors = [];
        $seen = [];

        foreach ($items as $row) {
            if ($row->quantity <= 0) {
                $errors[] = ['id' => $row->id, 'error' => 'INVALID_QUANTITY'];
            }
            $key = $row->ref_type.':'.$row->ref_id;
            if (isset($seen[$key])) {
                $errors[] = ['id' => $row->id, 'error' => 'DUPLICATE_ITEM'];
            } else {
                $seen[$key] = true;
            }
            // 자기참조
            if ($row->ref_type === 'PRODUCT' && (int) $row->ref_id === (int) $parentProductId) {
                $errors[] = ['id' => $row->id, 'error' => 'SELF_REFERENCE'];
            }
        }

        return [
            'valid' => count($errors) === 0,
            'errors' => $errors,
        ];
    }

    // ---------- helpers ----------

    private function validateItem(array $it): array
    {
        $v = Validator::make($it, [
            'id' => 'nullable|integer',
            'ref_type' => 'required|in:PRODUCT,MATERIAL',
            'ref_id' => 'required|integer',
            'quantity' => 'required|numeric|min:0.0001',
            'sort_order' => 'nullable|integer|min:0',
            'is_default' => 'nullable|in:0,1',
        ]);

        return $v->validate();
    }

    private function assertProduct(int $tenantId, int $productId): void
    {
        $exists = Product::query()->where('tenant_id', $tenantId)->where('id', $productId)->exists();
        if (! $exists) {
            // ko: 제품 정보를 찾을 수 없습니다.
            throw new NotFoundHttpException(__('error.not_found_resource', ['resource' => '제품']));
        }
    }

    private function assertReference(int $tenantId, int $parentProductId, string $refType, int $refId): void
    {
        if ($refType === 'PRODUCT') {
            if ($refId === $parentProductId) {
                throw new BadRequestHttpException(__('error.invalid_payload')); // 자기참조 방지
            }
            $ok = Product::query()->where('tenant_id', $tenantId)->where('id', $refId)->exists();
            if (! $ok) {
                throw new BadRequestHttpException(__('error.not_found'));
            }
        } else {
            $ok = Material::query()->where('tenant_id', $tenantId)->where('id', $refId)->exists();
            if (! $ok) {
                throw new BadRequestHttpException(__('error.not_found'));
            }
        }
    }

    /**
     * 특정 제품의 BOM을 전체 교체(기존 삭제 → 새 데이터 일괄 삽입)
     * - $productId: products.id
     * - $payload: ['categories' => [ {id?, name?, items: [{ref_type, ref_id, quantity, sort_order?}, ...]}, ... ]]
     * 반환: ['deleted_count' => int, 'inserted_count' => int]
     */
    public function replaceBom(int $productId, array $payload): array
    {
        if ($productId <= 0) {
            throw new BadRequestHttpException(__('error.bad_request')); // 400
        }

        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();

        // 0) ====== 빈 카테고리 제거 ======
        $rawCats = Arr::get($payload, 'categories', []);
        $normalized = [];

        foreach ((array) $rawCats as $cat) {
            $catId = Arr::get($cat, 'id');
            $catName = Arr::get($cat, 'name');

            $items = array_values(array_filter((array) Arr::get($cat, 'items', []), function ($it) {
                $type = Arr::get($it, 'ref_type');
                $id = (int) Arr::get($it, 'ref_id');
                $qty = Arr::get($it, 'quantity');

                return in_array($type, ['MATERIAL', 'PRODUCT'], true)
                    && $id > 0
                    && is_numeric($qty);
            }));

            if (count($items) === 0) {
                continue; // 아이템 없으면 skip
            }

            $normalized[] = [
                'id' => $catId,
                'name' => $catName,
                'items' => $items,
            ];
        }

        // 🔕 전부 비었으면: 기존 BOM 전체 삭제 후 성공
        if (count($normalized) === 0) {
            $deleted = ProductComponent::where('tenant_id', $tenantId)
                ->where('parent_product_id', $productId)
                ->delete();

            return [
                'deleted_count' => $deleted,
                'inserted_count' => 0,
                'message' => '모든 BOM 항목이 비어 기존 데이터를 삭제했습니다.',
            ];
        }

        // 1) ====== 검증 ======
        $v = Validator::make(
            ['categories' => $normalized],
            [
                'categories' => ['required', 'array', 'min:1'],
                'categories.*.id' => ['nullable', 'integer'],
                'categories.*.name' => ['nullable', 'string', 'max:100'],
                'categories.*.items' => ['required', 'array', 'min:1'],
                'categories.*.items.*.ref_type' => ['required', 'in:MATERIAL,PRODUCT'],
                'categories.*.items.*.ref_id' => ['required', 'integer', 'min:1'],
                'categories.*.items.*.quantity' => ['required', 'numeric', 'min:0'],
                'categories.*.items.*.sort_order' => ['nullable', 'integer', 'min:0'],
            ]
        );
        if ($v->fails()) {
            throw new ValidationException($v, null, __('error.validation_failed'));
        }

        // 2) ====== 플랫 레코드 생성 (note 제거) ======
        $rows = [];
        $now = now();
        foreach ($normalized as $cat) {
            $catId = Arr::get($cat, 'id');
            $catName = Arr::get($cat, 'name');

            foreach ($cat['items'] as $idx => $item) {
                $rows[] = [
                    'tenant_id' => $tenantId,
                    'parent_product_id' => $productId,
                    'category_id' => $catId,
                    'category_name' => $catName,
                    'ref_type' => $item['ref_type'],
                    'ref_id' => (int) $item['ref_id'],
                    'quantity' => (string) $item['quantity'],
                    'sort_order' => isset($item['sort_order']) ? (int) $item['sort_order'] : $idx,
                    'created_by' => $userId,
                    'updated_by' => $userId,
                    'created_at' => $now,
                    'updated_at' => $now,
                ];
            }
        }

        // 3) ====== 트랜잭션: 기존 삭제 후 신규 삽입 ======
        return DB::transaction(function () use ($tenantId, $productId, $rows) {
            $deleted = ProductComponent::where('tenant_id', $tenantId)
                ->where('parent_product_id', $productId)
                ->delete();

            $inserted = 0;
            foreach (array_chunk($rows, 500) as $chunk) {
                $ok = ProductComponent::insert($chunk);
                $inserted += $ok ? count($chunk) : 0;
            }

            return [
                'deleted_count' => $deleted,
                'inserted_count' => $inserted,
                'message' => 'BOM 저장 성공',
            ];
        });
    }

    /** 제품별: 현재 BOM에 쓰인 카테고리 */
    public function listCategoriesForProduct(int $productId): array
    {
        if ($productId <= 0) {
            throw new BadRequestHttpException(__('error.bad_request'));
        }

        $tenantId = $this->tenantId();

        $rows = ProductComponent::query()
            ->where('tenant_id', $tenantId)
            ->where('parent_product_id', $productId)
            ->whereNotNull('category_name')
            ->select([
                DB::raw('category_id'),
                DB::raw('category_name'),
                DB::raw('COUNT(*) as count'),
            ])
            ->groupBy('category_id', 'category_name')
            ->orderByDesc('count')
            ->orderBy('category_name')
            ->get()
            ->toArray();

        return $rows;
    }

    /** 테넌트 전역: 자주 쓰인 카테고리 추천(+검색) */
    public function listCategoriesForTenant(?string $q, int $limit = 20): array
    {
        $tenantId = $this->tenantId();

        $query = ProductComponent::query()
            ->where('tenant_id', $tenantId)
            ->whereNotNull('category_name')
            ->select([
                DB::raw('category_id'),
                DB::raw('category_name'),
                DB::raw('COUNT(*) as count'),
            ])
            ->groupBy('category_id', 'category_name');

        if ($q) {
            $query->havingRaw('category_name LIKE ?', ["%{$q}%"]);
        }

        $rows = $query
            ->orderByDesc('count')
            ->orderBy('category_name')
            ->limit($limit > 0 ? $limit : 20)
            ->get()
            ->toArray();

        return $rows;
    }

    public function tree($request, int $productId): array
    {
        $depth = (int) $request->query('depth', config('products.default_tree_depth', 10));
        $resolver = app(ProductComponentResolver::class);

        // 트리 배열만 반환 (ApiResponse가 바깥에서 래핑)
        return $resolver->resolveTree($productId, $depth);
    }
}
