<?php

namespace App;

use App\Models\ProfileAlias;
use App\Services\FollowerService;
use App\Util\Lexer\PrettyNumber;
use Cache;
use DB;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Storage;

/**
 * @property int $id
 * @property int|null $user_id
 * @property string $username
 * @property string|null $name
 * @property string|null $domain
 * @property string|null $remote_url
 * @property bool $is_private
 * @property int $status_count
 * @property int $following_count
 * @property int $followers_count
 * @property \Illuminate\Support\Carbon|null $last_fetched_at
 * @property \Illuminate\Support\Carbon|null $last_status_at
 * @property \Illuminate\Support\Carbon|null $created_at
 * @property \Illuminate\Support\Carbon|null $updated_at
 * @property \Illuminate\Support\Carbon|null $deleted_at
 * @property-read \App\User|null $user
 * @property-read \App\Avatar $avatar
 */
class Profile extends Model
{
    use HasSnowflakePrimary, SoftDeletes;

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

    protected $hidden = ['private_key'];

    protected $visible = ['id', 'user_id', 'username', 'name'];

    protected $guarded = [];

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

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function url($suffix = null)
    {
        return $this->remote_url ?? url($this->username.$suffix);
    }

    public function localUrl($suffix = null)
    {
        return url($this->username.$suffix);
    }

    public function permalink($suffix = null)
    {
        return $this->remote_url ?? url('users/'.$this->username.$suffix);
    }

    public function emailUrl()
    {
        if ($this->domain) {
            return $this->username;
        }

        $domain = parse_url(config('app.url'), PHP_URL_HOST);

        return $this->username.'@'.$domain;
    }

    public function statuses()
    {
        return $this->hasMany(Status::class);
    }

    public function followingCount($short = false)
    {
        $count = Cache::remember('profile:following_count:'.$this->id, now()->addMonths(1), function () {
            if ($this->domain == null && $this->user->settings->show_profile_following_count == false) {
                return 0;
            }
            $count = DB::table('followers')->where('profile_id', $this->id)->count();
            if ($this->following_count != $count) {
                $this->following_count = $count;
                $this->save();
            }

            return $count;
        });

        return $short ? PrettyNumber::convert($count) : $count;
    }

    public function followerCount($short = false)
    {
        $count = Cache::remember('profile:follower_count:'.$this->id, now()->addMonths(1), function () {
            if ($this->domain == null && $this->user->settings->show_profile_follower_count == false) {
                return 0;
            }
            $count = DB::table('followers')->where('following_id', $this->id)->count();
            if ($this->followers_count != $count) {
                $this->followers_count = $count;
                $this->save();
            }

            return $count;
        });

        return $short ? PrettyNumber::convert($count) : $count;
    }

    public function statusCount()
    {
        return $this->status_count;
    }

    public function following()
    {
        return $this->belongsToMany(
            self::class,
            'followers',
            'profile_id',
            'following_id'
        );
    }

    public function followers()
    {
        return $this->belongsToMany(
            self::class,
            'followers',
            'following_id',
            'profile_id'
        );
    }

    public function follows($profile): bool
    {
        return Follower::whereProfileId($this->id)->whereFollowingId($profile->id)->exists();
    }

    public function followedBy($profile): bool
    {
        return Follower::whereProfileId($profile->id)->whereFollowingId($this->id)->exists();
    }

    public function bookmarks()
    {
        return $this->belongsToMany(
            Status::class,
            'bookmarks',
            'profile_id',
            'status_id'
        );
    }

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

    public function avatar()
    {
        return $this->hasOne(Avatar::class)->withDefault([
            'media_path' => 'public/avatars/default.jpg',
            'change_count' => 0,
        ]);
    }

    public function avatarUrl()
    {
        $url = Cache::remember('avatar:'.$this->id, 1209600, function () {
            $avatar = $this->avatar;

            if (! $avatar) {
                return url('/storage/avatars/default.jpg');
            }

            if ($avatar->cdn_url) {
                if (substr($avatar->cdn_url, 0, 8) === 'https://') {
                    return $avatar->cdn_url;
                } else {
                    return url('/storage/avatars/default.jpg');
                }
            }

            $path = $avatar->media_path;

            if (! $path) {
                return url('/storage/avatars/default.jpg');
            }

            if ($avatar->is_remote &&
                $avatar->remote_url &&
                boolval(config_cache('federation.avatars.store_local')) == true
            ) {
                return $avatar->remote_url;
            }

            if ($path === 'public/avatars/default.jpg') {
                return url('/storage/avatars/default.jpg');
            }

            if (substr($path, 0, 6) !== 'public') {
                return url('/storage/avatars/default.jpg');
            }

            if (config('filesystems.default') !== 'local') {
                return Storage::url($path);
            }

            $path = "{$path}?v={$avatar->change_count}";

            return url(Storage::url($path));
        });

        return $url;
    }

    // deprecated
    public function recommendFollowers()
    {
        return collect([]);
    }

    public function keyId()
    {
        if ($this->remote_url) {
            return;
        }

        return $this->permalink('#main-key');
    }

    public function mutedIds()
    {
        return UserFilter::whereUserId($this->id)
            ->whereFilterableType('App\Profile')
            ->whereFilterType('mute')
            ->pluck('filterable_id');
    }

    public function blockedIds()
    {
        return UserFilter::whereUserId($this->id)
            ->whereFilterableType('App\Profile')
            ->whereFilterType('block')
            ->pluck('filterable_id');
    }

    public function mutedProfileUrls()
    {
        $ids = $this->mutedIds();

        return $this->whereIn('id', $ids)->get()->map(function ($i) {
            return $i->url();
        });
    }

    public function blockedProfileUrls()
    {
        $ids = $this->blockedIds();

        return $this->whereIn('id', $ids)->get()->map(function ($i) {
            return $i->url();
        });
    }

    public function reports()
    {
        return $this->hasMany(Report::class, 'profile_id');
    }

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

    public function inboxUrl()
    {
        return $this->inbox_url ?? $this->permalink('/inbox');
    }

    public function outboxUrl()
    {
        return $this->outbox_url ?? $this->permalink('/outbox');
    }

    public function sharedInbox()
    {
        return $this->sharedInbox ?? $this->inboxUrl();
    }

    public function getDefaultScope()
    {
        return $this->is_private == true ? 'private' : 'public';
    }

    public function getAudience($scope = false)
    {
        if ($this->remote_url) {
            return [];
        }
        $scope = $scope ?? $this->getDefaultScope();
        $audience = [];
        switch ($scope) {
            case 'public':
                $audience = [
                    'to' => [
                        'https://www.w3.org/ns/activitystreams#Public',
                    ],
                    'cc' => [
                        $this->permalink('/followers'),
                    ],
                ];
                break;

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

            case 'private':
                $audience = [
                    'to' => [
                        $this->permalink('/followers'),
                    ],
                    'cc' => [
                    ],
                ];
                break;
        }

        return $audience;
    }

    public function getAudienceInbox($scope = 'public')
    {
        return FollowerService::audience($this->id, $scope);
    }

    public function circles()
    {
        return $this->hasMany(Circle::class);
    }

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

    public function hashtagFollowing()
    {
        return $this->hasMany(HashtagFollow::class);
    }

    public function collections()
    {
        return $this->hasMany(Collection::class);
    }

    public function hasFollowRequestById(int $id)
    {
        return FollowRequest::whereFollowerId($id)
            ->whereFollowingId($this->id)
            ->exists();
    }

    public function stories()
    {
        return $this->hasMany(Story::class);
    }

    public function reported()
    {
        return $this->hasMany(Report::class, 'object_id');
    }

    public function aliases()
    {
        return $this->hasMany(ProfileAlias::class);
    }
}
