<?php

namespace App\Models;

use App\Domains\Contact\Dav\VCardResource;
use App\Helpers\AvatarHelper;
use App\Helpers\ContactImportantDateHelper;
use App\Helpers\ImportantDateHelper;
use App\Helpers\NameHelper;
use App\Helpers\ScoutHelper;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Laravel\Scout\Attributes\SearchUsingFullText;
use Laravel\Scout\Attributes\SearchUsingPrefix;
use Laravel\Scout\Searchable;

class Contact extends VCardResource
{
    use HasFactory, HasUuids;
    use Searchable;
    use SoftDeletes;

    /**
     * Possible avatar types.
     */
    public const AVATAR_TYPE_SVG = 'svg';

    public const AVATAR_TYPE_URL = 'url';

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int,string>
     */
    protected $fillable = [
        'vault_id',
        'gender_id',
        'pronoun_id',
        'first_name',
        'last_name',
        'middle_name',
        'nickname',
        'maiden_name',
        'can_be_deleted',
        'show_quick_facts',
        'template_id',
        'last_updated_at',
        'company_id',
        'job_position',
        'listed',
        'file_id',
        'religion_id',
        'vcard',
        'distant_uuid',
        'distant_etag',
        'distant_uri',
        'prefix',
        'suffix',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'vault_id' => 'string',
        'can_be_deleted' => 'boolean',
        'listed' => 'boolean',
        'show_quick_facts' => 'boolean',
        'last_updated_at' => 'datetime',
    ];

    /**
     * Get the indexable data array for the model.
     *
     *
     * @codeCoverageIgnore
     */
    #[SearchUsingPrefix(['id', 'vault_id'])]
    #[SearchUsingFullText(['first_name', 'last_name', 'middle_name', 'nickname', 'maiden_name'])]
    public function toSearchableArray(): array
    {
        return [
            'id' => $this->id,
            'vault_id' => $this->vault_id,
            'first_name' => $this->first_name,
            'last_name' => $this->last_name,
            'middle_name' => $this->middle_name,
            'nickname' => $this->nickname,
            'maiden_name' => $this->maiden_name,
        ];
    }

    /**
     * Determine if the model should be searchable.
     *
     * @return bool
     */
    public function shouldBeSearchable()
    {
        return $this->listed;
    }

    /**
     * Scope a query to only include contacts who are active.
     */
    public function scopeActive(Builder $query): Builder
    {
        return $query->where('listed', 1);
    }

    /**
     * Used to delete related objects from Meilisearch/Algolia instance.
     */
    protected static function boot(): void
    {
        parent::boot();

        static::deleting(function (self $model) {
            $model->notes()->unsearchable();
        });
    }

    /**
     * When updating a model, this method determines if we should update the search index.
     *
     * @return bool
     */
    public function searchIndexShouldBeUpdated()
    {
        return ScoutHelper::activated();
    }

    /**
     * Get the vault associated with the contact.
     */
    public function vault(): BelongsTo
    {
        return $this->belongsTo(Vault::class);
    }

    /**
     * Get the gender associated with the contact.
     */
    public function gender(): BelongsTo
    {
        return $this->belongsTo(Gender::class);
    }

    /**
     * Get the pronoun associated with the contact.
     */
    public function pronoun(): BelongsTo
    {
        return $this->belongsTo(Pronoun::class);
    }

    /**
     * Get the template associated with the contact.
     */
    public function template(): BelongsTo
    {
        return $this->belongsTo(Template::class);
    }

    /**
     * Get the relationships associated with the contact.
     */
    public function relationships(): BelongsToMany
    {
        return $this->belongsToMany(Contact::class, 'relationships', 'contact_id', 'related_contact_id');
    }

    /**
     * Get the labels associated with the contact.
     */
    public function labels(): BelongsToMany
    {
        return $this->belongsToMany(Label::class);
    }

    /**
     * Get the contact information records associated with the contact.
     */
    public function contactInformations(): HasMany
    {
        return $this->hasMany(ContactInformation::class);
    }

    /**
     * Get the note records associated with the contact.
     */
    public function notes(): HasMany
    {
        return $this->hasMany(Note::class);
    }

    /**
     * Get the date records associated with the contact.
     */
    public function importantDates(): HasMany
    {
        return $this->hasMany(ContactImportantDate::class);
    }

    /**
     * Get the contact reminders records associated with the contact.
     */
    public function reminders(): HasMany
    {
        return $this->hasMany(ContactReminder::class);
    }

    /**
     * Get the loans associated with the contact as the person who takes the
     * loan.
     * I know loaner is not a real word, but it's the best I could come up with.
     *
     * @return BelongsToMany
     */
    public function loansAsLoaner()
    {
        return $this->belongsToMany(Loan::class, 'contact_loan', 'loaner_id');
    }

    /**
     * Get the loans associated with the contact as the person who takes the
     * loan.
     * I know loanee is not a real word, but it's the best I could come up with.
     *
     * @return BelongsToMany
     */
    public function loansAsLoanee()
    {
        return $this->belongsToMany(Loan::class, 'contact_loan', 'loanee_id');
    }

    /**
     * Get the company associated with the contact.
     */
    public function company(): BelongsTo
    {
        return $this->belongsTo(Company::class);
    }

    /**
     * Get the tasks associated with the contact.
     */
    public function tasks(): HasMany
    {
        return $this->hasMany(ContactTask::class);
    }

    /**
     * Get the calls associated with the contact.
     */
    public function calls(): HasMany
    {
        return $this->hasMany(Call::class);
    }

    /**
     * Get the pets associated with the contact.
     */
    public function pets(): HasMany
    {
        return $this->hasMany(Pet::class);
    }

    /**
     * Get the goals associated with the contact.
     */
    public function goals(): HasMany
    {
        return $this->hasMany(Goal::class);
    }

    /**
     * Get the files associated with the contact.
     */
    public function files(): MorphMany
    {
        return $this->morphMany(File::class, 'ufileable', 'fileable_type');
    }

    /**
     * Get the file associated with the contact.
     * If it exists, it's the avatar.
     */
    public function file(): BelongsTo
    {
        return $this->belongsTo(File::class);
    }

    /**
     * Get the groups associated with the contact.
     */
    public function groups(): BelongsToMany
    {
        return $this->belongsToMany(Group::class);
    }

    /**
     * Get the posts associated with the contact.
     */
    public function posts(): BelongsToMany
    {
        return $this->belongsToMany(Post::class);
    }

    /**
     * Get the religion associated with the contact.
     */
    public function religion(): BelongsTo
    {
        return $this->belongsTo(Religion::class);
    }

    /**
     * Get the life events associated with the contact.
     */
    public function lifeEvents(): BelongsToMany
    {
        return $this->belongsToMany(LifeEvent::class, 'life_event_participants', 'contact_id', 'life_event_id');
    }

    /**
     * Get the timeline events associated with the contact.
     */
    public function timelineEvents(): BelongsToMany
    {
        return $this->belongsToMany(TimelineEvent::class, 'timeline_event_participants', 'contact_id', 'timeline_event_id');
    }

    /**
     * Get the mood tracking events associated with the contact.
     */
    public function moodTrackingEvents(): HasMany
    {
        return $this->hasMany(MoodTrackingEvent::class);
    }

    /**
     * Get the addresses associated with the contact.
     */
    public function addresses(): BelongsToMany
    {
        return $this->belongsToMany(Address::class, 'contact_address')
            ->withPivot('is_past_address')
            ->withTimestamps();
    }

    /**
     * Get the quick facts associated with the contact.
     */
    public function quickFacts(): HasMany
    {
        return $this->hasMany(QuickFact::class);
    }

    /**
     * Get the life metrics associated with the contact.
     */
    public function lifeMetrics(): BelongsToMany
    {
        return $this->belongsToMany(LifeMetric::class, 'contact_life_metric', 'contact_id', 'life_metric_id')->withTimestamps();
    }

    /**
     * Get the name of the contact, according to the user preference.
     *
     * @return Attribute<string,never>
     */
    protected function name(): Attribute
    {
        return Attribute::make(
            get: function ($value, $attributes) {
                if (Auth::check()) {
                    return NameHelper::formatContactName(Auth::user(), $this);
                }

                $firstName = Arr::get($attributes, 'first_name');
                $lastName = Arr::get($attributes, 'last_name');
                $separator = $firstName && $lastName ? ' ' : '';

                return $firstName.$separator.$lastName;
            }
        );
    }

    /**
     * Get the age of the contact.
     * The birthdate is stored in a ContactImportantDate object, of the
     * TYPE_BIRTHDATE type. So we need to find if a date of this type exists.
     *
     * @return Attribute<?int,never>
     */
    protected function age(): Attribute
    {
        return Attribute::make(
            get: function ($value) {
                $type = ContactImportantDateHelper::getImportantDateType(ContactImportantDate::TYPE_BIRTHDATE, $this->vault_id);

                if (! $type) {
                    return null;
                }

                $birthdate = $this->importantDates
                    ->firstWhere('contact_important_date_type_id', $type->id);

                if (! $birthdate) {
                    return null;
                }

                return ImportantDateHelper::getAge($birthdate);
            }
        );
    }

    /**
     * Get the avatar of the contact.
     *
     * @return Attribute<array,never>
     */
    protected function avatar(): Attribute
    {
        return Attribute::make(
            get: function ($value) {
                $type = self::AVATAR_TYPE_SVG;
                $content = AvatarHelper::generateRandomAvatar($this);

                if ($this->file) {
                    $type = self::AVATAR_TYPE_URL;
                    $content = 'https://ucarecdn.com/'.$this->file->uuid.'/-/scale_crop/300x300/smart/-/format/auto/-/quality/smart_retina/';
                }

                return [
                    'type' => $type,
                    'content' => $content,
                ];
            }
        );
    }
}
