<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
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\Collection;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
use Spatie\Image\Enums\Fit;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;

class Meetup extends Model implements HasMedia
{
    use HasFactory;
    use HasSlug;
    use InteractsWithMedia;

    /**
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'slug',
        'city_id',
        'intro',
        'telegram_link',
        'webpage',
        'twitter_username',
        'matrix_group',
        'nostr',
        'nostr_status',
        'simplex',
        'signal',
        'community',
        'github_data',
        'visible_on_map',
        'is_active',
        'last_event_at',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'id' => 'integer',
        'city_id' => 'integer',
        'github_data' => 'json',
        'simplified_geojson' => 'array',
        'is_active' => 'boolean',
        'last_event_at' => 'datetime',
        'restore_point' => 'array',
    ];

    /**
     * Stammdaten, die im Restore-Point gesichert und wiederhergestellt werden.
     *
     * @var list<string>
     */
    public const RESTORE_POINT_ATTRIBUTES = [
        'name',
        'slug',
        'city_id',
        'intro',
        'telegram_link',
        'webpage',
        'twitter_username',
        'matrix_group',
        'nostr',
        'simplex',
        'signal',
        'community',
        'github_data',
        'visible_on_map',
    ];

    /**
     * Sichert den aktuellen Stand der Stammdaten als Restore-Point.
     * restore_point ist bewusst nicht fillable und nur per Command erreichbar.
     */
    public function captureRestorePoint(): void
    {
        $this->forceFill([
            'restore_point' => [
                'captured_at' => now()->toIso8601String(),
                'attributes' => $this->only(self::RESTORE_POINT_ATTRIBUTES),
            ],
        ])->saveQuietly();
    }

    /**
     * Stellt die Stammdaten aus dem Restore-Point wieder her.
     * Liefert false, wenn noch kein Restore-Point gesichert wurde.
     */
    public function restoreFromRestorePoint(): bool
    {
        $attributes = $this->restore_point['attributes'] ?? null;

        if (! is_array($attributes)) {
            return false;
        }

        $this->forceFill(
            collect($attributes)->only(self::RESTORE_POINT_ATTRIBUTES)->all()
        )->save();

        return true;
    }

    protected static function booted()
    {
        static::creating(function ($model) {
            if (! $model->created_by) {
                $model->created_by = auth()->id();
            }
        });

        // Der Ersteller wird automatisch als Leiter in die meetup_user-Pivot eingetragen,
        // damit das Meetup einheitlich (MCP, REST-API, Livewire) in „Meine Meetups"
        // erscheint – egal über welchen Pfad es angelegt wurde.
        static::created(function (Meetup $model): void {
            if ($model->created_by !== null) {
                $model->users()->syncWithoutDetaching([
                    $model->created_by => ['is_leader' => true],
                ]);
            }
        });
    }

    public function getSlugOptions(): SlugOptions
    {
        return SlugOptions::create()
            ->generateSlugsFrom(['name'])
            ->saveSlugsTo('slug')
            ->usingLanguage(Cookie::get('lang', config('app.locale')));
    }

    public function registerMediaConversions(?Media $media = null): void
    {
        $this
            ->addMediaConversion('preview')
            ->fit(Fit::Crop, 300, 300)
            ->nonQueued();
        $this
            ->addMediaConversion('thumb')
            ->fit(Fit::Crop, 130, 130)
            ->width(130)
            ->height(130);
    }

    public function registerMediaCollections(): void
    {
        $this
            ->addMediaCollection('logo')
            ->acceptsMimeTypes(['image/jpeg', 'image/png', 'image/gif', 'image/webp'])
            ->singleFile()
            ->useFallbackUrl(get_domain_attributes()['image']);
    }

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

    public function users()
    {
        return $this->belongsToMany(User::class);
    }

    /**
     * Ist der Nutzer über die meetup_user-Pivot Mitglied dieses Meetups?
     */
    public function hasMember(User $user): bool
    {
        return $this->users()->whereKey($user->id)->exists();
    }

    /**
     * Ist der Nutzer Leader dieses Meetups (meetup_user.is_leader = true)?
     * Nur Leader (und der Ersteller/Super-Admin) dürfen Stammdaten bearbeiten
     * und weitere Leader einsetzen/entziehen.
     */
    public function isLeader(User $user): bool
    {
        return $this->users()
            ->whereKey($user->id)
            ->wherePivot('is_leader', true)
            ->exists();
    }

    /**
     * Aktuelle Leader dieses Meetups (meetup_user.is_leader = true), Ersteller
     * zuerst. Gemeinsame Quelle für REST-API (MeetupLeaderController) und das
     * Portal-Frontend (meetups.edit).
     *
     * @return Collection<int, User>
     */
    public function leaders(): Collection
    {
        return $this->users()
            ->wherePivot('is_leader', true)
            ->get()
            ->sortByDesc(fn (User $user): bool => $user->getKey() === $this->created_by)
            ->values();
    }

    /**
     * Nutzer zum Leader befördern (meetup_user.is_leader = true). Idempotent.
     * Geteilt von REST-Controller, MCP-Tools und Portal-Frontend.
     */
    public function promoteLeader(User $user): void
    {
        $this->users()->syncWithoutDetaching([$user->getKey() => ['is_leader' => true]]);
    }

    /**
     * Nutzer die Leader-Rolle entziehen (Demote; bleibt Mitglied). Der Ersteller
     * des Meetups ist geschützt und wird nie entzogen (Domain-Invariante) —
     * Aufrufer dürfen zusätzlich mit 403 antworten.
     */
    public function demoteLeader(User $user): void
    {
        if ($user->getKey() === $this->created_by) {
            return;
        }

        $this->users()->updateExistingPivot($user->getKey(), ['is_leader' => false]);
    }

    /**
     * Den Nutzer als Mitglied (nicht Leader) zu „Meine Meetups" hinzufügen.
     * Idempotent: ein bereits hinzugefügter Nutzer bleibt unverändert. Gibt
     * true zurück, wenn neu hinzugefügt (false = war bereits Mitglied).
     * Geteilt von REST-Controller (addToMine) und MCP-Tool (AddMeetupToMineTool).
     */
    public function addMember(User $user): bool
    {
        $wasMember = $this->hasMember($user);

        $this->users()->syncWithoutDetaching([
            $user->getKey() => ['is_leader' => false],
        ]);

        return ! $wasMember;
    }

    /**
     * Den Nutzer wieder aus „Meine Meetups" entfernen (löst die meetup_user-Pivot).
     * Idempotent: war der Nutzer kein Mitglied, passiert nichts. Gibt true zurück,
     * wenn tatsächlich eine Zuordnung gelöst wurde (false = war kein Mitglied).
     * Gegenstück zu {@see addMember()}; die Stammdaten bleiben unberührt.
     */
    public function removeMember(User $user): bool
    {
        return $this->users()->detach($user->getKey()) > 0;
    }

    /**
     * RateLimiter-Key für Meetup-Stammdaten-Updates über das Portal-Frontend.
     */
    public static function updateRateLimitKey(int $userId): string
    {
        return 'meetup-update:'.$userId;
    }

    /**
     * Meetups, die dem Nutzer zugeordnet sind: selbst erstellt (created_by) ODER
     * Mitglied über die meetup_user-Pivot. Entspricht „Meine Meetups" im Portal.
     */
    public function scopeAssociatedWith(Builder $query, int $userId): void
    {
        $query->where(function (Builder $inner) use ($userId): void {
            $inner->where('created_by', $userId)
                ->orWhereHas('users', fn (Builder $user) => $user->whereKey($userId));
        });
    }

    /**
     * Meetups, die der Nutzer als Leader führt (meetup_user.is_leader = true).
     * Maßgeblich dafür, wer Stammdaten UND Termine bearbeiten darf.
     */
    public function scopeLedBy(Builder $query, int $userId): void
    {
        $query->whereHas('users', fn (Builder $user) => $user->whereKey($userId)->wherePivot('is_leader', true));
    }

    /**
     * Führt der eingeloggte Nutzer dieses Meetup als Leader? Steuert die
     * Sichtbarkeit der Bearbeiten-/Termin-Affordances im Portal-Frontend
     * (Gegenstück zu {@see belongsToMe()}, aber leader- statt mitgliedschafts-
     * basiert).
     */
    protected function leadByMe(): Attribute
    {
        return Attribute::make(
            get: fn (): bool => auth()->check() && DB::table('meetup_user')
                ->where('meetup_id', $this->id)
                ->where('user_id', auth()->id())
                ->where('is_leader', true)
                ->exists()
        );
    }

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

    protected function logoSquare(): Attribute
    {
        $media = $this->getFirstMedia('logo');
        if ($media) {
            $path = str($media->getPath())->after('storage/app/');
        } else {
            $path = 'img/einundzwanzig.png';
        }

        return Attribute::make(
            get: fn () => url()->route('img',
                [
                    'path' => $path,
                    'w' => 900,
                    'h' => 900,
                    'fit' => 'crop',
                    'fm' => 'webp',
                ]),
        );
    }

    protected function nextEvent(): Attribute
    {
        $nextEvent = $this->meetupEvents()->where('start', '>=', now())->orderBy('start')->first();

        return Attribute::make(
            get: fn () => $nextEvent ? [
                'id' => $nextEvent->id,
                'start' => $nextEvent->start,
                'portalLink' => url()->route('meetups.landingpage-event',
                    ['country' => $this->city->country, 'meetup' => $this, 'event' => $nextEvent]),
                'location' => $nextEvent->location,
                'description' => $nextEvent->description,
                'link' => $nextEvent->link,
                'attendees' => $nextEvent->attendeesCount(),
                'might_attendees' => $nextEvent->mightAttendeesCount(),
                'nostr_note' => str($nextEvent->nostr_status)->after('Sent event ')->before(' to '),
            ] : null,
        );
    }

    protected function belongsToMe(): Attribute
    {
        return Attribute::make(
            get: fn () => DB::table('meetup_user')->where('meetup_id', $this->id)->where('user_id',
                auth()->id())->exists(),
        );
    }

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

    public function recalculateActivity(): void
    {
        $threshold = now()->subYear();

        $lastEventAt = MeetupEvent::query()
            ->where('meetup_id', $this->id)
            ->where('start', '<=', now())
            ->max('start');

        $lastEventAt = $lastEventAt ? Date::parse($lastEventAt) : null;

        $hasFutureEvent = MeetupEvent::query()
            ->where('meetup_id', $this->id)
            ->where('start', '>', now())
            ->exists();

        $hasActiveRecurrence = MeetupEvent::query()
            ->where('meetup_id', $this->id)
            ->whereNotNull('recurrence_type')
            ->whereNotNull('recurrence_end_date')
            ->where('recurrence_end_date', '>=', now())
            ->exists();

        $isActive = ($lastEventAt && $lastEventAt->greaterThanOrEqualTo($threshold))
            || $hasFutureEvent
            || $hasActiveRecurrence;

        $this->forceFill([
            'is_active' => $isActive,
            'last_event_at' => $lastEventAt,
        ])->saveQuietly();
    }
}
