<?php

namespace Fleetbase\FleetOps\Models;

use Fleetbase\Casts\Json;
use Fleetbase\Casts\Money;
use Fleetbase\FleetOps\Support\Utils;
use Fleetbase\Models\File;
use Fleetbase\Models\Model;
use Fleetbase\Models\User;
use Fleetbase\Traits\HasApiModelBehavior;
use Fleetbase\Traits\HasCustomFields;
use Fleetbase\Traits\HasMetaAttributes;
use Fleetbase\Traits\HasPublicId;
use Fleetbase\Traits\HasUuid;
use Fleetbase\Traits\Searchable;
use Fleetbase\Traits\TracksApiCredential;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\Relation;
use Spatie\Activitylog\LogOptions;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;

/**
 * Class Equipment.
 *
 * Represents auxiliary gear that can be assigned to assets or other entities.
 * Examples include PPE, refrigeration units, tools, liftgates, and other equipment.
 */
class Equipment extends Model
{
    use HasUuid;
    use HasPublicId;
    use TracksApiCredential;
    use HasApiModelBehavior;
    use HasSlug;
    use LogsActivity;
    use HasMetaAttributes;
    use Searchable;
    use HasCustomFields;

    /**
     * The database table used by the model.
     *
     * @var string
     */
    protected $table = 'equipments';

    /**
     * The type of public Id to generate.
     *
     * @var string
     */
    protected $publicIdType = 'equipment';

    /**
     * The attributes that can be queried.
     *
     * @var array
     */
    protected $searchableColumns = ['name', 'code', 'type', 'serial_number', 'manufacturer', 'model', 'public_id'];

    /**
     * The attributes that can be used for filtering.
     *
     * @var array
     */
    protected $filterParams = ['type', 'status', 'manufacturer', 'warranty_uuid', 'equipable_type'];

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'company_uuid',
        'warranty_uuid',
        'photo_uuid',
        'name',
        'code',
        'type',
        'status',
        'serial_number',
        'manufacturer',
        'model',
        'equipable_type',
        'equipable_uuid',
        'purchased_at',
        'purchase_price',
        'currency',
        'meta',
        'slug',
    ];

    /**
     * Set attributes and defaults.
     *
     * @var array
     */
    protected $attributes = [
        'status' => 'available',
    ];

    /**
     * Dynamic attributes that are appended to object.
     *
     * @var array
     */
    protected $appends = [
        'warranty_name',
        'photo_url',
        'equipped_to_name',
        'is_equipped',
        'age_in_days',
        'depreciated_value',
    ];

    /**
     * The attributes excluded from the model's JSON form.
     *
     * @var array
     */
    protected $hidden = ['warranty', 'photo', 'equipable'];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'purchased_at'   => 'date',
        // Money values
        'purchase_price' => Money::class,
        'meta'           => Json::class,
    ];

    /**
     * Properties which activity needs to be logged.
     *
     * @var array
     */
    protected static $logAttributes = '*';

    /**
     * Do not log empty changed.
     *
     * @var bool
     */
    protected static $submitEmptyLogs = false;

    /**
     * The name of the subject to log.
     *
     * @var string
     */
    protected static $logName = 'equipment';

    /**
     * Enforce assignment morph aliases for existing short type values.
     */
    public static function boot(): void
    {
        parent::boot();

        Relation::morphMap([
            'fleet-ops:vehicle' => Vehicle::class,
            'fleet-ops:driver'  => Driver::class,
        ]);
    }

    /**
     * Get the options for generating the slug.
     */
    public function getSlugOptions(): SlugOptions
    {
        return SlugOptions::create()
            ->generateSlugsFrom(['name', 'code'])
            ->saveSlugsTo('slug')
            ->doNotGenerateSlugsOnUpdate();
    }

    /**
     * Get the activity log options for the model.
     */
    public function getActivitylogOptions(): LogOptions
    {
        return LogOptions::defaults()->logAll();
    }

    public function warranty(): BelongsTo
    {
        return $this->belongsTo(Warranty::class, 'warranty_uuid', 'uuid');
    }

    public function photo(): BelongsTo
    {
        return $this->belongsTo(File::class, 'photo_uuid', 'uuid');
    }

    public function createdBy(): BelongsTo
    {
        return $this->belongsTo(User::class, 'created_by_uuid', 'uuid');
    }

    public function updatedBy(): BelongsTo
    {
        return $this->belongsTo(User::class, 'updated_by_uuid', 'uuid');
    }

    public function equipable(): MorphTo
    {
        return $this->morphTo(__FUNCTION__, 'equipable_type', 'equipable_uuid');
    }

    /**
     * Set the equipment status attribute.
     */
    public function setStatusAttribute(?string $status = 'available'): void
    {
        $this->attributes['status'] = $status === null || $status === 'active' ? 'available' : $status;
    }

    /**
     * Normalize equipment assignment type aliases to Eloquent morph classes.
     */
    public function setEquipableTypeAttribute(?string $type): void
    {
        if ($type === null || $type === '') {
            $this->attributes['equipable_type'] = null;

            return;
        }

        if (str_contains($type, '\\')) {
            $this->attributes['equipable_type'] = $type;

            return;
        }

        $this->attributes['equipable_type'] = match ($type) {
            'fleet-ops:vehicle', 'vehicle' => Utils::getMutationType('fleet-ops:vehicle'),
            'fleet-ops:driver', 'driver'   => Utils::getMutationType('fleet-ops:driver'),
            default                       => $type,
        };
    }

    public function maintenances(): HasMany
    {
        return $this->hasMany(Maintenance::class, 'maintainable_uuid', 'uuid')
            ->where('maintainable_type', static::class);
    }

    /**
     * Get the warranty name.
     */
    public function getWarrantyNameAttribute(): ?string
    {
        return $this->warranty?->name;
    }

    /**
     * Get the photo URL.
     */
    public function getPhotoUrlAttribute(): ?string
    {
        return $this->photo?->url;
    }

    /**
     * Get the name of what the equipment is equipped to.
     */
    public function getEquippedToNameAttribute(): ?string
    {
        if ($this->equipable) {
            return $this->equipable->name ?? $this->equipable->display_name ?? null;
        }

        return null;
    }

    /**
     * Check if the equipment is currently equipped to something.
     */
    public function getIsEquippedAttribute(): bool
    {
        return !is_null($this->equipable_uuid) && !is_null($this->equipable_type);
    }

    /**
     * Get the age of the equipment in days.
     */
    public function getAgeInDaysAttribute(): ?int
    {
        if ($this->purchased_at) {
            return $this->purchased_at->diffInDays(now());
        }

        return null;
    }

    /**
     * Get the depreciated value of the equipment.
     */
    public function getDepreciatedValueAttribute(): ?float
    {
        if (!$this->purchase_price || !$this->purchased_at) {
            return null;
        }

        $meta             = $this->meta ?? [];
        $depreciationRate = $meta['depreciation_rate'] ?? 0.1; // 10% per year default
        $yearsOld         = $this->purchased_at->diffInYears(now());

        $depreciatedAmount = $this->purchase_price * ($depreciationRate * $yearsOld);
        $currentValue      = $this->purchase_price - $depreciatedAmount;

        return max($currentValue, 0); // Don't go below 0
    }

    /**
     * Scope to get equipment by type.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeByType($query, string $type)
    {
        return $query->where('type', $type);
    }

    /**
     * Scope to get active equipment.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeActive($query)
    {
        return $query->where('status', 'active');
    }

    /**
     * Scope to get equipped equipment.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeEquipped($query)
    {
        return $query->whereNotNull('equipable_uuid')
                    ->whereNotNull('equipable_type');
    }

    /**
     * Scope to get unequipped equipment.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeUnequipped($query)
    {
        return $query->whereNull('equipable_uuid')
                    ->orWhereNull('equipable_type');
    }

    /**
     * Scope to get equipment by manufacturer.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeByManufacturer($query, string $manufacturer)
    {
        return $query->where('manufacturer', $manufacturer);
    }

    /**
     * Equip this equipment to an entity.
     */
    public function equipTo(Model $equipable): bool
    {
        $updated = $this->update([
            'equipable_type' => get_class($equipable),
            'equipable_uuid' => $equipable->uuid,
        ]);

        if ($updated) {
            activity('equipment_equipped')
                ->performedOn($this)
                ->withProperties([
                    'equipped_to_type' => get_class($equipable),
                    'equipped_to_uuid' => $equipable->uuid,
                    'equipped_to_name' => $equipable->name ?? $equipable->display_name ?? null,
                ])
                ->log('Equipment equipped');
        }

        return $updated;
    }

    /**
     * Unequip this equipment.
     */
    public function unequip(): bool
    {
        $oldEquipableType = $this->equipable_type;
        $oldEquipableUuid = $this->equipable_uuid;

        $updated = $this->update([
            'equipable_type' => null,
            'equipable_uuid' => null,
        ]);

        if ($updated) {
            activity('equipment_unequipped')
                ->performedOn($this)
                ->withProperties([
                    'previous_equipped_to_type' => $oldEquipableType,
                    'previous_equipped_to_uuid' => $oldEquipableUuid,
                ])
                ->log('Equipment unequipped');
        }

        return $updated;
    }

    /**
     * Check if the equipment needs maintenance.
     */
    public function needsMaintenance(): bool
    {
        // Check if there's overdue maintenance
        $overdueMaintenance = $this->maintenances()
            ->where('status', 'scheduled')
            ->where('scheduled_at', '<', now())
            ->exists();

        if ($overdueMaintenance) {
            return true;
        }

        // Check maintenance intervals based on age or usage
        $meta                    = $this->meta ?? [];
        $maintenanceIntervalDays = $meta['maintenance_interval_days'] ?? null;

        if ($maintenanceIntervalDays && $this->purchased_at) {
            $lastMaintenance = $this->maintenances()
                ->where('status', 'completed')
                ->orderBy('completed_at', 'desc')
                ->first();

            $lastMaintenanceDate      = $lastMaintenance?->completed_at ?? $this->purchased_at;
            $daysSinceLastMaintenance = $lastMaintenanceDate->diffInDays(now());

            return $daysSinceLastMaintenance >= $maintenanceIntervalDays;
        }

        return false;
    }

    /**
     * Schedule maintenance for the equipment.
     */
    public function scheduleMaintenance(string $type, \DateTime $scheduledAt, array $details = []): Maintenance
    {
        return Maintenance::create([
            'company_uuid'      => $this->company_uuid,
            'maintainable_type' => static::class,
            'maintainable_uuid' => $this->uuid,
            'type'              => $type,
            'status'            => 'scheduled',
            'scheduled_at'      => $scheduledAt,
            'summary'           => $details['summary'] ?? null,
            'notes'             => $details['notes'] ?? null,
            'created_by_uuid'   => auth()->id(),
        ]);
    }

    /**
     * Get the utilization rate of the equipment.
     */
    public function getUtilizationRate(int $days = 30): float
    {
        // This would calculate based on actual usage data
        // For now, return a placeholder calculation based on equipped status
        if ($this->is_equipped) {
            return 75.0; // Assume 75% utilization when equipped
        }

        return 0.0;
    }

    /**
     * Get the maintenance cost for a period.
     */
    public function getMaintenanceCost(int $days = 365): float
    {
        $startDate = now()->subDays($days);

        return $this->maintenances()
            ->where('status', 'completed')
            ->where('completed_at', '>=', $startDate)
            ->sum('total_cost') ?? 0;
    }

    /**
     * Check if the equipment is under warranty.
     */
    public function isUnderWarranty(): bool
    {
        return $this->warranty && $this->warranty->is_active;
    }

    /**
     * Get the replacement cost estimate.
     */
    public function getReplacementCostEstimate(): ?float
    {
        $meta = $this->meta ?? [];

        if (isset($meta['replacement_cost'])) {
            return $meta['replacement_cost'];
        }

        // Estimate based on purchase price with inflation
        if ($this->purchase_price && $this->purchased_at) {
            $yearsOld      = $this->purchased_at->diffInYears(now());
            $inflationRate = $meta['inflation_rate'] ?? 0.03; // 3% default

            return $this->purchase_price * pow(1 + $inflationRate, $yearsOld);
        }

        return null;
    }

    /**
     * Create an Equipment instance from an import row.
     *
     * Monetary values (purchase_price) must be supplied in cents (integers).
     * The Money cast will strip non-numeric characters before storage.
     *
     * @param array $row          Associative array from the import spreadsheet
     * @param bool  $saveInstance Whether to persist the record immediately
     */
    public static function createFromImport(array $row, bool $saveInstance = false): Equipment
    {
        $row = array_filter($row);

        $name          = Utils::or($row, ['name']);
        $code          = Utils::or($row, ['code', 'internal_id']);
        $type          = Utils::or($row, ['type'], 'equipment');
        $status        = Utils::or($row, ['status'], 'operational');
        $serialNumber  = Utils::or($row, ['serial_number', 'serial']);
        $manufacturer  = Utils::or($row, ['manufacturer', 'make', 'brand']);
        $model         = Utils::or($row, ['model', 'equipment_model']);
        $purchasePrice = Utils::or($row, ['purchase_price', 'price', 'cost']);
        $currency      = Utils::or($row, ['currency'], 'USD');
        $purchasedAt   = Utils::or($row, ['purchased_at', 'purchase_date']);
        $warrantyName  = Utils::or($row, ['warranty_name', 'warranty']);

        $equipment = new static([
            'company_uuid'   => session('company'),
            'name'           => $name,
            'code'           => $code,
            'type'           => $type,
            'status'         => $status,
            'serial_number'  => $serialNumber,
            'manufacturer'   => $manufacturer,
            'model'          => $model,
            'purchase_price' => $purchasePrice,
            'currency'       => strtoupper($currency ?? 'USD'),
            'purchased_at'   => $purchasedAt ? \Carbon\Carbon::parse($purchasedAt) : null,
        ]);

        // Attempt to resolve warranty by name
        if ($warrantyName) {
            $warranty = Warranty::where('company_uuid', session('company'))
                ->where('name', 'like', '%' . $warrantyName . '%')
                ->first();
            if ($warranty) {
                $equipment->warranty_uuid = $warranty->uuid;
            }
        }

        if ($saveInstance === true) {
            $equipment->save();
        }

        return $equipment;
    }
}
