<?php

namespace Database\Seeders\Kyungdong;

use Database\Seeders\DummyDataSeeder;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

/**
 * 경동기업(tenant_id=287) 품목 기준 데이터 배포용 시더
 *
 * DELETE + 재삽입 방식. AUTO_INCREMENT ID 사용으로 환경별 충돌 없음.
 * 종속 테이블은 새 ID로 자동 매핑.
 *
 * 실행: php artisan db:seed --class="Database\Seeders\Kyungdong\KyungdongItemMasterSeeder"
 */
class KyungdongItemMasterSeeder extends Seeder
{
    private int $tenantId;

    private int $userId;

    private string $dataPath;

    /** @var array<int,int> 원본ID → 새ID */
    private array $pageMap = [];

    private array $sectionMap = [];

    private array $fieldMap = [];

    private array $categoryMap = [];

    /** @var array<string,int> code → 새ID */
    private array $itemMap = [];

    /** 기대 건수 (검증용) */
    private array $expectedCounts = [];

    public function run(): void
    {
        $this->tenantId = DummyDataSeeder::TENANT_ID;
        $this->userId = DummyDataSeeder::USER_ID;
        $this->dataPath = database_path('seeders/data/kyungdong');

        $this->command->info('=== 경동기업 품목 기준 데이터 시더 시작 ===');
        $this->command->info("  tenant_id: {$this->tenantId}");
        $this->command->newLine();

        DB::transaction(function () {
            $this->cleanup();

            $this->command->info('--- Phase 1: 독립 테이블 ---');
            $this->seedItemPages();
            $this->seedItemSections();
            $this->seedItemFields();
            $this->seedEntityRelationships();
            $this->updateDisplayConditions();

            $this->command->info('--- Phase 2: 기반 테이블 ---');
            $this->seedCategories();
            $this->seedItems();

            $this->command->info('--- Phase 3: 종속 테이블 ---');
            $this->seedItemDetails();
            $this->seedPrices();
        });

        $this->verify();
        $this->printSummary();
    }

    // ──────────────────────────────────────────────────────────────────
    // CLEANUP
    // ──────────────────────────────────────────────────────────────────

    private function cleanup(): void
    {
        $this->command->info('CLEANUP: tenant_id='.$this->tenantId.' 데이터 삭제');

        // FK 역순 삭제
        // prices - tenant_id로 직접 삭제
        $c = DB::table('prices')->where('tenant_id', $this->tenantId)->delete();
        $this->command->info("  prices: {$c}건 삭제");

        // item_details - items를 통해 삭제
        $itemIds = DB::table('items')->where('tenant_id', $this->tenantId)->pluck('id');
        $c = DB::table('item_details')->whereIn('item_id', $itemIds)->delete();
        $this->command->info("  item_details: {$c}건 삭제");

        // items
        $c = DB::table('items')->where('tenant_id', $this->tenantId)->delete();
        $this->command->info("  items: {$c}건 삭제");

        // categories - 자식 먼저 삭제
        $catIds = DB::table('categories')->where('tenant_id', $this->tenantId)->pluck('id');
        // 자식 (parent_id가 있는 것) 먼저
        DB::table('categories')
            ->where('tenant_id', $this->tenantId)
            ->whereNotNull('parent_id')
            ->delete();
        $c = DB::table('categories')->where('tenant_id', $this->tenantId)->delete();
        $this->command->info('  categories: '.count($catIds).'건 삭제');

        // entity_relationships
        $c = DB::table('entity_relationships')->where('tenant_id', $this->tenantId)->delete();
        $this->command->info("  entity_relationships: {$c}건 삭제");

        // item_fields
        $c = DB::table('item_fields')->where('tenant_id', $this->tenantId)->delete();
        $this->command->info("  item_fields: {$c}건 삭제");

        // item_sections
        $c = DB::table('item_sections')->where('tenant_id', $this->tenantId)->delete();
        $this->command->info("  item_sections: {$c}건 삭제");

        // item_pages
        $c = DB::table('item_pages')->where('tenant_id', $this->tenantId)->delete();
        $this->command->info("  item_pages: {$c}건 삭제");

        $this->command->newLine();
    }

    // ──────────────────────────────────────────────────────────────────
    // PHASE 1: 독립 테이블
    // ──────────────────────────────────────────────────────────────────

    private function seedItemPages(): void
    {
        $rows = $this->loadJson('item_pages.json');
        $this->expectedCounts['item_pages'] = count($rows);

        foreach ($rows as $row) {
            $originalId = $row['_original_id'];
            $data = $this->stripMeta($row);
            $data['tenant_id'] = $this->tenantId;
            $this->setAuditFields($data);

            $newId = DB::table('item_pages')->insertGetId($data);
            $this->pageMap[$originalId] = $newId;
        }

        $this->command->info('  item_pages: '.count($rows).'건 삽입');
    }

    private function seedItemSections(): void
    {
        $rows = $this->loadJson('item_sections.json');
        $this->expectedCounts['item_sections'] = count($rows);

        foreach ($rows as $row) {
            $originalId = $row['_original_id'];
            $data = $this->stripMeta($row);
            $data['tenant_id'] = $this->tenantId;
            $this->setAuditFields($data);

            $newId = DB::table('item_sections')->insertGetId($data);
            $this->sectionMap[$originalId] = $newId;
        }

        $this->command->info('  item_sections: '.count($rows).'건 삽입');
    }

    private function seedItemFields(): void
    {
        $rows = $this->loadJson('item_fields.json');
        $this->expectedCounts['item_fields'] = count($rows);

        foreach ($rows as $row) {
            $originalId = $row['_original_id'];
            $data = $this->stripMeta($row);
            $data['tenant_id'] = $this->tenantId;
            $this->setAuditFields($data);

            $newId = DB::table('item_fields')->insertGetId($data);
            $this->fieldMap[$originalId] = $newId;
        }

        $this->command->info('  item_fields: '.count($rows).'건 삽입');
    }

    private function seedEntityRelationships(): void
    {
        $rows = $this->loadJson('entity_relationships.json');
        $this->expectedCounts['entity_relationships'] = count($rows);
        $inserted = 0;

        foreach ($rows as $row) {
            $data = $this->stripMeta($row);
            $data['tenant_id'] = $this->tenantId;
            $this->setAuditFields($data);

            // parent_id 매핑
            $data['parent_id'] = $this->mapEntityId($data['parent_type'], $data['parent_id']);
            // child_id 매핑
            $data['child_id'] = $this->mapEntityId($data['child_type'], $data['child_id']);

            if ($data['parent_id'] === null || $data['child_id'] === null) {
                $this->command->warn("  entity_relationships: 매핑 실패 건 스킵 (parent_type={$row['parent_type']}, child_type={$row['child_type']})");

                continue;
            }

            DB::table('entity_relationships')->insert($data);
            $inserted++;
        }

        $this->expectedCounts['entity_relationships'] = $inserted;
        $this->command->info("  entity_relationships: {$inserted}건 삽입");
    }

    /**
     * item_fields의 display_condition 내 targetFieldIds를 새 ID로 치환
     */
    private function updateDisplayConditions(): void
    {
        if (empty($this->fieldMap)) {
            return;
        }

        $fields = DB::table('item_fields')
            ->where('tenant_id', $this->tenantId)
            ->whereNotNull('display_condition')
            ->get(['id', 'display_condition']);

        $updated = 0;
        foreach ($fields as $field) {
            $condition = json_decode($field->display_condition, true);
            if (! is_array($condition)) {
                continue;
            }

            $changed = false;
            $condition = $this->remapDisplayCondition($condition, $changed);

            if ($changed) {
                DB::table('item_fields')
                    ->where('id', $field->id)
                    ->update(['display_condition' => json_encode($condition)]);
                $updated++;
            }
        }

        if ($updated > 0) {
            $this->command->info("  display_condition 후처리: {$updated}건 업데이트");
        }
    }

    /**
     * display_condition 내 targetFieldIds 재귀적 치환
     */
    private function remapDisplayCondition(array $condition, bool &$changed): array
    {
        if (isset($condition['targetFieldIds']) && is_array($condition['targetFieldIds'])) {
            $newIds = [];
            foreach ($condition['targetFieldIds'] as $oldId) {
                if (isset($this->fieldMap[$oldId])) {
                    $newIds[] = $this->fieldMap[$oldId];
                    $changed = true;
                } else {
                    $newIds[] = $oldId;
                }
            }
            $condition['targetFieldIds'] = $newIds;
        }

        // targetFieldId (단일) 처리
        if (isset($condition['targetFieldId']) && isset($this->fieldMap[$condition['targetFieldId']])) {
            $condition['targetFieldId'] = $this->fieldMap[$condition['targetFieldId']];
            $changed = true;
        }

        // conditions 배열 재귀 처리
        if (isset($condition['conditions']) && is_array($condition['conditions'])) {
            foreach ($condition['conditions'] as $i => $sub) {
                $condition['conditions'][$i] = $this->remapDisplayCondition($sub, $changed);
            }
        }

        return $condition;
    }

    // ──────────────────────────────────────────────────────────────────
    // PHASE 2: 기반 테이블
    // ──────────────────────────────────────────────────────────────────

    private function seedCategories(): void
    {
        $rows = $this->loadJson('categories.json');
        $this->expectedCounts['categories'] = count($rows);

        // depth 순 삽입을 위해 parent_id 없는 것 먼저
        $roots = array_filter($rows, fn ($r) => empty($r['parent_id']));
        $children = array_filter($rows, fn ($r) => ! empty($r['parent_id']));

        // 루트 카테고리 삽입
        foreach ($roots as $row) {
            $originalId = $row['_original_id'];
            $data = $this->stripMeta($row);
            $data['tenant_id'] = $this->tenantId;
            $data['parent_id'] = null;
            $this->setAuditFields($data);

            $newId = DB::table('categories')->insertGetId($data);
            $this->categoryMap[$originalId] = $newId;
        }

        // 자식 카테고리 삽입 (여러 depth 처리를 위해 최대 5회 반복)
        $remaining = $children;
        for ($pass = 0; $pass < 5 && ! empty($remaining); $pass++) {
            $nextRemaining = [];
            foreach ($remaining as $row) {
                $originalId = $row['_original_id'];
                $parentOriginalId = $row['parent_id'];

                if (! isset($this->categoryMap[$parentOriginalId])) {
                    $nextRemaining[] = $row;

                    continue;
                }

                $data = $this->stripMeta($row);
                $data['tenant_id'] = $this->tenantId;
                $data['parent_id'] = $this->categoryMap[$parentOriginalId];
                $this->setAuditFields($data);

                $newId = DB::table('categories')->insertGetId($data);
                $this->categoryMap[$originalId] = $newId;
            }
            $remaining = $nextRemaining;
        }

        if (! empty($remaining)) {
            $this->command->warn('  categories: '.count($remaining).'건 매핑 실패 (depth 초과)');
        }

        $this->command->info('  categories: '.count($this->categoryMap).'건 삽입');
    }

    private function seedItems(): void
    {
        $rows = $this->loadJson('items.json');
        $this->expectedCounts['items'] = count($rows);

        $chunks = array_chunk($rows, 500);
        $total = 0;

        foreach ($chunks as $chunk) {
            $insertData = [];
            foreach ($chunk as $row) {
                $data = $this->stripMeta($row);
                $data['tenant_id'] = $this->tenantId;
                $this->setAuditFields($data);

                // category_id 매핑
                if (! empty($data['category_id']) && isset($this->categoryMap[$data['category_id']])) {
                    $data['category_id'] = $this->categoryMap[$data['category_id']];
                }

                $insertData[] = $data;
            }

            DB::table('items')->insert($insertData);
            $total += count($insertData);
        }

        // code로 itemMap 구축
        $this->itemMap = DB::table('items')
            ->where('tenant_id', $this->tenantId)
            ->whereNull('deleted_at')
            ->pluck('id', 'code')
            ->toArray();

        $this->command->info("  items: {$total}건 삽입 (itemMap: ".count($this->itemMap).'건)');

        // BOM의 child_item_code → child_item_id 변환
        $this->remapBomCodes();
    }

    /**
     * BOM JSON의 child_item_code를 실제 child_item_id로 변환
     *
     * items.json의 bom 필드가 child_item_code(품목코드) 기반으로 저장되어 있으므로,
     * INSERT 후 itemMap(code→id)을 이용해 child_item_id로 교체한다.
     */
    private function remapBomCodes(): void
    {
        $bomItems = DB::table('items')
            ->where('tenant_id', $this->tenantId)
            ->whereNotNull('bom')
            ->get(['id', 'code', 'bom']);

        $updated = 0;

        foreach ($bomItems as $item) {
            $bom = json_decode($item->bom, true);
            if (! is_array($bom)) {
                continue;
            }

            $changed = false;
            foreach ($bom as &$entry) {
                // child_item_code → child_item_id 변환
                if (isset($entry['child_item_code'])) {
                    $code = $entry['child_item_code'];
                    if (isset($this->itemMap[$code])) {
                        $entry['child_item_id'] = $this->itemMap[$code];
                        unset($entry['child_item_code']);
                        $changed = true;
                    } else {
                        $this->command->warn("  BOM 매핑 실패: {$item->code} → {$code} (품목 미존재)");
                    }
                }
            }
            unset($entry);

            if ($changed) {
                DB::table('items')->where('id', $item->id)
                    ->update(['bom' => json_encode($bom, JSON_UNESCAPED_UNICODE)]);
                $updated++;
            }
        }

        $this->command->info("  BOM code→id 매핑: {$updated}건 변환");
    }

    // ──────────────────────────────────────────────────────────────────
    // PHASE 3: 종속 테이블
    // ──────────────────────────────────────────────────────────────────

    private function seedItemDetails(): void
    {
        $rows = $this->loadJson('item_details.json');
        $this->expectedCounts['item_details'] = count($rows);

        // items JSON에서 원본 item_id → code 매핑 구축
        $itemsJson = $this->loadJson('items.json');
        $originalIdToCode = [];
        foreach ($itemsJson as $item) {
            $originalIdToCode[$item['_original_id']] = $item['code'];
        }

        $insertData = [];
        $skipped = 0;

        foreach ($rows as $row) {
            $data = $this->stripMeta($row);
            $originalItemId = $data['item_id'];

            // 원본 item_id → code → 새 item_id
            $code = $originalIdToCode[$originalItemId] ?? null;
            if ($code === null || ! isset($this->itemMap[$code])) {
                $skipped++;

                continue;
            }

            $data['item_id'] = $this->itemMap[$code];
            $insertData[] = $data;
        }

        if (! empty($insertData)) {
            foreach (array_chunk($insertData, 500) as $chunk) {
                DB::table('item_details')->insert($chunk);
            }
        }

        if ($skipped > 0) {
            $this->command->warn("  item_details: {$skipped}건 매핑 실패 스킵");
        }

        $this->expectedCounts['item_details'] = count($insertData);
        $this->command->info('  item_details: '.count($insertData).'건 삽입');
    }

    private function seedPrices(): void
    {
        $rows = $this->loadJson('prices.json');
        $this->expectedCounts['prices'] = count($rows);

        // items JSON에서 원본 item_id → code 매핑 구축
        $itemsJson = $this->loadJson('items.json');
        $originalIdToCode = [];
        foreach ($itemsJson as $item) {
            $originalIdToCode[$item['_original_id']] = $item['code'];
        }

        $insertData = [];
        $skipped = 0;

        foreach ($rows as $row) {
            $data = $this->stripMeta($row);
            $data['tenant_id'] = $this->tenantId;
            $this->setAuditFields($data);
            $originalItemId = $data['item_id'];

            // 원본 item_id → code → 새 item_id
            $code = $originalIdToCode[$originalItemId] ?? null;
            if ($code === null || ! isset($this->itemMap[$code])) {
                $skipped++;

                continue;
            }

            $data['item_id'] = $this->itemMap[$code];
            $insertData[] = $data;
        }

        if (! empty($insertData)) {
            foreach (array_chunk($insertData, 500) as $chunk) {
                DB::table('prices')->insert($chunk);
            }
        }

        if ($skipped > 0) {
            $this->command->warn("  prices: {$skipped}건 매핑 실패 스킵");
        }

        $this->expectedCounts['prices'] = count($insertData);
        $this->command->info('  prices: '.count($insertData).'건 삽입');
    }

    // ──────────────────────────────────────────────────────────────────
    // VERIFY & SUMMARY
    // ──────────────────────────────────────────────────────────────────

    private function verify(): void
    {
        $this->command->newLine();
        $this->command->info('=== 검증 ===');

        $tables = [
            'item_pages' => fn () => DB::table('item_pages')->where('tenant_id', $this->tenantId)->whereNull('deleted_at')->count(),
            'item_sections' => fn () => DB::table('item_sections')->where('tenant_id', $this->tenantId)->whereNull('deleted_at')->count(),
            'item_fields' => fn () => DB::table('item_fields')->where('tenant_id', $this->tenantId)->whereNull('deleted_at')->count(),
            'entity_relationships' => fn () => DB::table('entity_relationships')->where('tenant_id', $this->tenantId)->count(),
            'categories' => fn () => DB::table('categories')->where('tenant_id', $this->tenantId)->whereNull('deleted_at')->count(),
            'items' => fn () => DB::table('items')->where('tenant_id', $this->tenantId)->whereNull('deleted_at')->count(),
            'item_details' => fn () => DB::table('item_details')->whereIn('item_id', DB::table('items')->where('tenant_id', $this->tenantId)->pluck('id'))->count(),
            'prices' => fn () => DB::table('prices')->where('tenant_id', $this->tenantId)->whereNull('deleted_at')->count(),
        ];

        $allOk = true;
        foreach ($tables as $table => $countFn) {
            $actual = $countFn();
            $expected = $this->expectedCounts[$table] ?? '?';
            $status = ($actual == $expected) ? 'OK' : 'MISMATCH';

            if ($status === 'MISMATCH') {
                $allOk = false;
                $this->command->error("  {$table}: {$actual}건 (기대: {$expected}) [{$status}]");
            } else {
                $this->command->info("  {$table}: {$actual}건 [{$status}]");
            }
        }

        // entity_relationships 참조 무결성 검증
        $this->verifyRelationshipIntegrity();

        if ($allOk) {
            $this->command->newLine();
            $this->command->info('모든 검증 통과!');
        }
    }

    private function verifyRelationshipIntegrity(): void
    {
        $relationships = DB::table('entity_relationships')
            ->where('tenant_id', $this->tenantId)
            ->get();

        $orphans = 0;
        foreach ($relationships as $rel) {
            $parentExists = $this->entityExists($rel->parent_type, $rel->parent_id);
            $childExists = $this->entityExists($rel->child_type, $rel->child_id);

            if (! $parentExists || ! $childExists) {
                $orphans++;
            }
        }

        if ($orphans > 0) {
            $this->command->warn("  entity_relationships 참조 무결성: {$orphans}건 고아 레코드");
        } else {
            $this->command->info('  entity_relationships 참조 무결성: OK');
        }
    }

    private function entityExists(string $type, int $id): bool
    {
        return match ($type) {
            'page' => DB::table('item_pages')->where('id', $id)->exists(),
            'section' => DB::table('item_sections')->where('id', $id)->exists(),
            'field' => DB::table('item_fields')->where('id', $id)->exists(),
            'bom' => DB::table('item_bom_items')->where('id', $id)->exists(),
            default => false,
        };
    }

    private function printSummary(): void
    {
        $this->command->newLine();
        $this->command->info('=== 경동기업 품목 기준 데이터 시더 완료 ===');
        $this->command->info('  매핑 현황:');
        $this->command->info('    pageMap: '.count($this->pageMap).'건');
        $this->command->info('    sectionMap: '.count($this->sectionMap).'건');
        $this->command->info('    fieldMap: '.count($this->fieldMap).'건');
        $this->command->info('    categoryMap: '.count($this->categoryMap).'건');
        $this->command->info('    itemMap: '.count($this->itemMap).'건');
    }

    // ──────────────────────────────────────────────────────────────────
    // HELPERS
    // ──────────────────────────────────────────────────────────────────

    private function loadJson(string $filename): array
    {
        $path = $this->dataPath.'/'.$filename;

        if (! file_exists($path)) {
            $this->command->error("JSON 파일 없음: {$path}");
            $this->command->error('먼저 php artisan kyungdong:export-item-master 를 실행하세요.');

            throw new \RuntimeException("JSON 파일 없음: {$path}");
        }

        return json_decode(file_get_contents($path), true);
    }

    /**
     * _original_id, id, created_at, updated_at, deleted_at 등 메타 제거
     */
    private function stripMeta(array $row): array
    {
        unset(
            $row['_original_id'],
            $row['id'],
            $row['created_at'],
            $row['updated_at'],
            $row['deleted_at'],
        );

        return $row;
    }

    private function setAuditFields(array &$data): void
    {
        $now = now();
        $data['created_at'] = $now;
        $data['updated_at'] = $now;

        if (isset($data['created_by'])) {
            $data['created_by'] = $this->userId;
        }
        if (isset($data['updated_by'])) {
            $data['updated_by'] = $this->userId;
        }

        // deleted_by, deleted_at는 항상 제거
        unset($data['deleted_by'], $data['deleted_at']);
    }

    /**
     * entity type에 따라 원본 ID를 새 ID로 매핑
     */
    private function mapEntityId(string $type, int $originalId): ?int
    {
        return match ($type) {
            'page' => $this->pageMap[$originalId] ?? null,
            'section' => $this->sectionMap[$originalId] ?? null,
            'field' => $this->fieldMap[$originalId] ?? null,
            default => $originalId, // bom 등은 그대로
        };
    }
}
