<?php

namespace Tests\Feature\Production;

use App\DTOs\Production\DynamicBomEntry;
use App\Services\Production\PrefixResolver;
use App\Services\WorkOrderService;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;

/**
 * 절곡 자재투입 LOT 매핑 파이프라인 통합 테스트
 *
 * getMaterials() → dynamic_bom 우선 체크 → 세부품목 반환 → 자재투입 플로우 검증
 *
 * 실행 조건: Docker 환경 + 로컬 DB 접속 필요
 */
class BendingLotPipelineTest extends TestCase
{
    use DatabaseTransactions;

    private const TENANT_ID = 287;

    private PrefixResolver $resolver;

    protected function setUp(): void
    {
        parent::setUp();
        $this->resolver = new PrefixResolver;
    }

    // ─────────────────────────────────────────────────
    // PrefixResolver → items.id 조회 통합
    // ─────────────────────────────────────────────────

    /**
     * BD-* 품목이 items 테이블에 실제 존재하는지 확인
     */
    public function test_prefix_resolver_resolves_existing_bd_items(): void
    {
        $testCodes = [
            'BD-RS-43', 'BD-RM-30', 'BD-RC-35', 'BD-RD-40',
            'BD-SS-43', 'BD-SM-30', 'BD-SC-35', 'BD-SD-40',
            'BD-BE-30', 'BD-BS-40', 'BD-LA-30',
            'BD-CF-30', 'BD-CL-24', 'BD-CP-30', 'BD-CB-30',
            'BD-GI-53', 'BD-GI-84',
            'BD-XX-30', 'BD-YY-43', 'BD-HH-30',
        ];

        $foundCount = 0;
        $missingCodes = [];

        foreach ($testCodes as $code) {
            $id = $this->resolver->resolveItemId($code, self::TENANT_ID);
            if ($id !== null) {
                $foundCount++;
                $this->assertGreaterThan(0, $id, "Item ID for {$code} must be positive");
            } else {
                $missingCodes[] = $code;
            }
        }

        // Phase 0에서 전부 등록했으므로 모두 존재해야 함
        $this->assertEmpty(
            $missingCodes,
            'Missing BD items: '.implode(', ', $missingCodes)
        );
        $this->assertCount(count($testCodes), array_diff($testCodes, $missingCodes));
    }

    /**
     * resolveItemId 캐시 동작 확인
     */
    public function test_resolve_item_id_uses_cache(): void
    {
        $code = 'BD-RS-43';
        $id1 = $this->resolver->resolveItemId($code, self::TENANT_ID);
        $id2 = $this->resolver->resolveItemId($code, self::TENANT_ID);

        $this->assertNotNull($id1);
        $this->assertSame($id1, $id2, 'Cached result should be identical');
    }

    // ─────────────────────────────────────────────────
    // dynamic_bom 생성 → JSON 구조 검증
    // ─────────────────────────────────────────────────

    /**
     * DynamicBomEntry 배열이 올바른 JSON 구조로 변환되는지 확인
     */
    public function test_dynamic_bom_entries_produce_valid_json_structure(): void
    {
        $entries = [];

        // 가이드레일 벽면형 KSS01 (SUS) 4300mm
        $testCombinations = [
            ['finish', 'wall', 'KSS01', 4300, 'guideRail', 'SUS'],
            ['body', 'wall', 'KSS01', 4300, 'guideRail', 'EGI'],
            ['c_type', 'wall', 'KSS01', 4300, 'guideRail', 'EGI'],
            ['d_type', 'wall', 'KSS01', 4300, 'guideRail', 'EGI'],
            ['base', 'wall', 'KSS01', 4300, 'guideRail', 'EGI'],
        ];

        foreach ($testCombinations as [$partType, $guideType, $productCode, $lengthMm, $category, $materialType]) {
            $prefix = $this->resolver->resolveGuideRailPrefix($partType, $guideType, $productCode);
            $itemCode = $this->resolver->buildItemCode($prefix, $lengthMm);
            $this->assertNotNull($itemCode, "buildItemCode failed for {$prefix}/{$lengthMm}");

            $itemId = $this->resolver->resolveItemId($itemCode, self::TENANT_ID);
            if ($itemId === null) {
                $this->markTestSkipped("Item {$itemCode} not found in DB — run Phase 0 first");
            }

            $entries[] = DynamicBomEntry::fromArray([
                'child_item_id' => $itemId,
                'child_item_code' => $itemCode,
                'lot_prefix' => $prefix,
                'part_type' => PrefixResolver::partTypeName($partType),
                'category' => $category,
                'material_type' => $materialType,
                'length_mm' => $lengthMm,
                'qty' => 1,
            ]);
        }

        $json = DynamicBomEntry::toArrayList($entries);

        $this->assertCount(5, $json);
        $this->assertEquals('BD-RS-43', $json[0]['child_item_code']);
        $this->assertEquals('BD-RM-43', $json[1]['child_item_code']);
        $this->assertEquals('BD-RC-43', $json[2]['child_item_code']);
        $this->assertEquals('BD-RD-43', $json[3]['child_item_code']);
        $this->assertEquals('BD-XX-43', $json[4]['child_item_code']);

        // JSON 인코딩/디코딩 정합성
        $encoded = json_encode($json, JSON_UNESCAPED_UNICODE);
        $decoded = json_decode($encoded, true);
        $this->assertEquals($json, $decoded, 'JSON round-trip should be identical');
    }

    // ─────────────────────────────────────────────────
    // getMaterials dynamic_bom 우선 체크
    // ─────────────────────────────────────────────────

    /**
     * work_order_items.options.dynamic_bom이 있는 경우
     * getMaterials가 세부품목을 반환하는지 확인
     */
    public function test_get_materials_returns_dynamic_bom_items(): void
    {
        // 절곡 작업지시 찾기 (dynamic_bom이 있는)
        $woItem = DB::table('work_order_items')
            ->where('tenant_id', self::TENANT_ID)
            ->whereNotNull('options')
            ->whereRaw("JSON_EXTRACT(options, '$.dynamic_bom') IS NOT NULL")
            ->first();

        if (! $woItem) {
            $this->markTestSkipped('No work_order_items with dynamic_bom found — create a bending work order first');
        }

        $options = is_string($woItem->options) ? json_decode($woItem->options, true) : ($woItem->options ?? []);
        $dynamicBom = $options['dynamic_bom'] ?? [];

        $this->assertNotEmpty($dynamicBom, 'dynamic_bom should not be empty');

        // dynamic_bom 각 항목 구조 검증
        foreach ($dynamicBom as $entry) {
            $this->assertArrayHasKey('child_item_id', $entry);
            $this->assertArrayHasKey('child_item_code', $entry);
            $this->assertArrayHasKey('lot_prefix', $entry);
            $this->assertArrayHasKey('part_type', $entry);
            $this->assertArrayHasKey('category', $entry);
            $this->assertGreaterThan(0, $entry['child_item_id']);
            $this->assertMatchesRegularExpression('/^BD-[A-Z]{2}-\d{2}$/', $entry['child_item_code']);
        }
    }

    /**
     * getMaterials API 응답에 work_order_item_id 필드가 포함되는지 확인
     */
    public function test_get_materials_api_includes_work_order_item_id(): void
    {
        // 절곡 작업지시 찾기
        $wo = DB::table('work_orders')
            ->where('tenant_id', self::TENANT_ID)
            ->whereExists(function ($query) {
                $query->select(DB::raw(1))
                    ->from('work_order_items')
                    ->whereColumn('work_order_items.work_order_id', 'work_orders.id')
                    ->whereRaw("JSON_EXTRACT(options, '$.dynamic_bom') IS NOT NULL");
            })
            ->first();

        if (! $wo) {
            $this->markTestSkipped('No work order with dynamic_bom items found');
        }

        // WorkOrderService 직접 호출로 getMaterials 검증
        $service = app(WorkOrderService::class);
        $service->setContext(self::TENANT_ID, 1);

        $materials = $service->getMaterials($wo->id);

        // dynamic_bom 품목에는 work_order_item_id가 포함되어야 함
        $dynamicBomMaterials = array_filter($materials, fn ($m) => isset($m['work_order_item_id']));

        if (empty($dynamicBomMaterials)) {
            $this->markTestSkipped('getMaterials returned no dynamic_bom materials');
        }

        foreach ($dynamicBomMaterials as $material) {
            $this->assertArrayHasKey('work_order_item_id', $material);
            $this->assertArrayHasKey('lot_prefix', $material);
            $this->assertArrayHasKey('category', $material);
            $this->assertGreaterThan(0, $material['work_order_item_id']);
        }
    }

    // ─────────────────────────────────────────────────
    // 전체 prefix × lengthCode 마스터 검증 (Phase 0 검증 재확인)
    // ─────────────────────────────────────────────────

    /**
     * 19종 prefix × 해당 lengthCode 조합이 모두 items 테이블에 존재하는지 확인
     */
    public function test_all_prefix_length_combinations_exist_in_items(): void
    {
        $standardLengths = [30, 35, 40, 43];
        $boxLengths = [12, 24, 30, 35, 40, 41];

        $prefixLengthMap = [
            // 가이드레일 벽면형
            'RS' => $standardLengths, 'RM' => array_merge($standardLengths, [24, 35]),
            'RC' => array_merge($standardLengths, [24, 35]), 'RD' => array_merge($standardLengths, [24, 35]),
            'RT' => [30, 43],
            // 가이드레일 측면형
            'SS' => [30, 35, 40, 43], 'SM' => [30, 35, 40, 43, 24],
            'SC' => [30, 35, 40, 43, 24], 'SD' => [30, 35, 40, 43, 24],
            'ST' => [43], 'SU' => [30, 35, 40, 43],
            // 하단마감재
            'BE' => [30, 40], 'BS' => [30, 35, 40, 43, 24],
            'TS' => [40, 43],
            'LA' => [30, 40],
            // 셔터박스 (표준 길이: 43 제외 — 4300mm는 가이드레일 전용)
            'CF' => $boxLengths, 'CL' => $boxLengths,
            'CP' => $boxLengths, 'CB' => $boxLengths,
            // 연기차단재
            'GI' => [53, 54, 83, 84, 30, 35, 40],
            // 공통
            'XX' => array_merge($boxLengths, [43]), 'YY' => $standardLengths,
            'HH' => [30, 40],
        ];

        $missing = [];

        foreach ($prefixLengthMap as $prefix => $codes) {
            foreach ($codes as $code) {
                $itemCode = "BD-{$prefix}-{$code}";
                $exists = DB::table('items')
                    ->where('tenant_id', self::TENANT_ID)
                    ->where('code', $itemCode)
                    ->whereNull('deleted_at')
                    ->exists();

                if (! $exists) {
                    $missing[] = $itemCode;
                }
            }
        }

        $this->assertEmpty(
            $missing,
            'Missing BD items in items table: '.implode(', ', $missing)
        );
    }
}
