<?php

declare(strict_types = 1);

namespace Plugin\Pinboard\Entity;

use App\Core\Cache;
use App\Core\DB;
use App\Core\Entity;
use App\Entity\LocalUser;
use App\Util\Common;
use DateTimeInterface;

class Token extends Entity
{
    // {{{ Autocode
    // @codeCoverageIgnoreStart
    private int $actor_id;
    private string $token;
    private bool $enabled = false;
    private DateTimeInterface $created;

    public function setActorId(int $actor_id): self
    {
        $this->actor_id = $actor_id;
        return $this;
    }

    public function getActorId(): int
    {
        return $this->actor_id;
    }

    public function setToken(string $token): self
    {
        $this->token = mb_substr($token, 0, 64);
        return $this;
    }

    public function getToken(): string
    {
        return $this->token;
    }

    public function setEnabled(bool $enabled): self
    {
        $this->enabled = $enabled;
        return $this;
    }

    public function getEnabled(): bool
    {
        return $this->enabled;
    }

    public function setCreated(DateTimeInterface $created): self
    {
        $this->created = $created;
        return $this;
    }

    public function getCreated(): DateTimeInterface
    {
        return $this->created;
    }

    // @codeCoverageIgnoreEnd
    // }}} Autocode

    public static function cacheKeys(int $id): array
    {
        return [
            'user-token' => "pinboard-token-{$id}",
        ];
    }

    public function getUser(): LocalUser
    {
        return LocalUser::getById($this->getActorId());
    }

    /**
     * Get a token for a $id and $token pair, unless given a $user, in which case the token field is not validated
     *
     * XXX: may need to verify it's timing safe
     */
    public static function get(?int $id, ?string $token, ?LocalUser $user = null): ?self
    {
        if (!\is_null($user)) {
            return Cache::get(
                self::cacheKeys($user->getId())['user-token'],
                fn () => DB::dql(
                    'select t from \Plugin\Pinboard\Entity\Token t where t.actor_id = :id',
                    ['id' => $user->getId()],
                    options: ['limit' => 1],
                ),
            );
        } elseif (!\is_null($id) && !\is_null($token)) {
            return Cache::get(
                self::cacheKeys($id)['user-token'],
                fn () => DB::dql(
                    <<<'EOF'
                        select t from \Plugin\Pinboard\Entity\Token t
                        where t.actor_id = :id and t.token = :token and t.enabled = true
                        EOF,
                    ['id' => $id, 'token' => $token],
                    options: ['limit' => 1],
                ),
            );
        } else {
            return null;
        }
    }

    public function getUserTokenString()
    {
        return $this->getActorId() . ':' . $this->getToken();
    }

    public static function generateTokenString(): string
    {
        return bin2hex(random_bytes(Common::config('plugin_pinboard', 'token_length') / 2));
    }

    public static function schemaDef(): array
    {
        return [
            'name'   => 'pinboard_token',
            'fields' => [
                'actor_id' => ['type' => 'int', 'not null' => true, 'description' => 'Actor who created this note'],
                'token'    => ['type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'The token this user has enabled'],
                'enabled'  => ['type' => 'bool', 'not null' => true, 'default' => false, 'description whether this user enabled the pinboard API'],
                'created'  => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'],
            ],
            'primary key' => ['actor_id'],
        ];
    }
}
