<?php

namespace App\Models;

use App\CentralLogics\Helpers;
use Carbon\Carbon;
use App\Scopes\ZoneScope;
use App\Traits\DemoMaskable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Facades\Schema;
use App\Traits\ReportFilter;
use Modules\TaxModule\Entities\OrderTax;


class Order extends Model
{
    use HasFactory , ReportFilter, DemoMaskable;

    public const ADVANCE_RECEIVE_BLOCKED_STATUSES = [
        'delivered',
        'returned',
        'canceled',
        'refund_requested',
        'refunded',
        'failed',
    ];

    protected $casts = [
        'order_amount' => 'float',
        'coupon_discount_amount' => 'float',
        'total_tax_amount' => 'float',
        'store_discount_amount' => 'float',
        'flash_admin_discount_amount' => 'float',
        'flash_store_discount_amount' => 'float',
        'delivery_address_id' => 'integer',
        'delivery_man_id' => 'integer',
        'delivery_charge' => 'float',
        'additional_charge' => 'float',
        'original_delivery_charge' => 'float',
        'user_id' => 'integer',
        'zone_id' => 'integer',
        'scheduled' => 'integer',
        'store_id' => 'integer',
        'details_count' => 'integer',
        'module_id' => 'integer',
        'dm_vehicle_id' => 'integer',
        'processing_time' => 'integer',
        'created_at' => 'datetime',
        'updated_at' => 'datetime',
        'extra_packaging_amount' => 'float',
        'receiver_details' => 'array',
        'dm_tips' => 'float',
        'distance' => 'float',
        'tax_percentage' => 'float',
        'prescription_order' => 'boolean',
        'cutlery' => 'boolean',
        'is_guest' => 'boolean',
        'ref_bonus_amount' => 'float',
        'bring_change_amount'=>'integer',
    ];

    protected $appends = ['module_type','order_attachment_full_url','order_proof_full_url'];

    public function getOrderAttachmentFullUrlAttribute(){
        $images = [];
        $attachment[] = $this->order_attachment;

        $value = is_array($attachment)
            ? $attachment
            : ($attachment && is_string($attachment) && $this->isValidJson($attachment)
                ? json_decode($attachment, true)
                : []);



        if ($value) {
            foreach ($value as $item) {
                $item = is_array($item) ? $item : (is_object($item) && get_class($item) == 'stdClass' ? json_decode(json_encode($item), true) : ['img' => $item, 'storage' => 'public']);
                $images[] = Helpers::get_full_url('order', $item['img'], $item['storage']);
            }
        }

        return $images;
    }

    public function getOrderProofFullUrlAttribute(){
        $images = [];
        $value = is_array($this->order_proof)
            ? $this->order_proof
            : ($this->order_proof && is_string($this->order_proof) && $this->isValidJson($this->order_proof)
                ? json_decode($this->order_proof, true)
                : []);
        if ($value){
            foreach ($value as $item){
                $item = is_array($item)?$item:(is_object($item) && get_class($item) == 'stdClass' ? json_decode(json_encode($item), true):['img' => $item, 'storage' => 'public']);
                $images[] = Helpers::get_full_url('order',$item['img'],$item['storage']);
            }
        }

        return $images;
    }

    public function getDeliveryAddressAttribute($value)
    {
        $value = $value ?: ($this->getAttributes()['shipping_address_data'] ?? $this->getRawOriginal('shipping_address_data'));

        if ($this->shouldMask()) {
            if (is_array($value)) {
                $address = $value;
            } elseif ($value instanceof \stdClass) {
                $address = (array) $value;
            } elseif (is_string($value)) {
                $address = json_decode($value, true);
            } else {
                $address = [];
            }

            if (!empty($address['contact_person_email'])) {
                $address['contact_person_email'] = $this->maskEmail($address['contact_person_email']);
            }

            if (!empty($address['contact_person_number'])) {
                $address['contact_person_number'] = $this->maskPhone($address['contact_person_number']);
            }
            return json_encode($address);
        }
        return $value;
    }

    public function getUserIdAttribute($value)
    {
        $value = $value ?? ($this->getAttributes()['customer_id'] ?? $this->getRawOriginal('customer_id'));

        return is_numeric($value) ? (int) $value : $value;
    }

    public function getTransactionReferenceAttribute($value)
    {
        return $value ?? ($this->getAttributes()['transaction_ref'] ?? $this->getRawOriginal('transaction_ref'));
    }

    public function getReceiverDetailsAttribute($value)
    {
        $address = is_array($value) ? $value : json_decode($value, true);
        if ($this->shouldMask()) {
            if (!empty($address['contact_person_email'])) {
                $address['contact_person_email'] = $this->maskEmail($address['contact_person_email']);
            }
            if (!empty($address['contact_person_number'])) {
                $address['contact_person_number'] = $this->maskPhone($address['contact_person_number']);
            }
        }

        return $address;
    }

    private function isValidJson($value)
    {
        json_decode($value);
        return (json_last_error() === JSON_ERROR_NONE);
    }

    public function setDeliveryChargeAttribute($value)
    {
        $this->attributes['delivery_charge'] = round($value, 3);
    }

    public function cashback_history()
    {
        return $this->hasOne(CashBackHistory::class, 'order_id');
    }
    public function parcelCancellation()
    {
        return $this->hasOne(ParcelCancellation::class, 'order_id');
    }


    public function offline_payments()
    {
        return $this->belongsTo(OfflinePayments::class,'id','order_id');
    }

    public function details()
    {
        return $this->hasMany(OrderDetail::class);
    }
    public function reviews()
    {
        return $this->hasMany(Review::class , 'order_id');
    }

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

    public function receipts()
    {
        return $this->hasMany(OrderReceipt::class)->latest('received_at')->latest('id');
    }

    public function exchanges()
    {
        return $this->hasMany(OrderExchange::class)->latest('exchanged_at')->latest('id');
    }

    public function customerLedgerEntries()
    {
        return $this->hasMany(CustomerLedgerEntry::class)->latest('entry_date')->latest('id');
    }

    public function canReceiveAdvance(): bool
    {
        return ! in_array($this->order_status, self::ADVANCE_RECEIVE_BLOCKED_STATUSES, true);
    }

    public function financialSummary(): array
    {
        $payments = $this->relationLoaded('payments')
            ? $this->payments
            : $this->payments()->get();
        $receipts = $this->relationLoaded('receipts')
            ? $this->receipts
            : (Schema::hasTable('order_receipts') ? $this->receipts()->get() : collect());
        $exchanges = $this->relationLoaded('exchanges')
            ? $this->exchanges
            : (Schema::hasTable('order_exchanges') ? $this->exchanges()->get() : collect());

        $invoiceTotal = round(max((float) $this->order_amount, 0), 3);
        $advanceReceipts = $receipts->where(
            'account_type',
            OrderReceipt::ACCOUNT_TYPE_CUSTOMER_ADVANCE
        );
        $advanceReceived = round((float) $advanceReceipts
            ->where('receipt_status', 'posted')
            ->sum('amount'), 3);
        $paidFromRows = round((float) $payments
            ->where('payment_status', 'paid')
            ->sum('amount'), 3);
        $legacyPaid = max((float) ($this->partially_paid_amount ?? 0), 0);
        $hasReceiptHistory = $advanceReceipts->isNotEmpty();
        $paidAmount = $hasReceiptHistory
            ? max($paidFromRows, $advanceReceived)
            : max($paidFromRows, $legacyPaid, $advanceReceived);

        if ($this->payment_status === 'paid' && ! $hasReceiptHistory) {
            $paidAmount = max($paidAmount, $invoiceTotal);
        }

        $postedExchanges = $exchanges->where('exchange_status', 'posted');
        $exchangeAdjustment = round((float) $postedExchanges->sum('difference_amount'), 3);
        $collectableExchangeAdjustment = round((float) $postedExchanges
            ->where('affects_collectable', true)
            ->sum('difference_amount'), 3);
        $adjustedTotal = max(round($invoiceTotal + $exchangeAdjustment, 3), 0);
        $courierTotal = max(round($invoiceTotal + $collectableExchangeAdjustment, 3), 0);

        return [
            'invoice_total' => $invoiceTotal,
            'advance_received' => $advanceReceived,
            'paid_amount' => round($paidAmount, 3),
            'exchange_adjustment' => $exchangeAdjustment,
            'adjusted_total' => $adjustedTotal,
            'remaining_due' => max(round($adjustedTotal - $paidAmount, 3), 0),
            'customer_credit' => max(round($paidAmount - $adjustedTotal, 3), 0),
            'courier_collectable' => max(round($courierTotal - $paidAmount, 3), 0),
        ];
    }

    public function paymentSplitSummary(): array
    {
        $payments = $this->relationLoaded('payments')
            ? $this->payments
            : $this->payments()->get();
        $totalAmount = max((float) $this->order_amount, 0);
        $walletPaymentAmount = (float) $payments
            ->where('payment_method', 'wallet')
            ->sum('amount');
        $walletAmount = max(
            (float) $this->partially_paid_amount,
            $walletPaymentAmount
        );

        if ($this->payment_method === 'wallet') {
            $walletAmount = $totalAmount;
        }

        $walletAmount = min($walletAmount, $totalAmount);
        $remainingAmount = max($totalAmount - $walletAmount, 0);
        $secondaryPayment = $payments
            ->where('payment_method', '!=', 'wallet')
            ->sortByDesc(fn ($payment) => $payment->payment_status === 'unpaid')
            ->first();
        $selectedPaymentMethod = $remainingAmount <= 0
            ? 'wallet'
            : ($secondaryPayment?->payment_method
                ?? ($this->payment_method === 'partial_payment'
                    ? null
                    : $this->payment_method));

        return [
            'wallet_amount' => $walletAmount,
            'remaining_amount' => $remainingAmount,
            'selected_payment_method' => $selectedPaymentMethod,
            'total_amount' => $totalAmount,
        ];
    }

    public function appendPaymentSplitSummary(): self
    {
        foreach ($this->paymentSplitSummary() as $key => $value) {
            $this->setAttribute($key, $value);
        }

        return $this;
    }

    public function delivery_man()
    {
        return $this->belongsTo(DeliveryMan::class, 'delivery_man_id');
    }

    public function customer()
    {
        return $this->belongsTo(User::class, 'user_id');
    }

    public function guest()
    {
        return $this->belongsTo(Guest::class, 'user_id','id');
    }

    public function coupon()
    {
        return $this->belongsTo(Coupon::class, 'coupon_code', 'code');
    }

    public function store()
    {
        return $this->belongsTo(Store::class, 'store_id');
    }

    public function zone()
    {
        return $this->belongsTo(Zone::class, 'zone_id');
    }

    public function module()
    {
        return $this->belongsTo(Module::class);
    }

    public function delivery_history()
    {
        return $this->hasMany(DeliveryHistory::class, 'order_id');
    }

    public function dm_last_location()
    {
        // return $this->hasOne(DeliveryHistory::class, 'order_id')->latest();
        return $this->delivery_man->last_location();
    }

    public function transaction()
    {
        return $this->hasOne(OrderTransaction::class);
    }

    public function parcel_category()
    {
        return $this->belongsTo(ParcelCategory::class);
    }

    public function refund()
    {
        return $this->hasOne(Refund::class, 'order_id');
    }
    public function courierShipment()
    {
        return $this->hasOne(OrderCourierShipment::class, 'order_id');
    }

    public function isCourierManagedOrder(): bool
    {
        return $this->resolvedCourierShipment() !== null;
    }

    public function isCourierConfirmedOutForDelivery(): bool
    {
        $shipment = $this->resolvedCourierShipment();

        return $shipment?->isCourierConfirmedOutForDelivery() === true;
    }

    /**
     * Never expose a stale/manual picked_up state for a courier-managed order.
     * Provider-confirmed status plus provider rider data is the only courier
     * path that may be presented as Out for Delivery.
     */
    public function courierSafeOrderStatus(): string
    {
        $status = (string) $this->getAttribute('order_status');

        if ($status !== 'picked_up') {
            return $status;
        }

        $shipment = $this->resolvedCourierShipment();

        if (! $shipment) {
            return $status;
        }

        return $shipment->isCourierConfirmedOutForDelivery() ? 'picked_up' : 'handover';
    }

    private function resolvedCourierShipment(): ?OrderCourierShipment
    {
        if ($this->relationLoaded('courierShipment')) {
            return $this->getRelation('courierShipment');
        }

        if (! Schema::hasTable('order_courier_shipments')) {
            return null;
        }

        return $this->courierShipment()->first();
    }

    public function OrderReference()
    {
        return $this->hasOne(OrderReference::class, 'order_id');
    }

    public function getModuleTypeAttribute()
    {
        return $this->module ? $this->module->module_type : null;
    }

    public function scopeAccepteByDeliveryman($query)
    {
        return $query->where('order_status', 'accepted');
    }

    public function scopePreparing($query)
    {
        return $query->whereIn('order_status', ['confirmed', 'processing', 'handover']);
    }

    public function scopeConfirmedForAdminList($query)
    {
        $query->whereIn('order_status', ['confirmed', 'accepted']);

        if (Schema::hasColumn('orders', 'confirmed')) {
            $query->whereNotNull('confirmed');
        }

        if (Schema::hasTable('order_courier_shipments')) {
            $query->whereDoesntHave('courierShipment');
        }

        if (Schema::hasColumn('orders', 'delivery_service_name')) {
            $query->where(function ($courierQuery) {
                $courierQuery->whereNull('delivery_service_name')
                    ->orWhere('delivery_service_name', '');
            });
        }

        return $query;
    }

    public function scopeCourierForAdminList($query)
    {
        return $query->whereNotIn('order_status', ['delivered', 'returned', 'failed', 'canceled', 'refund_requested', 'refund_request_canceled', 'refunded'])
            ->where(function ($query) {
                $query->where('order_status', 'handover');

                if (Schema::hasTable('order_courier_shipments')) {
                    $query->orWhereHas('courierShipment', function ($shipmentQuery) {
                        $shipmentQuery->whereIn('booking_status', ['not_sent', 'failed', 'booked'])
                            ->orWhereNotNull('sent_to_courier_at');
                    });
                }

                if (Schema::hasColumn('orders', 'delivery_service_name')) {
                    $query->orWhere(function ($courierQuery) {
                        $courierQuery->whereNotNull('delivery_service_name')
                            ->where('delivery_service_name', '!=', '');
                    });
                }
            });
    }

    public function scopeModule($query, $module_id)
    {
        return $query->where('module_id', $module_id);
    }

    public function scopeOngoing($query)
    {
        return $query->whereIn('order_status', ['accepted', 'confirmed', 'processing', 'handover', 'picked_up']);
    }

    public function scopeItemOnTheWay($query)
    {
        return $query->where('order_status', 'picked_up')
            ->whereHas('courierShipment', function ($shipmentQuery) {
                $shipmentQuery->where('status_mapping_result', 'out_for_delivery')
                    ->whereNotNull('out_for_delivery_at')
                    ->where(function ($riderQuery) {
                        $riderQuery->where(function ($query) {
                            $query->whereNotNull('courier_rider_id')
                                ->where('courier_rider_id', '!=', '');
                        })->orWhere(function ($query) {
                            $query->whereNotNull('courier_rider_name')
                                ->where('courier_rider_name', '!=', '');
                        })->orWhere(function ($query) {
                            $query->whereNotNull('courier_rider_phone')
                                ->where('courier_rider_phone', '!=', '');
                        });
                    });
            });
    }

    public function scopePending($query)
    {
        return $query->where('order_status', 'pending');
    }

    public function scopeFailed($query)
    {
        return $query->where('order_status', 'failed')->whereDoesntHave('offline_payments');
    }

    public function scopeCanceled($query)
    {
        return $query->where('order_status', 'canceled');
    }

    public function scopeDelivered($query)
    {
        return $query->where('order_status', 'delivered');
    }

    public function scopeNotRefunded($query)
    {
        return $query->where(function ($query) {
            $query->whereNotIn('order_status', ['refunded']);
        });
    }

    public function scopeRefunded($query)
    {
        return $query->where('order_status', 'refunded');
    }
    public function scopeRefund_requested($query)
    {
        return $query->where('order_status', 'refund_requested');
    }

    public function scopeRefund_request_canceled($query)
    {
        return $query->where('order_status', 'refund_request_canceled');
    }

    public function scopeSearchingForDeliveryman($query)
    {
        $query->whereNull('delivery_man_id')
            ->whereIn('order_type', ['delivery', 'parcel'])
            ->whereNotIn('order_status', ['delivered', 'failed', 'canceled', 'refund_requested', 'refund_request_canceled', 'refunded']);

        if (Schema::hasTable('order_courier_shipments')) {
            $query->whereDoesntHave('courierShipment', function ($shipmentQuery) {
                $shipmentQuery->where('booking_status', 'booked')
                    ->whereNotNull('courier_parcel_id');
            });
        }

        if (Schema::hasColumn('orders', 'delivery_service_name')) {
            $query->where(function ($courierQuery) {
                $courierQuery->whereNull('delivery_service_name')
                    ->orWhere('delivery_service_name', '');
            });
        }

        return $query;
    }

    public function scopeDelivery($query)
    {
        return $query->where('order_type', '=', 'delivery');
    }

    public function scopeScheduled($query)
    {
        return $query->whereRaw('created_at <> schedule_at')->where('scheduled', '1');
    }

    public function scopeOrderScheduledIn($query, $interval)
    {
        return $query->where(function ($query) use ($interval) {
            $query->whereRaw('created_at <> schedule_at')->where(function ($q) use ($interval) {
                $q->whereBetween('schedule_at', [Carbon::now()->toDateTimeString(), Carbon::now()->addMinutes($interval)->toDateTimeString()]);
            })->orWhere('schedule_at', '<', Carbon::now()->toDateTimeString());
        })->orWhereRaw('created_at = schedule_at');
    }


    public function scopeStoreOrder($query)
    {
        return $query->where(function ($q) {
            $q->where('order_type', 'take_away')->orWhere('order_type', 'delivery');
        });
    }

    public function scopeDmOrder($query)
    {
        return $query->where(function ($q) {
            $q->where('order_type', 'parcel')->orWhere('order_type', 'delivery');
        });
    }

    public function scopeParcelOrder($query)
    {
        return $query->where('order_type', 'parcel');
    }

    public function scopePos($query)
    {
        return $query->where('order_type', '=', 'pos');
    }

    public function scopeNotpos($query)
    {
        return $query->where('order_type', '<>', 'pos');
    }

    public function scopeNotDigitalOrder($query)
    {
        return $query->where(function ($q){
            $q->whereNotIn('payment_method', ['digital_payment','offline_payment'])->orwhereNot('order_status' , 'pending');
        });
    }

    public function getCreatedAtAttribute($value)
    {
        return date('Y-m-d H:i:s', strtotime($value));
    }

    protected static function booted()
    {
        static::addGlobalScope(new ZoneScope);
        static::addGlobalScope('storage', function ($builder) {
            $builder->with('storage');
        });
    }
    public function storage()
    {
        return $this->morphMany(Storage::class, 'data');
    }
    protected static function boot()
    {
        parent::boot();
    }
    public function orderTaxes()
    {
        return $this->morphMany(OrderTax::class, 'order');
    }
}
