<?php

declare(strict_types=1);

namespace BotGuard\Models;

use BotGuard\Core\Model;

/**
 * Programmatic-access API keys.
 *
 * Keys are of the form `bg_<40 hex>`. Only their SHA-256 hash is persisted; the
 * plaintext is returned to the caller exactly once at creation. Lookups hash the
 * presented key and match the stored hash in constant-ish time via a unique index.
 */
final class ApiKey extends Model
{
    protected string $table = 'api_keys';

    public const PREFIX = 'bg_';

    /**
     * Create a key for a user and return [row id, plaintext key].
     *
     * @return array{0:int, 1:string}
     */
    public function createForUser(int $userId, string $name): array
    {
        $plain = self::PREFIX . bin2hex(random_bytes(20));

        $id = $this->create([
            'user_id'    => $userId,
            'name'       => $name !== '' ? mb_substr($name, 0, 120) : 'API key',
            'key_prefix' => substr($plain, 0, 12),
            'key_hash'   => self::hash($plain),
            'revoked'    => 0,
        ]);

        return [$id, $plain];
    }

    /**
     * Resolve a presented key to its (non-revoked) row, updating last_used_at.
     *
     * @return array<string, mixed>|null
     */
    public function resolve(string $plain): ?array
    {
        if (! str_starts_with($plain, self::PREFIX)) {
            return null;
        }

        $row = $this->db->selectOne(
            "SELECT * FROM `{$this->table}` WHERE `key_hash` = ? AND `revoked` = 0 LIMIT 1",
            [self::hash($plain)]
        );

        if ($row !== null) {
            $this->db->affectingStatement(
                "UPDATE `{$this->table}` SET `last_used_at` = ? WHERE `id` = ?",
                [now(), $row['id']]
            );
        }

        return $row;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function forUser(int $userId): array
    {
        return $this->where(['user_id' => $userId], '`id` DESC');
    }

    /**
     * Revoke a key, scoped to its owner (prevents cross-user revocation).
     */
    public function revokeForUser(int $id, int $userId): int
    {
        return $this->db->affectingStatement(
            "UPDATE `{$this->table}` SET `revoked` = 1 WHERE `id` = ? AND `user_id` = ?",
            [$id, $userId]
        );
    }

    public static function hash(string $plain): string
    {
        return hash('sha256', $plain);
    }
}
