<?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\HasMany;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;

class Booking extends Model
{
    use HasFactory;

    /* =========================================================================
     | Fillable & Casts
     |=========================================================================*/

    /** @var array<int,string> */
    protected $fillable = [
        'customer_id',
        'vehicle_id',
        'vehicle',
        'reference',
        'external_reference',
        'status',

        // dates / times
        'start_date',
        'end_date',
        'start_at',
        'end_at',
        'pickup_at',
        'return_at',

        // money
        'total_amount',
        'deposit_amount',
        'hold_amount',
        'paid_amount',
        'currency',

        // vehicle info
        'vehicle_name',
        'vevs_car_id',

        // portal / stripe
        'portal_token',
        'stripe_payment_intent_id',

        // misc/meta
        'meta',
        'billing_address',

        // brand/source:
        'brand',
        'source_system',
        'source_id',
        'source_updated_at',
    ];

    /** Casts */
    protected $casts = [
        // dates & times
        'start_date'        => 'date',
        'end_date'          => 'date',
        'start_at'          => 'datetime',
        'end_at'            => 'datetime',
        'pickup_at'         => 'datetime',
        'return_at'         => 'datetime',

        // money
        'total_amount'      => 'integer',
        'deposit_amount'    => 'integer',
        'hold_amount'       => 'integer',
        'paid_amount'       => 'integer',

        // meta / json
        'meta'              => 'array',
        'billing_address'   => 'array',

        // bond lifecycle (if present)
        'bond_authorized_at'=> 'datetime',
        'bond_captured_at'  => 'datetime',
        'bond_released_at'  => 'datetime',

        // source
        'source_updated_at' => 'datetime',
    ];

    /* =========================================================================
     | Eager-loading
     |=========================================================================*/

    protected $with = ['customer'];

    public function __construct(array $attributes = [])
    {
        parent::__construct($attributes);

        if (class_exists(\App\Models\Vehicle::class)) {
            $this->with = array_values(array_unique(array_merge($this->with, ['vehicle'])));
        }
    }

    /* =========================================================================
     | Appended Accessors
     |=========================================================================*/

    /** @var array<int,string> */
    protected $appends = [
        'portal_url',
        'total_formatted',
        'deposit_formatted',
        'hold_formatted',
        'amount_paid_formatted',
        'balance_due_formatted',
        'deposit_paid_formatted',
        'deposit_due_formatted',
        'deposit_paid_so_far_formatted',
        'customer_name',
        'customer_real_name',
        'car_label',
        'brand_label',
        'vehicle_label', // friendly label based on VEVS mapping
    ];

    /* =========================================================================
     | Boot Logic
     |=========================================================================*/

    protected static function booted(): void
    {
        static::creating(function (self $booking): void {
            if (empty($booking->reference)) {
                do {
                    $candidate = 'BK-' . now()->format('ymdHis') . '-' . Str::upper(Str::random(3));
                } while (self::where('reference', $candidate)->exists());

                $booking->reference = $candidate;
            }

            if (empty($booking->currency)) {
                $booking->currency = 'NZD';
            }

            if (Schema::hasColumn($booking->getTable(), 'brand') && empty($booking->brand)) {
                $booking->brand = self::inferBrandFromSource($booking->source_system);
            }

            if (empty($booking->portal_token)) {
                $booking->portal_token = Str::random(40);
            }
        });
    }

    /** Create a portal token in-memory only. */
    public function ensurePortalToken(): self
    {
        if (empty($this->portal_token)) {
            $this->portal_token = Str::random(40);
        }

        return $this;
    }

    /* =========================================================================
     | Relationships
     |=========================================================================*/

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

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

    public function deposits(): HasMany
    {
        if (Schema::hasTable('deposits') && Schema::hasColumn('deposits', 'booking_id')) {
            return $this->hasMany(\App\Models\Deposit::class, 'booking_id', 'id');
        }

        return $this->hasMany(\App\Models\Deposit::class, 'booking_reference', 'reference');
    }

    public function vehicle(): BelongsTo
    {
        return $this->belongsTo(\App\Models\Vehicle::class);
    }

    public function vevsVehicle()
    {
        return $this->hasOne(\App\Models\VevsVehicle::class, 'vevs_car_id', 'vevs_car_id');
    }

    /**
     * Jobs generated in the payments/holds context for this booking.
     */
    public function jobs(): HasMany
    {
        return $this->hasMany(\App\Models\Job::class);
    }

    /* =========================================================================
     | Accessors - Customer
     |=========================================================================*/

    public function getCustomerNameAttribute(): string
    {
        $first = trim((string) data_get($this->customer, 'first_name', ''));
        $last  = trim((string) data_get($this->customer, 'last_name', ''));
        $full  = trim($first . ' ' . $last);

        return $full !== '' ? $full : trim((string) data_get($this->meta, 'customer.name', ''));
    }

    public function getCustomerRealNameAttribute(): string
    {
        $modelName = trim((string) data_get($this->customer, 'full_name', ''));
        if ($modelName !== '') {
            return $modelName;
        }

        $basic = $this->customer_name;
        if ($basic !== '') {
            return $basic;
        }

        return (string) data_get($this->customer, 'email', '');
    }

    /* =========================================================================
     | Accessors - Vehicle Labels
     |=========================================================================*/

    /** Friendly label for the vehicle model */
    public function getCarLabelAttribute(): string
    {
        if (
            class_exists(\App\Models\Vehicle::class) &&
            $this->relationLoaded('vehicle') &&
            $this->vehicle
        ) {
            $v = $this->vehicle;

            $display = trim((string) data_get($v, 'display_name', ''));
            if ($display !== '') {
                return $display;
            }

            $parts = array_filter([
                trim((string) data_get($v, 'make', '')),
                trim((string) data_get($v, 'model', '')),
            ]);

            $label = trim(implode(' ', $parts));

            $plate = trim((string) data_get($v, 'plate', ''));
            if ($plate !== '') {
                $label = $label !== '' ? ($label . ' • ' . $plate) : $plate;
            }

            if ($label !== '') {
                return $label;
            }
        }

        return (string) ($this->vehicle ?? '');
    }

    /** Friendly vehicle label based on VEVS mapping */
    public function getVehicleLabelAttribute(): ?string
    {
        $id = $this->vevs_car_id;
        if (! $id) {
            return null;
        }

        $map   = (array) config('vevs_cars', []);
        $idStr = (string) $id;

        if (array_key_exists($idStr, $map)) {
            return (string) $map[$idStr];
        }

        return 'VEVS car ' . $idStr;
    }

    /* =========================================================================
     | Accessors - Brand & Portal
     |=========================================================================*/

    public function getBrandLabelAttribute(): string
    {
        $brand = strtolower((string) ($this->brand ?? $this->source_system ?? ''));

        return $brand === 'jimny' ? 'Jimny' : 'Dream Drives';
    }

    public function getPortalUrlAttribute(): string
    {
        if (! empty($this->portal_token)) {
            return route('portal.pay.token', $this->portal_token);
        }

        return '';
    }

    /* =========================================================================
     | Money (raw values)
     |=========================================================================*/

    public function getPaidAmountDollarsAttribute(): string
    {
        return number_format(($this->paid_amount ?? 0) / 100, 2, '.', '');
    }

    public function getRemainingDueAttribute(): int
    {
        return max((int) ($this->total_amount ?? 0) - (int) ($this->paid_amount ?? 0), 0);
    }

    public function getAmountPaidAttribute(): int
    {
        return $this->sumCentsFrom('payments', 'amount', $this->paidStatuses());
    }

    public function getDepositPaidAttribute(): int
    {
        $sum = 0;

        // Preferred: deposits table
        if (
            Schema::hasTable('deposits') &&
            Schema::hasColumn('deposits', 'booking_id') &&
            Schema::hasColumn('deposits', 'amount')
        ) {
            $dq = DB::table('deposits')->where('booking_id', $this->id);

            if (Schema::hasColumn('deposits', 'status')) {
                $dq->whereIn('status', $this->paidStatuses());
            }

            $sum += (int) $dq->sum('amount');
            if ($sum > 0) {
                return $sum;
            }
        }

        // Fallback: payments
        if (Schema::hasTable('payments')) {
            $pq = DB::table('payments');

            if (Schema::hasColumn('payments', 'booking_reference')) {
                $pq->where('booking_reference', $this->reference);
            } elseif (Schema::hasColumn('payments', 'booking_id')) {
                $pq->where('booking_id', $this->id);
            }

            if (Schema::hasColumn('payments', 'purpose')) {
                $pq->where('purpose', 'deposit');
            } elseif (Schema::hasColumn('payments', 'type')) {
                $pq->where('type', 'deposit');
            }

            if (Schema::hasColumn('payments', 'status')) {
                $pq->whereIn('status', $this->paidStatuses());
            }

            $pq->select(DB::raw(
                Schema::hasColumn('payments', 'amount_cents')
                    ? 'COALESCE(SUM(amount_cents),0) as sum_amount'
                    : (Schema::hasColumn('payments', 'amount')
                        ? 'COALESCE(SUM(ROUND(amount*100)),0) as sum_amount'
                        : '0 as sum_amount')
            ));

            $row = $pq->first();
            if ($row && property_exists($row, 'sum_amount')) {
                $sum += (int) $row->sum_amount;
            }
        }

        return $sum;
    }

    public function getDepositPaidSoFarAttribute(): int
    {
        return (int) $this->deposit_paid;
    }

    public function getBalanceDueAttribute(): int
    {
        return max((int) ($this->total_amount ?? 0) - (int) ($this->amount_paid ?? 0), 0);
    }

    public function getDepositDueAttribute(): int
    {
        return max((int) ($this->deposit_amount ?? 0) - (int) ($this->deposit_paid ?? 0), 0);
    }

    /* =========================================================================
     | Money (formatted)
     |=========================================================================*/

    public function getTotalFormattedAttribute(): string
    {
        return $this->formatCents($this->total_amount);
    }

    public function getDepositFormattedAttribute(): string
    {
        return $this->formatCents($this->deposit_amount);
    }

    public function getHoldFormattedAttribute(): string
    {
        return $this->formatCents($this->hold_amount);
    }

    public function getAmountPaidFormattedAttribute(): string
    {
        return $this->formatCents($this->amount_paid);
    }

    public function getDepositPaidFormattedAttribute(): string
    {
        return $this->formatCents($this->deposit_paid);
    }

    public function getDepositPaidSoFarFormattedAttribute(): string
    {
        return $this->formatCents($this->deposit_paid_so_far);
    }

    public function getDepositDueFormattedAttribute(): string
    {
        return $this->formatCents($this->deposit_due);
    }

    public function getBalanceDueFormattedAttribute(): string
    {
        return $this->formatCents($this->balance_due);
    }

    /* =========================================================================
     | Scopes
     |=========================================================================*/

    public function scopeUpcoming(Builder $query): Builder
    {
        return $query->where('start_at', '>=', now())->orderBy('start_at');
    }

    public function scopeActive(Builder $query): Builder
    {
        return $query->where('start_at', '<=', now())->where('end_at', '>=', now());
    }

    public function scopeNeedsAttention(Builder $query): Builder
    {
        return $query->where(function (Builder $q) {
            $q->whereNull('deposit_amount')
              ->orWhere('deposit_amount', 0)
              ->orWhereHas('customer', function (Builder $cq) {
                  $cq->whereNull('first_name')->whereNull('last_name');
              });
        });
    }

    public function scopeBrand(Builder $query, string $brand): Builder
    {
        $table = $this->getTable();

        return $query->where(function (Builder $q) use ($brand, $table) {
            $q->when(Schema::hasColumn($table, 'brand'), function (Builder $qb) use ($brand) {
                $qb->where('brand', $brand);
            })->orWhere(function (Builder $qb) use ($brand) {
                $map = $brand === 'jimny'
                    ? ['jimny', 'vevs_jimny']
                    : ['dreamdrives', 'dd', ''];

                $qb->whereIn('source_system', $map);
            });
        });
    }

    /* =========================================================================
     | Internals
     |=========================================================================*/

    /** @return array<int,string> */
    protected function paidStatuses(): array
    {
        return ['succeeded', 'paid', 'captured', 'completed'];
    }

    protected function sumCentsFrom(string $relation, string $column = 'amount', ?array $statuses = null): int
    {
        if (! method_exists($this, $relation)) {
            return 0;
        }

        $rel = $this->{$relation}();

        try {
            if ($statuses && Schema::hasColumn($rel->getModel()->getTable(), 'status')) {
                $rel = $rel->whereIn('status', $statuses);
            }
        } catch (\Throwable) {
            // ignore
        }

        try {
            return (int) $rel->sum($column);
        } catch (\Throwable) {
            return 0;
        }
    }

    private static function inferBrandFromSource(?string $source): ?string
    {
        $s = strtolower((string) $source);

        return match ($s) {
            'jimny', 'vevs_jimny'   => 'jimny',
            'dreamdrives', 'dd', '' => 'dreamdrives',
            default                 => $s ?: null,
        };
    }

    private function formatCents(?int $cents): string
    {
        return '$' . number_format(((int) ($cents ?? 0)) / 100, 2);
    }
}
