<?php

namespace App\Http\Controllers;

use App\Models\Material;
use App\Models\MaterialCategory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Validation\Rule;

class MaterialController extends Controller
{
    public function __construct()
    {
        $this->middleware(\App\Http\Middleware\PermissionMiddleware::class . ':manage materials,manage stock')
            ->only(['create', 'store', 'edit', 'update', 'destroy']);
    }

    public function index(Request $request)
    {
        $with = ['category', 'sourceMaterial'];

        if ($this->hasMaterialComponentsTable()) {
            $with[] = 'components.componentMaterial';
        }

        $query = Material::with($with);

        if ($request->filled('category')) {
            $query->where('category_id', $request->category);
        }

        if ($request->filled('search')) {
            $query->where('name', 'like', '%' . $request->search . '%');
        }

        if ($request->filled('stock')) {
            if ($request->stock === 'low') {
                $query->lowStock();
            } elseif ($request->stock === 'out') {
                $query->where('is_custom', false)->where('stock', '<=', 0);
            }
        }

        $materials = $query->orderBy('name')->paginate(20);
        $categories = MaterialCategory::active()->orderBy('name')->get();

        return view('pages.stock.materials.index', [
            'title' => 'Data Bahan',
            'materials' => $materials,
            'categories' => $categories,
            'hasMaterialComponentsTable' => $this->hasMaterialComponentsTable(),
        ]);
    }

    public function create()
    {
        $categories = MaterialCategory::active()->orderBy('name')->get();
        $materials = Material::orderBy('name')->get();

        return view('pages.stock.materials.create', [
            'title' => 'Tambah Bahan',
            'categories' => $categories,
            'materials' => $materials,
            'hasMaterialComponentsTable' => $this->hasMaterialComponentsTable(),
            'hasMeasurementColumns' => $this->hasMeasurementColumns(),
        ]);
    }

    public function store(Request $request)
    {
        $hasComponentsTable = $this->hasMaterialComponentsTable();
        $hasMeasurementColumns = $this->hasMeasurementColumns();

        $rules = [
            'name' => ['required', 'string', 'max:255'],
            'category_id' => ['nullable', 'exists:material_categories,id'],
            'base_price' => ['required', 'numeric', 'min:0'],
            'unit' => ['required', 'string', 'max:20'],
            'stock' => ['nullable', 'numeric', 'min:0'],
            'min_stock' => ['nullable', 'numeric', 'min:0'],
            'track_clicks' => ['nullable', 'boolean'],

            'is_custom' => ['nullable', 'boolean'],
            'source_material_id' => ['nullable', 'integer', 'exists:materials,id'],
            'conversion_qty' => ['nullable', 'numeric', 'min:0.0001'],

            'prices' => ['nullable', 'array'],
            'prices.*.member_type' => ['required_with:prices', 'string', 'in:umum,studio'],
            'prices.*.sisi' => ['required_with:prices', 'integer', 'in:1,2'],
            'prices.*.min_qty' => ['required_with:prices', 'integer', 'min:1'],
            'prices.*.max_qty' => ['nullable', 'integer', 'min:1'],
            'prices.*.price' => ['required_with:prices', 'numeric', 'min:0'],
        ];

        if ($hasMeasurementColumns) {
            $rules['measurement_mode'] = ['nullable', 'in:manual,m2,roll'];
            $rules['roll_width_cm'] = [
                'nullable',
                'numeric',
                'min:0.0001',
                Rule::requiredIf(fn () => $request->input('measurement_mode', 'manual') === 'roll'),
            ];
            $rules['allow_rotation'] = ['nullable', 'boolean'];
        }

        if ($hasComponentsTable) {
            $rules['components'] = ['nullable', 'array'];
            $rules['components.*.component_material_id'] = ['nullable', 'integer', 'exists:materials,id'];
            $rules['components.*.qty'] = ['nullable', 'numeric', 'min:0.0001'];
        }

        $validated = $request->validate($rules);

        $isCustom = $request->boolean('is_custom');
        $measurementMode = $hasMeasurementColumns ? $request->input('measurement_mode', 'manual') : 'manual';
        $components = $hasComponentsTable && $isCustom
            ? $this->normalizeComponents($request->input('components', []))
            : [];

        if ($isCustom && $hasComponentsTable && empty($components) && !$request->filled('source_material_id')) {
            return back()
                ->withErrors(['components' => 'Minimal pilih 1 komponen bahan untuk bahan custom.'])
                ->withInput();
        }

        if ($hasComponentsTable && $this->hasDuplicateComponents($components)) {
            return back()
                ->withErrors(['components' => 'Komponen bahan tidak boleh duplikat.'])
                ->withInput();
        }

        [$legacySourceMaterialId, $legacyConversionQty] = $this->resolveLegacyComponentFields(
            $isCustom,
            $components,
            $request
        );

        DB::transaction(function () use (
            $validated,
            $request,
            $isCustom,
            $measurementMode,
            $components,
            $legacySourceMaterialId,
            $legacyConversionQty,
            $hasComponentsTable,
            $hasMeasurementColumns
        ) {
            $data = [
                'name' => $validated['name'],
                'category_id' => $validated['category_id'] ?? null,
                'base_price' => $validated['base_price'],
                'unit' => $validated['unit'],
                'stock' => $isCustom ? 0 : ($validated['stock'] ?? 0),
                'min_stock' => $isCustom ? 0 : ($validated['min_stock'] ?? 0),
                'track_clicks' => $request->boolean('track_clicks'),
                'is_custom' => $isCustom,
                'source_material_id' => $legacySourceMaterialId,
                'conversion_qty' => $legacyConversionQty,
            ];

            if ($hasMeasurementColumns) {
                $data['measurement_mode'] = $measurementMode;
                $data['roll_width_cm'] = $measurementMode === 'roll' ? ($validated['roll_width_cm'] ?? null) : null;
                $data['allow_rotation'] = $measurementMode === 'roll'
                    ? $request->boolean('allow_rotation', true)
                    : true;
            }

            $material = Material::create($data);

            if ($hasComponentsTable && $isCustom && !empty($components)) {
                foreach ($components as $component) {
                    $material->components()->create([
                        'component_material_id' => $component['component_material_id'],
                        'qty' => $component['qty'],
                    ]);
                }
            }

            if (!empty($validated['prices'])) {
                foreach ($validated['prices'] as $priceData) {
                    $material->prices()->create([
                        'member_type' => $priceData['member_type'],
                        'sisi' => $priceData['sisi'],
                        'min_qty' => $priceData['min_qty'],
                        'max_qty' => $priceData['max_qty'] ?? null,
                        'price' => $priceData['price'],
                    ]);
                }
            }
        });

        return redirect()->route('stock.materials.index')
            ->with('success', 'Bahan berhasil ditambahkan.');
    }

    public function edit(Material $material)
    {
        $with = ['prices'];

        if ($this->hasMaterialComponentsTable()) {
            $with[] = 'components.componentMaterial';
        }

        $material->load($with);

        $categories = MaterialCategory::active()->orderBy('name')->get();
        $materials = Material::where('id', '!=', $material->id)->orderBy('name')->get();

        $pricesJson = $this->buildGroupedPrices($material);
        $componentsJson = $this->buildComponentsJson($material);

        return view('pages.stock.materials.edit', [
            'title' => 'Edit Bahan',
            'material' => $material,
            'categories' => $categories,
            'materials' => $materials,
            'pricesJson' => $pricesJson,
            'componentsJson' => $componentsJson,
            'hasMaterialComponentsTable' => $this->hasMaterialComponentsTable(),
            'hasMeasurementColumns' => $this->hasMeasurementColumns(),
        ]);
    }

    public function update(Request $request, Material $material)
    {
        $hasComponentsTable = $this->hasMaterialComponentsTable();
        $hasMeasurementColumns = $this->hasMeasurementColumns();

        $rules = [
            'name' => ['required', 'string', 'max:255'],
            'category_id' => ['nullable', 'exists:material_categories,id'],
            'base_price' => ['required', 'numeric', 'min:0'],
            'unit' => ['required', 'string', 'max:20'],
            'stock' => ['nullable', 'numeric', 'min:0'],
            'min_stock' => ['nullable', 'numeric', 'min:0'],
            'is_active' => ['nullable', 'boolean'],
            'track_clicks' => ['nullable', 'boolean'],

            'is_custom' => ['nullable', 'boolean'],
            'source_material_id' => [
                'nullable',
                'integer',
                'exists:materials,id',
                Rule::notIn([$material->id]),
            ],
            'conversion_qty' => ['nullable', 'numeric', 'min:0.0001'],

            'prices' => ['nullable', 'array'],
            'prices.*.member_type' => ['required_with:prices', 'string', 'in:umum,studio'],
            'prices.*.sisi' => ['required_with:prices', 'integer', 'in:1,2'],
            'prices.*.min_qty' => ['required_with:prices', 'integer', 'min:1'],
            'prices.*.max_qty' => ['nullable', 'integer', 'min:1'],
            'prices.*.price' => ['required_with:prices', 'numeric', 'min:0'],
        ];

        if ($hasMeasurementColumns) {
            $rules['measurement_mode'] = ['nullable', 'in:manual,m2,roll'];
            $rules['roll_width_cm'] = [
                'nullable',
                'numeric',
                'min:0.0001',
                Rule::requiredIf(fn () => $request->input('measurement_mode', 'manual') === 'roll'),
            ];
            $rules['allow_rotation'] = ['nullable', 'boolean'];
        }

        if ($hasComponentsTable) {
            $rules['components'] = ['nullable', 'array'];
            $rules['components.*.component_material_id'] = [
                'nullable',
                'integer',
                'exists:materials,id',
                Rule::notIn([$material->id]),
            ];
            $rules['components.*.qty'] = ['nullable', 'numeric', 'min:0.0001'];
        }

        $validated = $request->validate($rules);

        $isCustom = $request->boolean('is_custom');
        $measurementMode = $hasMeasurementColumns ? $request->input('measurement_mode', 'manual') : 'manual';
        $components = $hasComponentsTable && $isCustom
            ? $this->normalizeComponents($request->input('components', []))
            : [];

        if ($isCustom && $hasComponentsTable && empty($components) && !$request->filled('source_material_id')) {
            return back()
                ->withErrors(['components' => 'Minimal pilih 1 komponen bahan untuk bahan custom.'])
                ->withInput();
        }

        if ($hasComponentsTable && $this->hasDuplicateComponents($components)) {
            return back()
                ->withErrors(['components' => 'Komponen bahan tidak boleh duplikat.'])
                ->withInput();
        }

        [$legacySourceMaterialId, $legacyConversionQty] = $this->resolveLegacyComponentFields(
            $isCustom,
            $components,
            $request
        );

        DB::transaction(function () use (
            $validated,
            $request,
            $material,
            $isCustom,
            $measurementMode,
            $components,
            $legacySourceMaterialId,
            $legacyConversionQty,
            $hasComponentsTable,
            $hasMeasurementColumns
        ) {
            $data = [
                'name' => $validated['name'],
                'category_id' => $validated['category_id'] ?? null,
                'base_price' => $validated['base_price'],
                'unit' => $validated['unit'],
                'stock' => $isCustom ? 0 : ($validated['stock'] ?? $material->stock),
                'min_stock' => $isCustom ? 0 : ($validated['min_stock'] ?? 0),
                'is_active' => $request->boolean('is_active'),
                'track_clicks' => $request->boolean('track_clicks'),
                'is_custom' => $isCustom,
                'source_material_id' => $legacySourceMaterialId,
                'conversion_qty' => $legacyConversionQty,
            ];

            if ($hasMeasurementColumns) {
                $data['measurement_mode'] = $measurementMode;
                $data['roll_width_cm'] = $measurementMode === 'roll' ? ($validated['roll_width_cm'] ?? null) : null;
                $data['allow_rotation'] = $measurementMode === 'roll'
                    ? $request->boolean('allow_rotation', true)
                    : true;
            }

            $material->update($data);

            if ($hasComponentsTable) {
                $material->components()->delete();

                if ($isCustom && !empty($components)) {
                    foreach ($components as $component) {
                        $material->components()->create([
                            'component_material_id' => $component['component_material_id'],
                            'qty' => $component['qty'],
                        ]);
                    }
                }
            }

            $material->prices()->delete();

            if (!empty($validated['prices'])) {
                foreach ($validated['prices'] as $priceData) {
                    $material->prices()->create([
                        'member_type' => $priceData['member_type'],
                        'sisi' => $priceData['sisi'],
                        'min_qty' => $priceData['min_qty'],
                        'max_qty' => $priceData['max_qty'] ?? null,
                        'price' => $priceData['price'],
                    ]);
                }
            }
        });

        return redirect()->route('stock.materials.index')
            ->with('success', 'Bahan berhasil diperbarui.');
    }

    public function destroy(Material $material)
    {
        $material->delete();

        return redirect()->route('stock.materials.index')
            ->with('success', 'Bahan berhasil dihapus.');
    }

    private function hasMaterialComponentsTable(): bool
    {
        return Schema::hasTable('material_components');
    }

    private function hasMeasurementColumns(): bool
    {
        return Schema::hasColumn('materials', 'measurement_mode')
            && Schema::hasColumn('materials', 'roll_width_cm')
            && Schema::hasColumn('materials', 'allow_rotation');
    }

    private function normalizeComponents(array $items): array
    {
        return collect($items)
            ->filter(function ($item) {
                return !empty($item['component_material_id']) && isset($item['qty']) && $item['qty'] !== '';
            })
            ->map(function ($item) {
                return [
                    'component_material_id' => (int) $item['component_material_id'],
                    'qty' => (float) $item['qty'],
                ];
            })
            ->filter(function ($item) {
                return $item['component_material_id'] > 0 && $item['qty'] > 0;
            })
            ->values()
            ->all();
    }

    private function hasDuplicateComponents(array $components): bool
    {
        return collect($components)
            ->pluck('component_material_id')
            ->duplicates()
            ->isNotEmpty();
    }

    private function resolveLegacyComponentFields(bool $isCustom, array $components, Request $request): array
    {
        if (!$isCustom) {
            return [null, 1];
        }

        if (!empty($components)) {
            return [
                $components[0]['component_material_id'],
                $components[0]['qty'],
            ];
        }

        return [
            $request->filled('source_material_id') ? (int) $request->input('source_material_id') : null,
            $request->filled('conversion_qty') ? (float) $request->input('conversion_qty') : 1,
        ];
    }

    private function buildGroupedPrices(Material $material): array
    {
        return $material->prices
            ->groupBy(function ($p) {
                return $p->sisi . '-' . $p->min_qty . '-' . ($p->max_qty ?? 'null');
            })
            ->map(function ($group) {
                $umum = $group->firstWhere('member_type', 'umum');
                $studio = $group->firstWhere('member_type', 'studio');

                return [
                    'sisi' => (int) ($umum->sisi ?? $studio->sisi ?? 1),
                    'min_qty' => (int) ($umum->min_qty ?? $studio->min_qty ?? 1),
                    'max_qty' => $umum->max_qty ?? $studio->max_qty,
                    'price_umum' => (float) ($umum->price ?? 0),
                    'price_studio' => (float) ($studio->price ?? 0),
                ];
            })
            ->values()
            ->all();
    }

    private function buildComponentsJson(Material $material): array
    {
        if (!$this->hasMaterialComponentsTable()) {
            if ($material->is_custom && $material->source_material_id) {
                return [[
                    'component_material_id' => (int) $material->source_material_id,
                    'qty' => (float) ($material->conversion_qty ?? 1),
                ]];
            }

            return [];
        }

        if ($material->relationLoaded('components') && $material->components->isNotEmpty()) {
            return $material->components
                ->map(function ($component) {
                    return [
                        'component_material_id' => (int) $component->component_material_id,
                        'qty' => (float) $component->qty,
                    ];
                })
                ->values()
                ->all();
        }

        if ($material->is_custom && $material->source_material_id) {
            return [[
                'component_material_id' => (int) $material->source_material_id,
                'qty' => (float) ($material->conversion_qty ?? 1),
            ]];
        }

        return [];
    }
}