<?php

declare(strict_types=1);

namespace Acms\Services\Crypto;

use Acms\Services\Facades\Database as DB;
use Acms\Services\Facades\Logger as AcmsLogger;
use SQL;

/**
 * アプリ全体で共有するSalt（アプリ固定Salt / 現在Salt / 1つ前のSalt）を管理する。
 *
 * setAppSalt() は main.php がリクエストごとに1度だけ呼び出し、DB（sequenceテーブル）から
 * 読み出し・必要なら生成/ローテーションして自身の状態にキャッシュする。以降の
 * getCurrentSalt() 等はこのキャッシュを返すだけの読み取り専用アクセサになる。
 * コンテナのシングルトンとして登録し、リクエスト内で1つだけの状態という契約を維持する。
 */
class SaltRepository
{
    /**
     * 現在のソルト
     *
     * @var string|null
     */
    private $currentSalt = null;

    /**
     * 1つ前のソルト
     *
     * @var string|null
     */
    private $previousSalt = null;

    /**
     * アプリの固定ソルト
     *
     * @var string|null
     */
    private $appSalt = null;

    /**
     * アプリ全体で使用するSaltを更新・設定
     *
     * @return void
     */
    public function setAppSalt(): void
    {
        $sql = SQL::newSelect('sequence');
        $item = DB::query($sql->get(dsn()), 'row');

        if (!$item || !array_key_exists('sequence_current_salt', $item) || !array_key_exists('sequence_previous_salt', $item) || !array_key_exists('sequence_app_salt', $item)) {
            $this->currentSalt = PASSWORD_SALT_1;
            $this->previousSalt = PASSWORD_SALT_2;
            $this->appSalt = PASSWORD_SALT_1;
            AcmsLogger::error('データベースがアップデートされていません。管理画面の更新メニューからDBをアップデートください。');
            return;
        }
        $currentSalt = $item['sequence_current_salt'] ?? null;
        $previousSalt = $item['sequence_previous_salt'] ?? null;
        $appSalt = $item['sequence_app_salt'] ?? null;
        $updatedAt = strtotime($item['sequence_salt_updated_at'] ?? '2000-01-01 00:00:00');

        if ($appSalt === null) {
            $appSalt = "base64:" . base64_encode(random_bytes(32));
            $sql = SQL::newUpdate('sequence');
            $sql->addUpdate('sequence_app_salt', $appSalt);
            DB::query($sql->get(dsn()), 'exec');
        }
        if ($currentSalt === null || $previousSalt === null) {
            $currentSalt = "base64:" . base64_encode(random_bytes(32));
            $previousSalt = "base64:" . base64_encode(random_bytes(32));
            $sql = SQL::newUpdate('sequence');
            $sql->addUpdate('sequence_current_salt', $currentSalt);
            $sql->addUpdate('sequence_previous_salt', $previousSalt);
            $sql->addUpdate('sequence_app_salt', $appSalt);
            $sql->addUpdate('sequence_salt_updated_at', date('Y-m-d H:i:s', REQUEST_TIME));
            DB::query($sql->get(dsn()), 'exec');
        } elseif ((REQUEST_TIME - $updatedAt) > (60 * 60 * 24)) {
            $previousSalt = $currentSalt;
            $currentSalt = "base64:" . base64_encode(random_bytes(32));
            $sql = SQL::newUpdate('sequence');
            $sql->addUpdate('sequence_current_salt', $currentSalt);
            $sql->addUpdate('sequence_previous_salt', $previousSalt);
            $sql->addUpdate('sequence_salt_updated_at', date('Y-m-d H:i:s', REQUEST_TIME));
            DB::query($sql->get(dsn()), 'exec');
        }
        $this->currentSalt = $currentSalt;
        $this->previousSalt = $previousSalt;
        $this->appSalt = $appSalt;
    }

    /**
     * 現在のソルトを取得
     *
     * @return string
     */
    public function getCurrentSalt(): string
    {
        return $this->currentSalt ?? base64_encode(random_bytes(32));
    }

    /**
     * 現在のソルトを設定
     *
     * @param string $salt
     * @return void
     */
    public function setCurrentSalt(string $salt): void
    {
        $this->currentSalt = $salt;
    }

    /**
     * 1つ前のソルトを取得
     *
     * @return string
     */
    public function getPreviousSalt(): string
    {
        return $this->previousSalt ?? base64_encode(random_bytes(32));
    }

    /**
     * 1つ前のソルトを設定
     *
     * @param string $salt
     * @return void
     */
    public function setPreviousSalt(string $salt): void
    {
        $this->previousSalt = $salt;
    }

    /**
     * アプリの固定ソルトを取得
     *
     * @return string
     */
    public function getAppSalt(): string
    {
        return $this->appSalt ?? base64_encode(random_bytes(32));
    }
}
