<?php

namespace Fleetbase\FleetOps\Models;

use Fleetbase\Casts\Json;
use Fleetbase\FleetOps\Casts\Point;
use Fleetbase\LaravelMysqlSpatial\Eloquent\SpatialTrait;
use Fleetbase\Models\Alert;
use Fleetbase\Models\Company;
use Fleetbase\Models\Model;
use Fleetbase\Models\User;
use Fleetbase\Traits\HasApiModelBehavior;
use Fleetbase\Traits\HasApiModelCache;
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 Spatie\Activitylog\LogOptions;
use Spatie\Activitylog\Traits\LogsActivity;

/**
 * Class DeviceEvent.
 *
 * Represents events generated by devices in the fleet management system.
 * Events can include status changes, alerts, data transmissions, and other
 * device-related activities.
 */
class DeviceEvent extends Model
{
    use HasUuid;
    use HasPublicId;
    use TracksApiCredential;
    use HasApiModelBehavior;
    use HasApiModelCache;
    use LogsActivity;
    use HasMetaAttributes;
    use Searchable;
    use SpatialTrait;

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

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

    /**
     * The attributes that can be queried.
     *
     * @var array
     */
    protected $searchableColumns = ['event_type', 'message', 'ident', 'code', 'provider', 'public_id'];

    /**
     * The attributes that can be used for filtering.
     *
     * @var array
     */
    protected $filterParams = ['event_type', 'severity', 'device_uuid', 'provider', 'code', 'processed', 'occurred_at', 'created_at', 'updated_at', 'telematic'];

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'company_uuid',
        'device_uuid',
        'payload',
        'data',
        'meta',
        'location',
        'event_type',
        'severity',
        'message',
        'ident',
        'protocol',
        'provider',
        'mileage',
        'state',
        'code',
        'reason',
        'comment',
        'resolved_at',
        'occurred_at',
        'processed_at',
        'slug',
    ];

    /**
     * Dynamic attributes that are appended to object.
     *
     * @var array
     */
    protected $appends = [
        'device_name',
        'device_id',
        'device_imei',
        'device_serial_number',
        'device_connection_status',
        'device_status',
        'device_photo_url',
        'telematic_uuid',
        'telematic_name',
        'provider_descriptor',
        'is_processed',
        'age_minutes',
        'processing_delay_minutes',
    ];

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

    /**
     * The attributes that are spatial columns.
     *
     * @var array
     */
    protected $spatialFields = ['location'];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'payload'         => Json::class,
        'data'            => Json::class,
        'meta'            => Json::class,
        'location'        => Point::class,
        'resolved_at'     => 'datetime',
        'occurred_at'     => 'datetime',
        'processed_at'    => 'datetime',
    ];

    /**
     * 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 = 'device_event';

    /**
     * Get the activity log options for the model.
     */
    public function getActivitylogOptions(): LogOptions
    {
        return LogOptions::defaults()
            ->logOnly([
                'company_uuid',
                'device_uuid',
                'event_type',
                'severity',
                'message',
                'ident',
                'protocol',
                'provider',
                'mileage',
                'state',
                'code',
                'reason',
                'comment',
                'resolved_at',
                'occurred_at',
                'processed_at',
            ])
            ->logOnlyDirty();
    }

    public function company(): BelongsTo
    {
        return $this->belongsTo(Company::class, 'company_uuid', 'uuid');
    }

    public function device(): BelongsTo
    {
        return $this->belongsTo(Device::class, 'device_uuid', 'uuid');
    }

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

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

    /**
     * Get the provider device identifier.
     */
    public function getDeviceIdAttribute(): ?string
    {
        return $this->device?->device_id ?? $this->ident;
    }

    /**
     * Get the device IMEI.
     */
    public function getDeviceImeiAttribute(): ?string
    {
        return $this->device?->imei;
    }

    /**
     * Get the device serial number.
     */
    public function getDeviceSerialNumberAttribute(): ?string
    {
        return $this->device?->serial_number;
    }

    /**
     * Get the device connection status.
     */
    public function getDeviceConnectionStatusAttribute(): ?string
    {
        return $this->device?->connection_status;
    }

    /**
     * Get the device operational status.
     */
    public function getDeviceStatusAttribute(): ?string
    {
        return $this->device?->status;
    }

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

    /**
     * Get the related telematic UUID.
     */
    public function getTelematicUuidAttribute(): ?string
    {
        return $this->device?->telematic_uuid;
    }

    /**
     * Get the related telematic name.
     */
    public function getTelematicNameAttribute(): ?string
    {
        return $this->device?->telematic?->name;
    }

    /**
     * Get the related telematic provider descriptor.
     */
    public function getProviderDescriptorAttribute(): array
    {
        return $this->device?->telematic?->provider_descriptor ?? [];
    }

    /**
     * Check if the event has been processed.
     */
    public function getIsProcessedAttribute(): bool
    {
        return !is_null($this->processed_at);
    }

    /**
     * Get the age of the event in minutes.
     */
    public function getAgeMinutesAttribute(): int
    {
        $startTime = $this->occurred_at ?? $this->created_at;

        return $startTime->diffInMinutes(now());
    }

    /**
     * Get the processing delay in minutes.
     */
    public function getProcessingDelayMinutesAttribute(): ?int
    {
        if (!$this->processed_at) {
            return null;
        }

        $startTime = $this->occurred_at ?? $this->created_at;

        return $startTime->diffInMinutes($this->processed_at);
    }

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

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

    /**
     * Scope to get processed events.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeProcessed($query)
    {
        return $query->whereNotNull('processed_at');
    }

    /**
     * Scope to get unprocessed events.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeUnprocessed($query)
    {
        return $query->whereNull('processed_at');
    }

    /**
     * Scope to get recent events.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeRecent($query, int $minutes = 60)
    {
        return $query->where('occurred_at', '>=', now()->subMinutes($minutes));
    }

    /**
     * Scope to get critical events.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeCritical($query)
    {
        return $query->where('severity', 'critical');
    }

    /**
     * Mark the event as processed.
     */
    public function markAsProcessed(): bool
    {
        if ($this->is_processed) {
            return false;
        }

        $updated = $this->update(['processed_at' => now()]);

        if ($updated) {
            activity('device_event_processed')
                ->performedOn($this)
                ->withProperties([
                    'event_type'               => $this->event_type,
                    'processing_delay_minutes' => $this->processing_delay_minutes,
                ])
                ->log('Device event processed');
        }

        return $updated;
    }

    /**
     * Get a specific data field from the event.
     */
    public function getData(string $field, $default = null)
    {
        $data = $this->data ?? [];

        // Support dot notation for nested data
        $keys  = explode('.', $field);
        $value = $data;

        foreach ($keys as $key) {
            if (is_array($value) && isset($value[$key])) {
                $value = $value[$key];
            } else {
                return $default;
            }
        }

        return $value;
    }

    /**
     * Set a specific data field in the event.
     */
    public function setData(string $field, $value): bool
    {
        $data = $this->data ?? [];

        // Support dot notation for nested data
        $keys    = explode('.', $field);
        $current = &$data;

        foreach ($keys as $i => $key) {
            if ($i === count($keys) - 1) {
                $current[$key] = $value;
            } else {
                if (!isset($current[$key]) || !is_array($current[$key])) {
                    $current[$key] = [];
                }
                $current = &$current[$key];
            }
        }

        return $this->update(['data' => $data]);
    }

    /**
     * Check if the event should trigger an alert.
     */
    public function shouldTriggerAlert(): bool
    {
        // Define event types that should trigger alerts
        $alertTriggerTypes = [
            'error',
            'warning',
            'critical_failure',
            'security_breach',
            'maintenance_required',
            'threshold_exceeded',
        ];

        if (in_array($this->event_type, $alertTriggerTypes)) {
            return true;
        }

        // Check severity
        if (in_array($this->severity, ['high', 'critical'])) {
            return true;
        }

        return false;
    }

    /**
     * Create an alert based on this event.
     */
    public function createAlert(): ?Alert
    {
        if (!$this->shouldTriggerAlert()) {
            return null;
        }

        // Check if an alert already exists for this event
        $existingAlert = Alert::where('subject_type', static::class)
            ->where('subject_uuid', $this->uuid)
            ->where('status', 'open')
            ->first();

        if ($existingAlert) {
            return $existingAlert;
        }

        $alert = Alert::create([
            'company_uuid' => $this->company_uuid,
            'type'         => 'device_event',
            'severity'     => $this->severity,
            'status'       => 'open',
            'subject_type' => static::class,
            'subject_uuid' => $this->uuid,
            'message'      => $this->generateAlertMessage(),
            'context'      => [
                'device_uuid' => $this->device_uuid,
                'device_name' => $this->device_name,
                'event_type'  => $this->event_type,
                'event_data'  => $this->data,
                'occurred_at' => $this->occurred_at,
            ],
            'triggered_at' => $this->occurred_at ?? now(),
        ]);

        return $alert;
    }

    /**
     * Generate an alert message for this event.
     */
    protected function generateAlertMessage(): string
    {
        $deviceName = $this->device_name ?? 'Unknown Device';

        switch ($this->event_type) {
            case 'error':
                return "Device '{$deviceName}' reported an error: {$this->message}";
            case 'warning':
                return "Device '{$deviceName}' issued a warning: {$this->message}";
            case 'critical_failure':
                return "Critical failure detected on device '{$deviceName}': {$this->message}";
            case 'security_breach':
                return "Security breach detected on device '{$deviceName}': {$this->message}";
            case 'maintenance_required':
                return "Device '{$deviceName}' requires maintenance: {$this->message}";
            case 'threshold_exceeded':
                return "Threshold exceeded on device '{$deviceName}': {$this->message}";
            default:
                return "Device '{$deviceName}' event ({$this->event_type}): {$this->message}";
        }
    }

    /**
     * Get the severity level as a numeric value for sorting.
     */
    public function getSeverityLevel(): int
    {
        switch ($this->severity) {
            case 'critical':
                return 4;
            case 'high':
                return 3;
            case 'medium':
                return 2;
            case 'low':
                return 1;
            default:
                return 0;
        }
    }

    /**
     * Get events that occurred around the same time as this event.
     *
     * @return \Illuminate\Database\Eloquent\Collection
     */
    public function getCorrelatedEvents(int $minuteWindow = 5)
    {
        $startTime = ($this->occurred_at ?? $this->created_at)->subMinutes($minuteWindow);
        $endTime   = ($this->occurred_at ?? $this->created_at)->addMinutes($minuteWindow);

        return static::where('device_uuid', $this->device_uuid)
            ->where('uuid', '!=', $this->uuid)
            ->whereBetween('occurred_at', [$startTime, $endTime])
            ->orderBy('occurred_at')
            ->get();
    }

    /**
     * Check if this event is part of a pattern.
     */
    public function isPartOfPattern(int $lookbackHours = 24, int $minimumOccurrences = 3): bool
    {
        $startTime = now()->subHours($lookbackHours);

        $similarEvents = static::where('device_uuid', $this->device_uuid)
            ->where('event_type', $this->event_type)
            ->where('occurred_at', '>=', $startTime)
            ->count();

        return $similarEvents >= $minimumOccurrences;
    }

    /**
     * Export the event data for analysis.
     */
    public function exportForAnalysis(): array
    {
        return [
            'event_id'                 => $this->public_id,
            'device_uuid'              => $this->device_uuid,
            'device_name'              => $this->device_name,
            'event_type'               => $this->event_type,
            'severity'                 => $this->severity,
            'message'                  => $this->message,
            'occurred_at'              => $this->occurred_at?->toISOString(),
            'processed_at'             => $this->processed_at?->toISOString(),
            'processing_delay_minutes' => $this->processing_delay_minutes,
            'data'                     => $this->data,
            'created_at'               => $this->created_at?->toISOString(),
        ];
    }

    /**
     * Create a Position for this event’s device attachable using position data.
     *
     * This delegates to the shared DeviceEventHelper utility.
     */
    public function createPosition(array $positionData = []): ?Position
    {
        if (empty($positionData)) {
            return null;
        }

        // Ensure device and attachable are loaded
        $this->loadMissing('device.attachable');
        $attachable = $this->device?->attachable;

        if ($attachable && method_exists($attachable, 'createPosition')) {
            return $attachable->createPosition($positionData);
        }

        return null;
    }
}
