<?php

declare(strict_types=1);

namespace App\Models;

use App\Core\Database;

/**
 * ApiKeyModel
 *
 * The raw secret is shown to the admin exactly once, at creation time
 * — only its SHA-256 hash and a short non-secret prefix (for
 * recognizing which key is which in the UI) are ever stored. This is
 * the same principle as password hashing: the database is never a
 * place an attacker could read out a usable secret.
 */
final class ApiKeyModel
{
    /** @return array{id:int, name:string, plaintext_key:string, prefix:string} plaintext_key is returned ONLY here, never stored. */
    public function create(string $name, array $scopes, int $userId): array
    {
        $secret = 'sk_live_' . bin2hex(random_bytes(24));
        $prefix = substr($secret, 0, 12);
        $hash = hash('sha256', $secret);

        $stmt = Database::connection()->prepare(
            'INSERT INTO api_keys (name, key_prefix, key_hash, scopes, created_by, created_at)
             VALUES (:name, :prefix, :hash, :scopes, :user, NOW())'
        );
        $stmt->execute(['name' => $name, 'prefix' => $prefix, 'hash' => $hash, 'scopes' => json_encode($scopes), 'user' => $userId]);
        $id = (int) Database::connection()->lastInsertId();

        return ['id' => $id, 'name' => $name, 'plaintext_key' => $secret, 'prefix' => $prefix];
    }

    public function all(): array
    {
        return Database::connection()->query(
            "SELECT ak.*, CONCAT(u.first_name, ' ', u.last_name) AS created_by_name
             FROM api_keys ak LEFT JOIN users u ON u.id = ak.created_by
             ORDER BY ak.created_at DESC"
        )->fetchAll();
    }

    /** Validates a bearer token and returns the key row if it's a real, unrevoked key — null otherwise. Updates last_used_at on success. */
    public function validate(string $plaintextKey): ?array
    {
        $hash = hash('sha256', $plaintextKey);
        $stmt = Database::connection()->prepare('SELECT * FROM api_keys WHERE key_hash = :hash AND revoked_at IS NULL');
        $stmt->execute(['hash' => $hash]);
        $row = $stmt->fetch();

        if ($row === false) {
            return null;
        }

        Database::connection()->prepare('UPDATE api_keys SET last_used_at = NOW() WHERE id = :id')->execute(['id' => $row['id']]);

        return $row;
    }

    public function revoke(int $id): void
    {
        Database::connection()->prepare('UPDATE api_keys SET revoked_at = NOW() WHERE id = :id')->execute(['id' => $id]);
    }

    public function hasScope(array $keyRow, string $scope): bool
    {
        $scopes = json_decode((string) ($keyRow['scopes'] ?? '[]'), true) ?: [];

        return $scopes === [] || in_array('*', $scopes, true) || in_array($scope, $scopes, true);
    }
}
