<?php

namespace App;

use App\Http\Controllers\StatusController;
use App\Models\Conversation;
use App\Models\Poll;
use App\Models\StatusEdit;
use App\Services\AccountService;
use App\Services\StatusService;
use Auth;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;
use Storage;

/**
 * @property int $id
 * @property int $profile_id
 * @property string|null $type
 * @property string|null $caption
 * @property int|null $in_reply_to_id
 * @property int|null $in_reply_to_profile_id
 * @property int|null $reblog_of_id
 * @property bool $is_nsfw
 * @property string|null $scope
 * @property string|null $visibility
 * @property string|null $cw_summary
 * @property bool $comments_disabled
 * @property int $likes_count
 * @property int $reblogs_count
 * @property int $reply_count
 * @property bool $local
 * @property string|null $place
 * @property \Illuminate\Support\Carbon|null $created_at
 * @property \Illuminate\Support\Carbon|null $updated_at
 * @property \Illuminate\Support\Carbon|null $deleted_at
 * @property \Illuminate\Support\Carbon|null $edited_at
 * @property-read \App\Profile $profile
 * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Media> $media
 * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Mention> $mentions
 */
class Status extends Model
{
    use HasSnowflakePrimary, SoftDeletes;

    /**
     * Indicates if the IDs are auto-incrementing.
     *
     * @var bool
     */
    public $incrementing = false;

    protected $guarded = [];

    protected function casts(): array
    {
        return [
            'deleted_at' => 'datetime',
            'edited_at' => 'datetime',
        ];
    }

    const STATUS_TYPES = [
        'text',
        'photo',
        'photo:album',
        'video',
        'video:album',
        'photo:video:album',
        'share',
        'reply',
        'story',
        'story:reply',
        'story:reaction',
        'story:live',
        'loop',
    ];

    const MAX_MENTIONS = 20;

    const MAX_HASHTAGS = 60;

    const MAX_LINKS = 5;

    public function profile()
    {
        return $this->belongsTo(Profile::class);
    }

    public function media()
    {
        return $this->hasMany(Media::class);
    }

    public function firstMedia()
    {
        return $this->hasMany(Media::class)->orderBy('order', 'asc')->first();
    }

    public function viewType()
    {
        if ($this->type) {
            return $this->type;
        }

        return $this->setType();
    }

    public function setType()
    {
        if (in_array($this->type, self::STATUS_TYPES)) {
            return $this->type;
        }
        $mimes = $this->media->pluck('mime')->toArray();
        $type = StatusController::mimeTypeCheck($mimes);
        if ($type) {
            $this->type = $type;
            $this->save();

            return $type;
        }
    }

    public function thumb($showNsfw = false)
    {
        $entity = StatusService::get($this->id, false);

        if (! $entity || ! isset($entity['media_attachments']) || empty($entity['media_attachments'])) {
            return url(Storage::url('public/no-preview.png'));
        }

        if ((! isset($entity['sensitive']) || $entity['sensitive']) && ! $showNsfw) {
            return url(Storage::url('public/no-preview.png'));
        }

        if (! isset($entity['visibility']) || ! in_array($entity['visibility'], ['public', 'unlisted'])) {
            return url(Storage::url('public/no-preview.png'));
        }

        return collect($entity['media_attachments'])
            ->filter(fn ($media) => $media['type'] == 'image' && in_array($media['mime'], ['image/jpeg', 'image/png', 'image/jpg']))
            ->map(function ($media) {
                if (! Str::endsWith($media['preview_url'], ['no-preview.png', 'no-preview.jpg'])) {
                    return $media['preview_url'];
                }

                return $media['url'];
            })
            ->first() ?? url(Storage::url('public/no-preview.png'));
    }

    public function url($forceLocal = false)
    {
        if ($this->uri) {
            return $forceLocal ? "/i/web/post/_/{$this->profile_id}/{$this->id}" : $this->uri;
        } else {
            $id = $this->id;
            $account = AccountService::get($this->profile_id, true);
            if (! $account || ! isset($account['username'])) {
                return '/404';
            }
            $path = url(config('app.url')."/p/{$account['username']}/{$id}");

            return $path;
        }
    }

    public function permalink($suffix = '/activity')
    {
        $id = $this->id;
        $username = $this->profile->username;
        $path = config('app.url')."/p/{$username}/{$id}{$suffix}";

        return url($path);
    }

    public function editUrl()
    {
        return $this->url().'/edit';
    }

    public function mediaUrl()
    {
        $media = $this->firstMedia();
        $path = $media->media_path;
        $hash = is_null($media->processed_at) ? md5('unprocessed') : md5($media->created_at);
        $url = $media->cdn_url ? $media->cdn_url."?v={$hash}" : url(Storage::url($path)."?v={$hash}");

        return $url;
    }

    public function likes()
    {
        return $this->hasMany(Like::class);
    }

    public function liked(): bool
    {
        if (! Auth::check()) {
            return false;
        }

        $pid = Auth::user()->profile_id;

        return Like::select('status_id', 'profile_id')
            ->whereStatusId($this->id)
            ->whereProfileId($pid)
            ->exists();
    }

    public function likedBy()
    {
        return $this->hasManyThrough(
            Profile::class,
            Like::class,
            'status_id',
            'id',
            'id',
            'profile_id'
        );
    }

    public function comments()
    {
        return $this->hasMany(self::class, 'in_reply_to_id');
    }

    public function bookmarked()
    {
        if (! Auth::check()) {
            return false;
        }
        $profile = Auth::user()->profile;

        return Bookmark::whereProfileId($profile->id)->whereStatusId($this->id)->count();
    }

    public function shares()
    {
        return $this->hasMany(self::class, 'reblog_of_id');
    }

    public function shared(): bool
    {
        if (! Auth::check()) {
            return false;
        }
        $pid = Auth::user()->profile_id;

        return $this->select('profile_id', 'reblog_of_id')
            ->whereProfileId($pid)
            ->whereReblogOfId($this->id)
            ->exists();
    }

    public function sharedBy()
    {
        return $this->hasManyThrough(
            Profile::class,
            Status::class,
            'reblog_of_id',
            'id',
            'id',
            'profile_id'
        );
    }

    public function parent()
    {
        $parent = $this->in_reply_to_id ?? $this->reblog_of_id;
        if (! empty($parent)) {
            return $this->findOrFail($parent);
        } else {
            return false;
        }
    }

    public function conversation()
    {
        return $this->hasOne(Conversation::class);
    }

    public function hashtags()
    {
        return $this->hasManyThrough(
            Hashtag::class,
            StatusHashtag::class,
            'status_id',
            'id',
            'id',
            'hashtag_id'
        );
    }

    public function mentions()
    {
        return $this->hasManyThrough(
            Profile::class,
            Mention::class,
            'status_id',
            'id',
            'id',
            'profile_id'
        );
    }

    public function reportUrl()
    {
        return route('report.form')."?type=post&id={$this->id}";
    }

    public function toActivityStream()
    {
        $media = $this->media;
        $mediaCollection = [];
        foreach ($media as $image) {
            $mediaCollection[] = [
                'type' => 'Link',
                'href' => $image->url(),
                'mediaType' => $image->mime,
            ];
        }
        $obj = [
            '@context' => 'https://www.w3.org/ns/activitystreams',
            'type' => 'Document',
            'name' => null,
            'url' => $mediaCollection,
        ];

        return $obj;
    }

    public function recentComments()
    {
        return $this->comments()->orderBy('created_at', 'desc')->take(3);
    }

    public function scopeToAudience($audience)
    {
        if (! in_array($audience, ['to', 'cc']) || $this->local == false) {
            return;
        }
        $res = [];
        $res['to'] = [];
        $res['cc'] = [];
        $scope = $this->scope;
        $mentions = $this->mentions->map(function ($mention) {
            return $mention->permalink();
        })->toArray();

        if ($this->in_reply_to_id != null) {
            $parent = $this->parent();
            if ($parent) {
                $mentions = array_merge([$parent->profile->permalink()], $mentions);
            }
        }

        switch ($scope) {
            case 'public':
                $res['to'] = [
                    'https://www.w3.org/ns/activitystreams#Public',
                ];
                $res['cc'] = array_merge([$this->profile->permalink('/followers')], $mentions);
                break;

            case 'unlisted':
                $res['to'] = array_merge([$this->profile->permalink('/followers')], $mentions);
                $res['cc'] = [
                    'https://www.w3.org/ns/activitystreams#Public',
                ];
                break;

            case 'private':
                $res['to'] = array_merge([$this->profile->permalink('/followers')], $mentions);
                $res['cc'] = [];
                break;

                // TODO: Update scope when DMs are supported
            case 'direct':
                $res['to'] = [];
                $res['cc'] = [];
                break;
        }

        return $res[$audience];
    }

    public function place()
    {
        return $this->belongsTo(Place::class);
    }

    public function directMessage()
    {
        return $this->hasOne(DirectMessage::class);
    }

    public function poll()
    {
        return $this->hasOne(Poll::class);
    }

    public function edits()
    {
        return $this->hasMany(StatusEdit::class);
    }
}
