<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Order extends Model
{
    use HasFactory;

    const PAYMENT_UNPAID = 'unpaid';
    const PAYMENT_PARTIAL = 'partial';
    const PAYMENT_PAID = 'paid';

    const STATUS_PENDING = 'pending';
    const STATUS_PROSES_CETAK = 'proses_cetak';
    const STATUS_FINISHING = 'finishing';
    const STATUS_SELESAI = 'selesai';

    const PICKUP_NOT_PICKED_UP = 'not_picked_up';
    const PICKUP_PICKED_UP = 'picked_up';

    const PAYMENT_STATUSES = [
        self::PAYMENT_UNPAID => 'Belum Bayar',
        self::PAYMENT_PARTIAL => 'Cicilan',
        self::PAYMENT_PAID => 'Lunas',
    ];

    const PRODUCTION_STATUSES = [
        self::STATUS_PENDING => 'Pending',
        self::STATUS_PROSES_CETAK => 'Proses Cetak',
        self::STATUS_FINISHING => 'Finishing',
        self::STATUS_SELESAI => 'Selesai',
    ];

    const PICKUP_STATUSES = [
        self::PICKUP_NOT_PICKED_UP => 'Belum Diambil',
        self::PICKUP_PICKED_UP => 'Sudah Diambil',
    ];

    protected $fillable = [
        'invoice_number',
        'customer_id',
        'customer_name',
        'customer_phone',
        'notes',
        'total_price',
        'paid_amount',
        'change_amount',
        'payment_status',
        'is_credit',
        'paid_at',
        'production_status',
        'started_at',
        'finished_at',
        'pickup_status',
        'picked_up_at',
        'designer_id',
        'cashier_id',
        'operator_id',
        'finishing_operator_id',
        'spk_printed',
        'spk_printed_at',
        'is_cancelled',
        'cancelled_at',
        'cancelled_by',
        'cancel_reason',
    ];

    protected function casts(): array
    {
        return [
            'total_price' => 'decimal:2',
            'paid_amount' => 'decimal:2',
            'change_amount' => 'decimal:2',
            'paid_at' => 'datetime',
            'started_at' => 'datetime',
            'finished_at' => 'datetime',
            'picked_up_at' => 'datetime',
            'spk_printed' => 'boolean',
            'spk_printed_at' => 'datetime',
            'is_credit' => 'boolean',
            'is_cancelled' => 'boolean',
            'cancelled_at' => 'datetime',
            'finishing_operator_id' => 'integer',
        ];
    }

    protected static function boot()
    {
        parent::boot();

        static::creating(function ($order) {
            if (empty($order->invoice_number)) {
                $order->invoice_number = self::generateInvoiceNumber();
            }
        });
    }

    public static function generateInvoiceNumber(): string
    {
        $prefix = 'INV';
        $date = now()->format('Ymd');
        $lastOrder = self::whereDate('created_at', today())
            ->orderBy('id', 'desc')
            ->first();

        $sequence = $lastOrder ? ((int) substr($lastOrder->invoice_number, -4)) + 1 : 1;

        return sprintf('%s-%s-%04d', $prefix, $date, $sequence);
    }

    public function isPaid(): bool { return $this->payment_status === self::PAYMENT_PAID; }
    public function isUnpaid(): bool { return $this->payment_status === self::PAYMENT_UNPAID; }
    public function isPartiallyPaid(): bool { return $this->payment_status === self::PAYMENT_PARTIAL; }
    public function hasReceivables(): bool { return in_array($this->payment_status, [self::PAYMENT_UNPAID, self::PAYMENT_PARTIAL]); }

    public function scopeHasReceivables($query)
    {
        return $query->whereIn('payment_status', [self::PAYMENT_UNPAID, self::PAYMENT_PARTIAL]);
    }

    public function isPending(): bool { return $this->production_status === self::STATUS_PENDING; }
    public function isProsesCetak(): bool { return $this->production_status === self::STATUS_PROSES_CETAK; }
    public function isFinishing(): bool { return $this->production_status === self::STATUS_FINISHING; }
    public function isSelesai(): bool { return $this->production_status === self::STATUS_SELESAI; }

    public function isPickedUp(): bool { return $this->pickup_status === self::PICKUP_PICKED_UP; }
    public function isNotPickedUp(): bool { return $this->pickup_status === self::PICKUP_NOT_PICKED_UP; }
    public function isCancelled(): bool { return (bool) $this->is_cancelled; }

    public function canBeCancelled(): bool
    {
        return !$this->is_cancelled && !$this->isPickedUp();
    }

    public function hasPendingCancellationRequest(): bool
    {
        return $this->cancellationRequests()
            ->where('type', OrderCancellationRequest::TYPE_ORDER)
            ->where('status', OrderCancellationRequest::STATUS_PENDING)
            ->exists();
    }

    public function canPrintSPK(): bool
    {
        return ($this->isPaid() || $this->is_credit) && !$this->is_cancelled;
    }

    public function canStartProduction(): bool
    {
        return ($this->isPaid() || $this->is_credit) && $this->isPending() && !$this->is_cancelled;
    }

    public function canMoveToFinishing(): bool
    {
        return $this->isProsesCetak() && !$this->is_cancelled;
    }

    public function canMarkComplete(): bool
    {
        return $this->isFinishing() && !$this->is_cancelled;
    }

    public function canMarkAsPickedUp(): bool
    {
        return $this->isSelesai() && $this->isNotPickedUp() && !$this->is_cancelled;
    }

    public function getPaymentStatusLabel(): string
    {
        return self::PAYMENT_STATUSES[$this->payment_status] ?? $this->payment_status;
    }

    public function getProductionStatusLabel(): string
    {
        return self::PRODUCTION_STATUSES[$this->production_status] ?? $this->production_status;
    }

    public function getPickupStatusLabel(): string
    {
        return self::PICKUP_STATUSES[$this->pickup_status] ?? $this->pickup_status;
    }

    public function getPaymentStatusBadgeClass(): string
    {
        return match ($this->payment_status) {
            self::PAYMENT_PAID => 'bg-success-50 text-success-700 dark:bg-success-500/15 dark:text-success-500',
            self::PAYMENT_PARTIAL => 'bg-blue-light-50 text-blue-light-700 dark:bg-blue-light-500/15 dark:text-blue-light-500',
            default => 'bg-warning-50 text-warning-700 dark:bg-warning-500/15 dark:text-warning-500',
        };
    }

    public function getProductionStatusBadgeClass(): string
    {
        return match ($this->production_status) {
            self::STATUS_SELESAI => 'bg-success-50 text-success-700 dark:bg-success-500/15 dark:text-success-500',
            self::STATUS_FINISHING => 'bg-blue-light-50 text-blue-light-700 dark:bg-blue-light-500/15 dark:text-blue-light-500',
            self::STATUS_PROSES_CETAK => 'bg-warning-50 text-warning-700 dark:bg-warning-500/15 dark:text-warning-500',
            default => 'bg-gray-100 text-gray-700 dark:bg-gray-500/15 dark:text-gray-400',
        };
    }

    public function getPickupStatusBadgeClass(): string
    {
        return match ($this->pickup_status) {
            self::PICKUP_PICKED_UP => 'bg-success-50 text-success-700 dark:bg-success-500/15 dark:text-success-500',
            default => 'bg-orange-50 text-orange-700 dark:bg-orange-500/15 dark:text-orange-500',
        };
    }

    public function markAsPaid(User $cashier, float $paidAmount, string $paymentMethod = 'cash'): void
    {
        $this->payments()->create([
            'amount' => $paidAmount,
            'payment_method' => $paymentMethod,
            'cashier_id' => $cashier->id,
        ]);

        $this->update([
            'payment_status' => self::PAYMENT_PAID,
            'paid_amount' => $paidAmount,
            'change_amount' => $paidAmount - $this->total_price,
            'paid_at' => now(),
            'cashier_id' => $cashier->id,
        ]);
    }

    public function startProduction(?User $operator = null): void
    {
        $this->update([
            'production_status' => self::STATUS_PROSES_CETAK,
            'started_at' => now(),
            'operator_id' => $operator?->id ?? $this->operator_id,
        ]);

        $this->items()->where('production_status', OrderItem::STATUS_PENDING)->update([
            'production_status' => OrderItem::STATUS_PROSES_CETAK,
            'item_started_at' => now(),
        ]);
    }

    public function moveToFinishing(): void
    {
        $this->update([
            'production_status' => self::STATUS_FINISHING,
        ]);

        $this->items()->where('production_status', OrderItem::STATUS_PROSES_CETAK)->update([
            'production_status' => OrderItem::STATUS_FINISHING,
        ]);
    }

    public function markAsComplete(): void
    {
        $update = [
            'production_status' => self::STATUS_SELESAI,
            'finished_at' => now(),
        ];

        if (auth()->check()) {
            $update['finishing_operator_id'] = auth()->id();
        }

        $this->update($update);

        $this->items()->where('production_status', '!=', OrderItem::STATUS_SELESAI)->update([
            'production_status' => OrderItem::STATUS_SELESAI,
            'item_finished_at' => now(),
        ]);
    }

    public function markAsPickedUp(): void
    {
        $this->update([
            'pickup_status' => self::PICKUP_PICKED_UP,
            'picked_up_at' => now(),
        ]);
    }

    public function markSPKPrinted(): void
    {
        static::query()
            ->whereKey($this->getKey())
            ->update([
                'spk_printed' => true,
                'spk_printed_at' => now(),
                'updated_at' => now(),
            ]);

        $this->forceFill([
            'spk_printed' => true,
            'spk_printed_at' => now(),
        ]);

        $this->syncOriginal();
    }

    public function markAsCancelled(User $approver, string $reason): void
    {
        $activeItems = $this->items()->where('is_cancelled', false)->get();
        foreach ($activeItems as $item) {
            if ($item->material_id) {
                $material = Material::find($item->material_id);
                if ($material) {
                    $usageQty = ($item->size_unit === 'm2')
                        ? $item->size_value * $item->qty
                        : $item->qty;
                    $material->addStock(
                        $usageQty,
                        0,
                        'Stok dikembalikan - Batal Order #' . $this->invoice_number,
                        $this
                    );
                }
            }
        }

        $this->update([
            'is_cancelled' => true,
            'cancelled_at' => now(),
            'cancelled_by' => $approver->id,
            'cancel_reason' => $reason,
        ]);

        $this->items()->where('is_cancelled', false)->update([
            'is_cancelled' => true,
            'cancelled_at' => now(),
            'cancelled_by' => $approver->id,
            'cancel_reason' => 'Order dibatalkan: ' . $reason,
        ]);

        $this->recalculateTotal();
    }

    public function canRevertToPending(): bool
    {
        return $this->production_status === self::STATUS_PROSES_CETAK;
    }

    public function canRevertToProsesCetak(): bool
    {
        return $this->production_status === self::STATUS_FINISHING;
    }

    public function revertToPending(): void
    {
        $this->update([
            'production_status' => self::STATUS_PENDING,
            'started_at' => null,
            'operator_id' => null,
        ]);

        $this->items()->update([
            'production_status' => OrderItem::STATUS_PENDING,
            'item_started_at' => null,
            'item_finished_at' => null,
        ]);
    }

    public function revertToProsesCetak(): void
    {
        $this->update([
            'production_status' => self::STATUS_PROSES_CETAK,
        ]);

        $this->items()->where('production_status', OrderItem::STATUS_FINISHING)->update([
            'production_status' => OrderItem::STATUS_PROSES_CETAK,
        ]);
    }

    public function syncProductionStatus(): void
    {
        $statusPriority = [
            OrderItem::STATUS_PENDING => 0,
            OrderItem::STATUS_PROSES_CETAK => 1,
            OrderItem::STATUS_FINISHING => 2,
            OrderItem::STATUS_SELESAI => 3,
        ];

        $items = $this->items()->where('is_cancelled', false)->get();
        if ($items->isEmpty()) {
            return;
        }

        $minPriority = $items->min(fn ($item) => $statusPriority[$item->production_status] ?? 0);
        $newStatus = array_search($minPriority, $statusPriority);

        $updateData = ['production_status' => $newStatus];

        if ($newStatus !== self::STATUS_PENDING && !$this->started_at) {
            $updateData['started_at'] = now();
            $updateData['operator_id'] = auth()->id() ?? $this->operator_id;
        }

        if ($newStatus === self::STATUS_SELESAI && !$this->finished_at) {
            $updateData['finished_at'] = now();

            if (auth()->check() && empty($this->finishing_operator_id)) {
                $updateData['finishing_operator_id'] = auth()->id();
            }
        }

        if ($newStatus !== self::STATUS_SELESAI && $this->finished_at) {
            $updateData['finished_at'] = null;
            $updateData['finishing_operator_id'] = null;
        }

        $this->update($updateData);
    }

    public function getItemStatusSummary(): array
    {
        $items = $this->items;
        return [
            'pending' => $items->where('is_cancelled', false)->where('production_status', OrderItem::STATUS_PENDING)->count(),
            'proses_cetak' => $items->where('is_cancelled', false)->where('production_status', OrderItem::STATUS_PROSES_CETAK)->count(),
            'finishing' => $items->where('is_cancelled', false)->where('production_status', OrderItem::STATUS_FINISHING)->count(),
            'selesai' => $items->where('is_cancelled', false)->where('production_status', OrderItem::STATUS_SELESAI)->count(),
            'cancelled' => $items->where('is_cancelled', true)->count(),
            'total' => $items->count(),
        ];
    }

    public function hasPartialItemStatus(): bool
    {
        return $this->items()->distinct('production_status')->count('production_status') > 1;
    }

    public function items(): HasMany
    {
        return $this->hasMany(OrderItem::class);
    }

    public function activeItems(): HasMany
    {
        return $this->hasMany(OrderItem::class)->where('is_cancelled', false);
    }

    public function payments(): HasMany
    {
        return $this->hasMany(Payment::class);
    }

    public function cancellationRequests(): HasMany
    {
        return $this->hasMany(OrderCancellationRequest::class);
    }

    public function canceller(): BelongsTo
    {
        return $this->belongsTo(User::class, 'cancelled_by');
    }

    public function customer(): BelongsTo
    {
        return $this->belongsTo(Customer::class);
    }

    public function designer(): BelongsTo
    {
        return $this->belongsTo(User::class, 'designer_id');
    }

    public function cashier(): BelongsTo
    {
        return $this->belongsTo(User::class, 'cashier_id');
    }

    public function operator(): BelongsTo
    {
        return $this->belongsTo(User::class, 'operator_id');
    }

    public function finishingOperator(): BelongsTo
    {
        return $this->belongsTo(User::class, 'finishing_operator_id');
    }

    public function recalculateTotal(): void
    {
        $this->total_price = $this->items()->where('is_cancelled', false)->sum('subtotal');
        $this->saveQuietly();
    }

    public function getTotalPaid(): float
    {
        return (float) $this->payments()->sum('amount');
    }

    public function getRemainingBalance(): float
    {
        return max(0, $this->total_price - $this->getTotalPaid());
    }

    public function addPayment(User $cashier, float $amount, string $method = 'cash', ?string $proofPath = null, ?string $notes = null, ?int $paymentAccountId = null): Payment
    {
        $payment = $this->payments()->create([
            'amount' => $amount,
            'payment_method' => $method,
            'payment_account_id' => $paymentAccountId,
            'proof_path' => $proofPath,
            'notes' => $notes,
            'cashier_id' => $cashier->id,
        ]);

        $totalPaid = $this->getTotalPaid();
        $this->paid_amount = $totalPaid;

        if ($totalPaid >= $this->total_price) {
            $this->payment_status = self::PAYMENT_PAID;
            $this->change_amount = $totalPaid - $this->total_price;
            $this->paid_at = now();
            $this->cashier_id = $cashier->id;
        } else {
            $this->payment_status = self::PAYMENT_PARTIAL;
            $this->change_amount = 0;
        }

        $this->save();

        return $payment;
    }

    public function getTotalQtyAttribute(): int
    {
        return $this->items()->sum('qty');
    }

    public function getItemsCountAttribute(): int
    {
        return $this->items()->count();
    }

    public function getFormattedTotalPriceAttribute(): string
    {
        return 'Rp ' . number_format($this->total_price, 0, ',', '.');
    }

    public function scopeUnpaid($query) { return $query->where('payment_status', self::PAYMENT_UNPAID); }
    public function scopePartiallyPaid($query) { return $query->where('payment_status', self::PAYMENT_PARTIAL); }
    public function scopeWithReceivables($query) { return $query->whereIn('payment_status', [self::PAYMENT_UNPAID, self::PAYMENT_PARTIAL]); }
    public function scopePaid($query) { return $query->where('payment_status', self::PAYMENT_PAID); }
    public function scopePending($query) { return $query->where('production_status', self::STATUS_PENDING); }
    public function scopeInProgress($query) { return $query->whereIn('production_status', [self::STATUS_PROSES_CETAK, self::STATUS_FINISHING]); }
    public function scopeCompleted($query) { return $query->where('production_status', self::STATUS_SELESAI); }
    public function scopePickedUp($query) { return $query->where('pickup_status', self::PICKUP_PICKED_UP); }
    public function scopeNotPickedUp($query) { return $query->where('pickup_status', self::PICKUP_NOT_PICKED_UP); }
    public function scopeNotCompleted($query) { return $query->where('production_status', '!=', self::STATUS_SELESAI); }
    public function scopeNotCancelled($query) { return $query->where('is_cancelled', false); }
    public function scopeCancelled($query) { return $query->where('is_cancelled', true); }
    public function scopeToday($query) { return $query->whereDate('created_at', today()); }
    public function scopeDateRange($query, $startDate, $endDate) { return $query->whereBetween('created_at', [$startDate, $endDate]); }

    public function getCancelledBadgeClass(): string
    {
        return 'bg-error-50 text-error-700 dark:bg-error-500/15 dark:text-error-500';
    }
}