<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Schema;

class Material extends Model
{
    use HasFactory;

    protected $fillable = [
        'name',
        'category_id',
        'base_price',
        'unit',
        'stock',
        'min_stock',
        'is_active',
        'track_clicks',
        'is_custom',
        'source_material_id',
        'conversion_qty',
        'measurement_mode',
        'roll_width_cm',
        'allow_rotation',
    ];

    protected $casts = [
        'base_price' => 'decimal:2',
        'stock' => 'decimal:4',
        'min_stock' => 'decimal:4',
        'is_active' => 'boolean',
        'track_clicks' => 'boolean',
        'is_custom' => 'boolean',
        'conversion_qty' => 'decimal:4',
        'roll_width_cm' => 'decimal:4',
        'allow_rotation' => 'boolean',
    ];

    /*
    |--------------------------------------------------------------------------
    | RELATIONS
    |--------------------------------------------------------------------------
    */

    public function category(): BelongsTo
    {
        return $this->belongsTo(MaterialCategory::class, 'category_id');
    }

    public function prices(): HasMany
    {
        return $this->hasMany(MaterialPrice::class, 'material_id');
    }

    public function sourceMaterial(): BelongsTo
    {
        return $this->belongsTo(self::class, 'source_material_id');
    }

    public function customMaterials(): HasMany
    {
        return $this->hasMany(self::class, 'source_material_id');
    }

    public function components(): HasMany
    {
        return $this->hasMany(MaterialComponent::class, 'material_id');
    }

    public function machines(): BelongsToMany
    {
        return $this->belongsToMany(Machine::class, 'machine_material');
    }

    /*
    |--------------------------------------------------------------------------
    | SCOPES
    |--------------------------------------------------------------------------
    */

    public function scopeActive(Builder $query): Builder
    {
        return $query->where('is_active', true);
    }

    public function scopeLowStock(Builder $query): Builder
    {
        return $query
            ->where('is_custom', false)
            ->where('min_stock', '>', 0)
            ->whereColumn('stock', '<=', 'min_stock');
    }

    public function scopeCustom(Builder $query): Builder
    {
        return $query->where('is_custom', true);
    }

    /*
    |--------------------------------------------------------------------------
    | ACCESSORS / COMPATIBILITY
    |--------------------------------------------------------------------------
    */

    public function getIsLowStockAttribute(): bool
    {
        if ((bool) $this->is_custom) {
            return false;
        }

        return (float) $this->min_stock > 0 && (float) $this->stock <= (float) $this->min_stock;
    }

    public function getCategoryName(): string
    {
        return $this->category->name ?? '-';
    }

    /*
    |--------------------------------------------------------------------------
    | INTERNAL HELPERS
    |--------------------------------------------------------------------------
    */

    protected function toFloat($value, float $default = 0): float
    {
        return is_numeric($value) ? (float) $value : $default;
    }

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

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

    protected function extractContextFromArgs(...$args): ?array
    {
        foreach ($args as $arg) {
            if (is_array($arg)) {
                return $arg;
            }
        }

        return null;
    }

    protected function canUseStockMovements(): bool
    {
        if (!class_exists(\App\Models\StockMovement::class)) {
            return false;
        }

        try {
            $table = (new \App\Models\StockMovement())->getTable();
            return !empty($table) && Schema::hasTable($table);
        } catch (\Throwable $e) {
            return false;
        }
    }

    protected function resolveMovementNote($reasonOrContext = null): ?string
    {
        if (is_string($reasonOrContext) && trim($reasonOrContext) !== '') {
            return trim($reasonOrContext);
        }

        if (is_array($reasonOrContext)) {
            foreach (['notes', 'note', 'description', 'reason'] as $key) {
                if (!empty($reasonOrContext[$key]) && is_string($reasonOrContext[$key])) {
                    return trim($reasonOrContext[$key]);
                }
            }
        }

        return null;
    }

    protected function recordStockMovement(
        float $qty,
        string $type = \App\Models\StockMovement::TYPE_OUT,
        ?string $note = null,
        $reference = null,
        ?int $userId = null
    ): void {
        $qty = round(max(0, $qty), 4);

        if ($qty <= 0 || !$this->canUseStockMovements()) {
            return;
        }

        try {
            $movement = new \App\Models\StockMovement();
            $table = $movement->getTable();

            if (Schema::hasColumn($table, 'material_id')) {
                $movement->material_id = $this->id;
            }

            $qtyColumn = Schema::hasColumn($table, 'quantity')
                ? 'quantity'
                : (Schema::hasColumn($table, 'qty') ? 'qty' : null);

            if ($qtyColumn) {
                $movement->{$qtyColumn} = $qty;
            }

            if (Schema::hasColumn($table, 'type')) {
                $movement->type = $type;
            } elseif (Schema::hasColumn($table, 'movement_type')) {
                $movement->movement_type = $type;
            }

            $resolvedUserId = $userId;
            if (!$resolvedUserId && function_exists('auth') && auth()->check()) {
                $resolvedUserId = auth()->id();
            }

            if ($resolvedUserId && Schema::hasColumn($table, 'user_id')) {
                $movement->user_id = $resolvedUserId;
            }

            if ($note) {
                if (Schema::hasColumn($table, 'notes')) {
                    $movement->notes = $note;
                } elseif (Schema::hasColumn($table, 'note')) {
                    $movement->note = $note;
                } elseif (Schema::hasColumn($table, 'description')) {
                    $movement->description = $note;
                }
            }

            if ($reference) {
                if (Schema::hasColumn($table, 'reference_type') && Schema::hasColumn($table, 'reference_id')) {
                    $movement->reference_type = get_class($reference);
                    $movement->reference_id = $reference->id ?? null;
                } elseif (Schema::hasColumn($table, 'source_type') && Schema::hasColumn($table, 'source_id')) {
                    $movement->source_type = get_class($reference);
                    $movement->source_id = $reference->id ?? null;
                }
            }

            $movement->save();
        } catch (\Throwable $e) {
            // Silent fail: stok tetap jalan walau logging movement gagal
        }
    }

    public function getMeasurementModeResolved(): string
    {
        if (!$this->hasMeasurementColumns()) {
            return 'manual';
        }

        $mode = strtolower((string) $this->measurement_mode);

        return in_array($mode, ['manual', 'm2', 'roll'], true) ? $mode : 'manual';
    }

    public function isManualMode(): bool
    {
        return $this->getMeasurementModeResolved() === 'manual';
    }

    public function isM2Mode(): bool
    {
        return $this->getMeasurementModeResolved() === 'm2';
    }

    public function isRollMode(): bool
    {
        return $this->getMeasurementModeResolved() === 'roll';
    }

    public function usesDimensionCalculation(): bool
    {
        return $this->isM2Mode() || $this->isRollMode();
    }

    public function hasComponents(): bool
    {
        if (!$this->hasMaterialComponentsTable()) {
            return false;
        }

        if ($this->relationLoaded('components')) {
            return $this->components->isNotEmpty();
        }

        return $this->components()->exists();
    }

    /*
    |--------------------------------------------------------------------------
    | SIZE / DIMENSION CALCULATION
    |--------------------------------------------------------------------------
    */

    public function calculateAreaM2(float $widthCm, float $heightCm): float
    {
        $widthCm = max(0, $widthCm);
        $heightCm = max(0, $heightCm);

        if ($widthCm <= 0 || $heightCm <= 0) {
            return 0;
        }

        return round(($widthCm * $heightCm) / 10000, 4);
    }

    public function calculateRollLengthCm(
        float $widthCm,
        float $heightCm,
        ?float $rollWidthCm = null,
        ?bool $allowRotation = null
    ): float {
        $widthCm = max(0, $widthCm);
        $heightCm = max(0, $heightCm);
        $rollWidthCm = $rollWidthCm !== null ? $rollWidthCm : $this->toFloat($this->roll_width_cm);
        $allowRotation = $allowRotation !== null ? $allowRotation : (bool) $this->allow_rotation;

        if ($widthCm <= 0 || $heightCm <= 0 || $rollWidthCm <= 0) {
            return 0;
        }

        $candidates = [];

        $stripNormal = (int) ceil($widthCm / $rollWidthCm);
        $candidates[] = $stripNormal * $heightCm;

        if ($allowRotation) {
            $stripRotate = (int) ceil($heightCm / $rollWidthCm);
            $candidates[] = $stripRotate * $widthCm;
        }

        return round(min($candidates), 4);
    }

    public function calculateRollLengthMeter(
        float $widthCm,
        float $heightCm,
        ?float $rollWidthCm = null,
        ?bool $allowRotation = null
    ): float {
        return round(
            $this->calculateRollLengthCm($widthCm, $heightCm, $rollWidthCm, $allowRotation) / 100,
            4
        );
    }

    public function calculateDimensionUsage(float $widthCm, float $heightCm): float
    {
        if ($this->isM2Mode()) {
            return $this->calculateAreaM2($widthCm, $heightCm);
        }

        if ($this->isRollMode()) {
            return $this->calculateRollLengthMeter($widthCm, $heightCm);
        }

        return 0;
    }

    public function calculateDimensionPrice(float $widthCm, float $heightCm, ?float $customBasePrice = null): float
    {
        $basePrice = $customBasePrice !== null
            ? max(0, $customBasePrice)
            : max(0, $this->toFloat($this->base_price));

        if ($this->isM2Mode()) {
            return round($this->calculateAreaM2($widthCm, $heightCm) * $basePrice, 2);
        }

        if ($this->isRollMode()) {
            return round($this->calculateRollLengthMeter($widthCm, $heightCm) * $basePrice, 2);
        }

        return round($basePrice, 2);
    }

    protected function parseSizeDescription(string $sizeDescription): array
    {
        $value = trim(strtolower($sizeDescription));

        if ($value === '') {
            return [0, 0];
        }

        $value = str_replace(['×', '*'], 'x', $value);

        if (!preg_match('/^\s*(\d+(?:[.,]\d+)?)\s*x\s*(\d+(?:[.,]\d+)?)\s*$/', $value, $matches)) {
            return [0, 0];
        }

        $a = (float) str_replace(',', '.', $matches[1]);
        $b = (float) str_replace(',', '.', $matches[2]);

        if ($a >= 10 || $b >= 10) {
            return [$a, $b];
        }

        return [$a * 100, $b * 100];
    }

    protected function resolveUsagePerQtyFromContext($context = null): ?float
    {
        if (!$this->usesDimensionCalculation()) {
            return null;
        }

        if (!is_array($context)) {
            return null;
        }

        if ($this->isM2Mode() && isset($context['size_value']) && is_numeric($context['size_value'])) {
            $sizeValue = (float) $context['size_value'];
            if ($sizeValue > 0) {
                return round($sizeValue, 4);
            }
        }

        if (
            isset($context['width_cm'], $context['height_cm']) &&
            is_numeric($context['width_cm']) &&
            is_numeric($context['height_cm'])
        ) {
            $widthCm = (float) $context['width_cm'];
            $heightCm = (float) $context['height_cm'];

            if ($widthCm > 0 && $heightCm > 0) {
                return $this->calculateDimensionUsage($widthCm, $heightCm);
            }
        }

        if (!empty($context['size_description']) && is_string($context['size_description'])) {
            [$widthCm, $heightCm] = $this->parseSizeDescription($context['size_description']);

            if ($widthCm > 0 && $heightCm > 0) {
                return $this->calculateDimensionUsage($widthCm, $heightCm);
            }
        }

        return null;
    }

    /*
    |--------------------------------------------------------------------------
    | COMPONENT / BREAKDOWN
    |--------------------------------------------------------------------------
    */

    public function getComponentBreakdown(float $qty = 1): array
    {
        $qty = max(0, $qty);

        if ($this->hasMaterialComponentsTable()) {
            $components = $this->relationLoaded('components')
                ? $this->components
                : $this->components()->with('componentMaterial')->get();

            if ($components->isNotEmpty()) {
                return $components->map(function ($component) use ($qty) {
                    return [
                        'material_id' => (int) $component->component_material_id,
                        'material_name' => $component->componentMaterial->name ?? '-',
                        'unit' => $component->componentMaterial->unit ?? '-',
                        'usage_per_qty' => (float) $component->qty,
                        'usage_total' => round((float) $component->qty * $qty, 4),
                    ];
                })->values()->all();
            }
        }

        if ($this->is_custom && $this->source_material_id) {
            return [[
                'material_id' => (int) $this->source_material_id,
                'material_name' => $this->sourceMaterial->name ?? '-',
                'unit' => $this->sourceMaterial->unit ?? '-',
                'usage_per_qty' => (float) ($this->conversion_qty ?? 1),
                'usage_total' => round((float) ($this->conversion_qty ?? 1) * $qty, 4),
            ]];
        }

        return [[
            'material_id' => (int) $this->id,
            'material_name' => (string) $this->name,
            'unit' => (string) $this->unit,
            'usage_per_qty' => 1,
            'usage_total' => round(1 * $qty, 4),
        ]];
    }

    /*
    |--------------------------------------------------------------------------
    | STOCK COMPATIBILITY METHODS
    |--------------------------------------------------------------------------
    */

    public function reduceStock($qty = 1, $reasonOrContext = null, $reference = null, $extra = null): bool
    {
        $qty = max(0, (float) $qty);

        if ($qty <= 0) {
            return true;
        }

        $context = $this->extractContextFromArgs($reasonOrContext, $reference, $extra);
        $note = $this->resolveMovementNote($reasonOrContext) ?? $this->resolveMovementNote($context);

        // custom multi component: hanya komponen yang berkurang
        if ($this->is_custom && $this->hasMaterialComponentsTable()) {
            $components = $this->relationLoaded('components')
                ? $this->components
                : $this->components()->with('componentMaterial')->get();

            if ($components->isNotEmpty()) {
                foreach ($components as $component) {
                    if (!$component->componentMaterial) {
                        continue;
                    }

                    $usage = round((float) $component->qty * $qty, 4);

                    if ($usage > 0) {
                        $component->componentMaterial->decrement('stock', $usage);
                        $component->componentMaterial->recordStockMovement(
                            $usage,
                            \App\Models\StockMovement::TYPE_OUT,
                            $note ?: ('Pemakaian komponen custom: ' . $this->name),
                            $reference
                        );
                        $component->componentMaterial->refresh();
                    }
                }

                $this->refresh();
                return true;
            }
        }

        // custom legacy 1 bahan: hanya source material yang berkurang
        if ($this->is_custom && $this->source_material_id && $this->sourceMaterial) {
            $usage = round(max(0, (float) ($this->conversion_qty ?? 1)) * $qty, 4);

            if ($usage > 0) {
                $this->sourceMaterial->decrement('stock', $usage);
                $this->sourceMaterial->recordStockMovement(
                    $usage,
                    \App\Models\StockMovement::TYPE_OUT,
                    $note ?: ('Pemakaian bahan custom legacy: ' . $this->name),
                    $reference
                );
                $this->sourceMaterial->refresh();
            }

            $this->refresh();
            return true;
        }

        // material biasa / material dimensi
        $usage = $this->resolveUsagePerQtyFromContext($context);

        if ($usage === null) {
            $usage = $qty;
        } else {
            $usage = round($usage * $qty, 4);
        }

        if ($usage > 0) {
            $this->decrement('stock', $usage);
            $this->recordStockMovement(
                $usage,
                \App\Models\StockMovement::TYPE_OUT,
                $note ?: ('Pemakaian bahan: ' . $this->name),
                $reference
            );
            $this->refresh();
        }

        return true;
    }

    public function increaseStock($qty = 1, $reasonOrContext = null, $reference = null, $extra = null): bool
    {
        $qty = max(0, (float) $qty);

        if ($qty <= 0) {
            return true;
        }

        $context = $this->extractContextFromArgs($reasonOrContext, $reference, $extra);

        // custom multi component: hanya komponen yang bertambah
        if ($this->is_custom && $this->hasMaterialComponentsTable()) {
            $components = $this->relationLoaded('components')
                ? $this->components
                : $this->components()->with('componentMaterial')->get();

            if ($components->isNotEmpty()) {
                foreach ($components as $component) {
                    if (!$component->componentMaterial) {
                        continue;
                    }

                    $usage = round((float) $component->qty * $qty, 4);

                    if ($usage > 0) {
                        $component->componentMaterial->increment('stock', $usage);
                        $component->componentMaterial->refresh();
                    }
                }

                $this->refresh();
                return true;
            }
        }

        // custom legacy 1 bahan: hanya source material yang bertambah
        if ($this->is_custom && $this->source_material_id && $this->sourceMaterial) {
            $usage = round(max(0, (float) ($this->conversion_qty ?? 1)) * $qty, 4);

            if ($usage > 0) {
                $this->sourceMaterial->increment('stock', $usage);
                $this->sourceMaterial->refresh();
            }

            $this->refresh();
            return true;
        }

        // material biasa / material dimensi
        $usage = $this->resolveUsagePerQtyFromContext($context);

        if ($usage === null) {
            $usage = $qty;
        } else {
            $usage = round($usage * $qty, 4);
        }

        if ($usage > 0) {
            $this->increment('stock', $usage);
            $this->refresh();
        }

        return true;
    }

    public function addStock($qty = 1, $unused = 0, $reasonOrContext = null, $reference = null): bool
    {
        return $this->increaseStock($qty, $reasonOrContext, $reference);
    }

    public function returnStock($qty = 1, $reasonOrContext = null, $reference = null): bool
    {
        return $this->increaseStock($qty, $reasonOrContext, $reference);
    }

    public function removeStock($qty = 1, $reasonOrContext = null, $reference = null): bool
    {
        return $this->reduceStock($qty, $reasonOrContext, $reference);
    }

    public function canReduceStock($qty = 1, $reasonOrContext = null, $reference = null, $extra = null): bool
    {
        $qty = max(0, (float) $qty);

        if ($qty <= 0) {
            return true;
        }

        $context = $this->extractContextFromArgs($reasonOrContext, $reference, $extra);

        if ($this->is_custom && $this->hasMaterialComponentsTable()) {
            $components = $this->relationLoaded('components')
                ? $this->components
                : $this->components()->with('componentMaterial')->get();

            if ($components->isNotEmpty()) {
                foreach ($components as $component) {
                    if (!$component->componentMaterial) {
                        return false;
                    }

                    $usage = round((float) $component->qty * $qty, 4);

                    if ((float) $component->componentMaterial->stock < $usage) {
                        return false;
                    }
                }

                return true;
            }
        }

        if ($this->is_custom && $this->source_material_id && $this->sourceMaterial) {
            $usage = round(max(0, (float) ($this->conversion_qty ?? 1)) * $qty, 4);
            return (float) $this->sourceMaterial->stock >= $usage;
        }

        $usage = $this->resolveUsagePerQtyFromContext($context);

        if ($usage === null) {
            $usage = $qty;
        } else {
            $usage = round($usage * $qty, 4);
        }

        return (float) $this->stock >= $usage;
    }

    /*
    |--------------------------------------------------------------------------
    | REQUIREMENT CALCULATION
    |--------------------------------------------------------------------------
    */

    public function calculateRequirement(
        float $qty = 1,
        ?float $widthCm = null,
        ?float $heightCm = null,
        ?float $packagePrice = null
    ): array {
        $qty = max(0, $qty);
        $widthCm = $widthCm !== null ? max(0, $widthCm) : null;
        $heightCm = $heightCm !== null ? max(0, $heightCm) : null;

        $hasDimension = $widthCm !== null
            && $heightCm !== null
            && $widthCm > 0
            && $heightCm > 0
            && $this->usesDimensionCalculation();

        if ($hasDimension) {
            $stockUsagePerQty = $this->calculateDimensionUsage($widthCm, $heightCm);
            $pricePerQty = $packagePrice !== null
                ? round(max(0, $packagePrice), 2)
                : $this->calculateDimensionPrice($widthCm, $heightCm);
        } else {
            $stockUsagePerQty = $this->is_custom
                ? max(0, $this->toFloat($this->conversion_qty, 1))
                : 1;

            $pricePerQty = $packagePrice !== null
                ? round(max(0, $packagePrice), 2)
                : round(max(0, $this->toFloat($this->base_price)), 2);
        }

        return [
            'material_id' => (int) $this->id,
            'material_name' => (string) $this->name,
            'is_custom' => (bool) $this->is_custom,
            'measurement_mode' => $this->getMeasurementModeResolved(),
            'qty' => $qty,
            'width_cm' => $widthCm,
            'height_cm' => $heightCm,
            'area_m2' => $hasDimension ? $this->calculateAreaM2($widthCm, $heightCm) : 0,
            'roll_length_cm' => $hasDimension && $this->isRollMode()
                ? $this->calculateRollLengthCm($widthCm, $heightCm)
                : 0,
            'roll_length_m' => $hasDimension && $this->isRollMode()
                ? $this->calculateRollLengthMeter($widthCm, $heightCm)
                : 0,
            'component_breakdown' => $this->getComponentBreakdown($qty),
            'calculated_price_per_qty' => $pricePerQty,
            'calculated_price_total' => round($pricePerQty * $qty, 2),
            'stock_usage_per_qty' => $stockUsagePerQty,
            'stock_usage_total' => round($stockUsagePerQty * $qty, 4),
        ];
    }
}
