<?php

namespace App\Services;

use App\Status;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Lottery;

class ReblogService
{
    const CACHE_KEY = 'pf:services:reblogs:';

    const REBLOGS_KEY = 'pf:services:reblogs:v1:post:';

    const COLDBOOT_KEY = 'pf:services:reblogs:v1:post_:';

    const CACHE_SKIP_KEY = 'pf:services:reblogs:skip_empty_check:';

    public static function get($profileId, $statusId)
    {
        return Lottery::odds(1, 20)
            ->winner(fn () => self::getFromDatabaseCheck($profileId, $statusId))
            ->loser(fn () => self::getFromRedis($profileId, $statusId))
            ->choose();
    }

    public static function getFromDatabaseCheck($profileId, $statusId)
    {
        if (! Redis::zcard(self::CACHE_KEY.$profileId)) {
            if (Cache::has(self::CACHE_SKIP_KEY.$profileId)) {
                return false;
            } else {
                self::warmCache($profileId);
                sleep(1);

                return self::getFromRedis($profileId, $statusId);
            }
        }

        $minId = SnowflakeService::byDate(now()->subMonths(12));

        if ($minId > $statusId) {
            return Redis::zscore(self::CACHE_KEY.$profileId, $statusId) != null;
        }

        $cachedRes = (bool) Redis::zscore(self::CACHE_KEY.$profileId, $statusId) != null;
        $databaseRes = (bool) self::getFromDatabase($profileId, $statusId);

        if ($cachedRes === $databaseRes) {
            return $cachedRes;
        }

        self::warmCache($profileId);
        sleep(1);

        return self::getFromDatabase($profileId, $statusId);
    }

    public static function getFromRedis($profileId, $statusId)
    {
        if (! Redis::zcard(self::CACHE_KEY.$profileId)) {
            if (Cache::has(self::CACHE_SKIP_KEY.$profileId)) {
                return false;
            } else {
                self::warmCache($profileId);
                sleep(1);

                return self::getFromDatabase($profileId, $statusId);
            }
        }

        return Redis::zscore(self::CACHE_KEY.$profileId, $statusId) != null;
    }

    public static function getFromDatabase($profileId, $statusId)
    {
        return Status::whereProfileId($profileId)
            ->where('reblog_of_id', $statusId)
            ->exists();
    }

    public static function add($profileId, $statusId)
    {
        return Redis::zadd(self::CACHE_KEY.$profileId, $statusId, $statusId);
    }

    public static function count($profileId)
    {
        return Redis::zcard(self::CACHE_KEY.$profileId);
    }

    public static function del($profileId, $statusId)
    {
        return Redis::zrem(self::CACHE_KEY.$profileId, $statusId);
    }

    public static function getWarmCacheCount($profileId)
    {
        $minId = SnowflakeService::byDate(now()->subMonths(12));

        return Status::where('id', '>', $minId)
            ->whereProfileId($profileId)
            ->whereNotNull('reblog_of_id')
            ->count();
    }

    public static function warmCache($profileId)
    {
        Redis::del(self::CACHE_KEY.$profileId);
        $minId = SnowflakeService::byDate(now()->subMonths(12));
        foreach (
            Status::where('id', '>', $minId)
                ->whereProfileId($profileId)
                ->whereNotNull('reblog_of_id')
                ->lazy() as $post
        ) {
            self::add($profileId, $post->reblog_of_id);
        }
        Cache::put(self::CACHE_SKIP_KEY.$profileId, 1, now()->addHours(24));
    }

    public static function getPostReblogs($id, $start = 0, $stop = 10)
    {
        if (! Redis::zcard(self::REBLOGS_KEY.$id)) {
            return Cache::remember(self::COLDBOOT_KEY.$id, 86400, function () use ($id) {
                return Status::whereReblogOfId($id)
                    ->pluck('id')
                    ->each(function ($reblog) use ($id) {
                        self::addPostReblog($id, $reblog);
                    })
                    ->map(function ($reblog) {
                        return (string) $reblog;
                    });
            });
        }

        return Redis::zrange(self::REBLOGS_KEY.$id, $start, $stop);
    }

    public static function addPostReblog($parentId, $reblogId)
    {
        $pid = intval($parentId);
        $id = intval($reblogId);
        if ($pid && $id) {
            return Redis::zadd(self::REBLOGS_KEY.$pid, $id, $id);
        }
    }

    public static function removePostReblog($parentId, $reblogId)
    {
        $pid = intval($parentId);
        $id = intval($reblogId);
        if ($pid && $id) {
            return Redis::zrem(self::REBLOGS_KEY.$pid, $id);
        }
    }
}