<?php

namespace App\Models;

use App\Models\Concerns\HasCompressedImages;
use App\Services\PetStatisticsService;
use Database\Factories\PetFactory;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
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\Carbon;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

/**
 * @property int $id
 * @property int $species_id
 * @property int|null $breed_id
 * @property string $name
 * @property string $slug
 * @property Carbon|null $date_of_birth
 * @property string|null $gender
 * @property string|null $size
 * @property string|null $color
 * @property string|null $description
 * @property string|null $medical_notes
 * @property bool $vaccination_status
 * @property bool $special_needs
 * @property bool $is_spayed_neutered
 * @property Carbon|null $intake_date
 * @property string $status
 * @property bool $is_visible
 * @property Carbon|null $adoption_date
 * @property Carbon|null $created_at
 * @property Carbon|null $updated_at
 * @property-read Collection<int, AdoptionApplication> $adoptionApplications
 * @property-read int|null $adoption_applications_count
 * @property-read Breed|null $breed
 * @property-read Collection<int, PetPhoto> $photos
 * @property-read int|null $photos_count
 * @property-read Species $species
 *
 * @method static \Database\Factories\PetFactory factory($count = null, $state = [])
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Pet newModelQuery()
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Pet newQuery()
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Pet query()
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Pet whereBreedId($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Pet whereColor($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Pet whereCreatedAt($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Pet whereDescription($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Pet whereGender($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Pet whereId($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Pet whereIntakeDate($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Pet whereMedicalNotes($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Pet whereName($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Pet whereSize($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Pet whereSlug($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Pet whereSpeciesId($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Pet whereStatus($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Pet whereUpdatedAt($value)
 *
 * @property array<array-key, mixed>|null $images
 * @property-read string|null $age_in_years
 * @property-read array{total_days: int, human_readable: string}|null $days_in_foster_care
 *
 * @method static Builder<static>|Pet publiclyVisible()
 * @method static Builder<static>|Pet visible()
 * @method static Builder<static>|Pet whereAdoptionDate($value)
 * @method static Builder<static>|Pet whereDateOfBirth($value)
 * @method static Builder<static>|Pet whereImages($value)
 * @method static Builder<static>|Pet whereIsSpayedNeutered($value)
 * @method static Builder<static>|Pet whereIsVisible($value)
 * @method static Builder<static>|Pet whereSpecialNeeds($value)
 * @method static Builder<static>|Pet whereVaccinationStatus($value)
 *
 * @mixin \Eloquent
 */
class Pet extends Model
{
    /** @use HasFactory<PetFactory> */
    use HasCompressedImages, HasFactory;

    protected static function booted(): void
    {
        static::created(function (Pet $pet) {
            $pet->moveImagesFromTemp();
        });

        static::saved(function () {
            app(PetStatisticsService::class)->invalidateCache();
        });

        static::deleted(function (Pet $pet) {
            $pet->deleteImageDirectory();
            app(PetStatisticsService::class)->invalidateCache();
        });
    }

    /**
     * Move images from the temp directory to the pet's directory after creation.
     */
    protected function moveImagesFromTemp(): void
    {
        $images = $this->images;

        if (empty($images)) {
            return;
        }

        $newImages = [];
        $targetDirectory = "pets/{$this->id}";

        foreach ($images as $imagePath) {
            if (! $imagePath || ! str_starts_with($imagePath, 'pets/temp/')) {
                $newImages[] = $imagePath;

                continue;
            }

            $filename = basename($imagePath);
            $newPath = "{$targetDirectory}/{$filename}";

            if (Storage::disk('public')->exists($imagePath)) {
                Storage::disk('public')->makeDirectory($targetDirectory);
                Storage::disk('public')->move($imagePath, $newPath);
                $newImages[] = $newPath;
            } else {
                $newImages[] = $imagePath;
            }
        }

        if ($newImages !== $images) {
            $this->updateQuietly(['images' => $newImages]);
        }
    }

    /**
     * Delete the pet's image directory and all its contents.
     */
    protected function deleteImageDirectory(): void
    {
        $directory = "pets/{$this->id}";

        if (Storage::disk('public')->exists($directory)) {
            Storage::disk('public')->deleteDirectory($directory);
        }
    }

    /**
     * @return array<string, array{type: string, width?: int, height?: int, quality?: int, mode?: string}>
     */
    protected function getImageAttributes(): array
    {
        return [
            'images' => [
                'type' => 'array',
                'width' => 1200,
                'height' => 1200,
                'quality' => 75,
                'mode' => 'fit',
            ],
        ];
    }

    protected $fillable = [
        'species_id',
        'breed_id',
        'name',
        'slug',
        'date_of_birth',
        'gender',
        'size',
        'color',
        'description',
        'medical_notes',
        'vaccination_status',
        'special_needs',
        'is_spayed_neutered',
        'intake_date',
        'status',
        'is_visible',
        'adoption_date',
        'images',
    ];

    protected function casts(): array
    {
        return [
            'date_of_birth' => 'date',
            'intake_date' => 'date',
            'adoption_date' => 'datetime',
            'vaccination_status' => 'boolean',
            'special_needs' => 'boolean',
            'is_spayed_neutered' => 'boolean',
            'is_visible' => 'boolean',
            'images' => 'array',
        ];
    }

    /**
     * @param  Builder<static>  $query
     * @return Builder<static>
     */
    public function scopeVisible(Builder $query): Builder
    {
        return $query->where('is_visible', true);
    }

    /**
     * Scope a query to pets whose public page is reachable: either visible,
     * or adopted (adopted pets remain reachable even once hidden).
     *
     * @param  Builder<static>  $query
     * @return Builder<static>
     */
    public function scopePubliclyVisible(Builder $query): Builder
    {
        return $query->where(function (Builder $query): void {
            $query->where('is_visible', true)
                ->orWhere('status', 'adopted');
        });
    }

    /**
     * Determine whether the pet's public page is reachable, matching the
     * visibility rules enforced by the scopePubliclyVisible query scope.
     */
    public function isPubliclyVisible(): bool
    {
        return $this->is_visible || $this->status === 'adopted';
    }

    /**
     * Generate a unique slug for a pet name, appending a counter on collisions.
     *
     * The database's unique constraint on `slug` is the final guard against
     * concurrent inserts producing the same slug.
     */
    public static function generateUniqueSlug(string $name): string
    {
        $base = Str::slug($name) ?: 'pet';
        $slug = $base;
        $counter = 1;

        while (static::query()->where('slug', $slug)->exists()) {
            $slug = "{$base}-{$counter}";
            $counter++;
        }

        return $slug;
    }

    /**
     * @return BelongsTo<Species, $this>
     */
    public function species(): BelongsTo
    {
        return $this->belongsTo(Species::class);
    }

    /**
     * @return BelongsTo<Breed, $this>
     */
    public function breed(): BelongsTo
    {
        return $this->belongsTo(Breed::class);
    }

    /**
     * @return HasMany<AdoptionApplication, $this>
     */
    public function adoptionApplications(): HasMany
    {
        return $this->hasMany(AdoptionApplication::class);
    }

    /**
     * @return Attribute<array<int, string>|null, string|null>
     */
    protected function images(): Attribute
    {
        $config = $this->getImageConfig('images');
        $width = $config['width'] ?? 1200;
        $height = $config['height'] ?? 1200;
        $quality = $config['quality'] ?? 85;
        $mode = $config['mode'] ?? 'crop';

        return Attribute::make(
            set: fn ($value) => $this->processImageArray($value, $width, $height, $quality, $mode)
        );
    }

    /**
     * @return Attribute<string|null, never>
     */
    protected function ageInYears(): Attribute
    {
        return Attribute::make(
            get: function (): ?string {
                if (! $this->date_of_birth) {
                    return null;
                }

                $years = (int) $this->date_of_birth->diffInYears(now());

                if ($years < 1) {
                    $months = (int) $this->date_of_birth->diffInMonths(now());

                    if ($months < 3) {
                        $weeks = (int) $this->date_of_birth->diffInWeeks(now());

                        if ($weeks < 1) {
                            $days = (int) $this->date_of_birth->diffInDays(now());

                            return $days === 1 ? '1 day' : "{$days} days";
                        }

                        return $weeks === 1 ? '1 week' : "{$weeks} weeks";
                    }

                    return "{$months} months";
                }

                return $years === 1 ? '1 year' : "{$years} years";
            }
        );
    }

    /**
     * @return Attribute<array{total_days: int, human_readable: string}|null, never>
     */
    protected function daysInFosterCare(): Attribute
    {
        return Attribute::make(
            get: function (): ?array {
                if (! $this->intake_date) {
                    return null;
                }

                $totalDays = (int) $this->intake_date->diffInDays(now());

                // Calculate human-readable format
                $years = (int) $this->intake_date->diffInYears(now());
                $remainingDays = $totalDays - ($years * 365);

                $months = (int) floor($remainingDays / 30);
                $remainingDays -= $months * 30;

                $weeks = (int) floor($remainingDays / 7);
                $days = $remainingDays % 7;

                $parts = [];

                if ($years > 0) {
                    $parts[] = $years === 1 ? '1 year' : "{$years} years";
                }

                if ($months > 0) {
                    $parts[] = $months === 1 ? '1 month' : "{$months} months";
                }

                if ($weeks > 0) {
                    $parts[] = $weeks === 1 ? '1 week' : "{$weeks} weeks";
                }

                if ($days > 0 || empty($parts)) {
                    $parts[] = $days === 1 ? '1 day' : "{$days} days";
                }

                $humanReadable = implode(', ', $parts);

                return [
                    'total_days' => $totalDays,
                    'human_readable' => $humanReadable,
                ];
            }
        );
    }
}
