<?php

namespace Acms\Services\Setup\Fix\Tools;

use Acms\Services\Setup\Http\Request;
use Acms\Services\Setup\SetupSession;
use Acms\Services\Setup\Tool\SetupTool;
use Acms\Services\Setup\Tool\ToolErrors;
use Acms\Services\Setup\Tool\ToolView;
use ACMS_RAM;
use Acms\Services\Facades\Logger;
use Acms\Services\Facades\Database;
use Field;
use SQL;

/**
 * 2段階認証リセットツール（fix.php?c=tfa-reset）。旧 setup/fix.php の Surfacer_FixTfaReset。
 * 2FA が原因でログインできなくなった管理者向けの復旧用。選択ユーザーの 2FA 設定を無効化する。
 */
class TfaReset implements SetupTool
{
    public function template(): string
    {
        return '@setup/fix/tfa-reset.twig';
    }

    public function show(Request $request): ToolView
    {
        return ToolView::form($this->viewData($request->post()));
    }

    public function handle(Request $request, SetupSession $session): ToolView
    {
        $post = $request->post();
        $data = $this->viewData($post);

        $errors = $this->validate($post);
        if (!$errors->isEmpty()) {
            return ToolView::invalid($errors, $data);
        }

        $this->apply($post);

        return ToolView::done($data);
    }

    /**
     * 2FA 有効ユーザー一覧はフォームで常に使うため、全画面で付与する。
     *
     * @return array<string, mixed>
     */
    private function viewData(Field $post): array
    {
        $SQL = SQL::newSelect('user');
        $SQL->addWhereOpr('user_tfa_secret', null, '<>'); // 2FA 有効（secret が NULL でない）ユーザーのみ
        $all = Database::query($SQL->get(dsn()), 'all');

        $users = [];
        foreach ($all as $row) {
            $users[] = [
                'uid'  => (int)$row['user_id'],
                'name' => $row['user_name'],
                'code' => $row['user_code'],
                'mail' => $row['user_mail'],
                'auth' => $row['user_auth'],
            ];
        }

        return [
            'users' => $users,
            'hasUser' => $users !== [],
            'currentUid' => (int)$post->get('uid'),
        ];
    }

    private function validate(Field $post): ToolErrors
    {
        if ($post->isNull('uid')) {
            return (new ToolErrors())->with('uid_input');
        }

        // 一覧に無い uid の投入を防ぐため、実在かつ 2FA 有効であることを再確認する
        $SQL = SQL::newSelect('user');
        $SQL->addWhereOpr('user_id', intval($post->get('uid')));
        $SQL->addWhereOpr('user_tfa_secret', null, '<>');
        if (!Database::query($SQL->get(dsn()), 'row')) {
            return (new ToolErrors())->with('uid_input');
        }

        return new ToolErrors();
    }

    private function apply(Field $post): void
    {
        $userId = intval($post->get('uid'));

        $SQL = SQL::newSelect('user');
        $SQL->addWhereOpr('user_id', $userId);
        $user = Database::query($SQL->get(dsn()), 'row');

        // 本体の 2FA 無効化（ACMS_POST_Member_Tfa_Unregister::disableTfa）と同じカラムをクリアする
        $SQL = SQL::newUpdate('user');
        $SQL->addUpdate('user_tfa_secret', null);
        $SQL->addUpdate('user_tfa_secret_iv', null);
        $SQL->addUpdate('user_tfa_recovery', null);
        $SQL->addWhereOpr('user_id', $userId);
        Database::query($SQL->get(dsn()), 'exec');
        ACMS_RAM::user($userId, null);

        // アクセス制限の解除に準じるセキュリティ上重要な操作のため監査ログに残す
        Logger::notice('メンテナンスツールで、ユーザーの2段階認証(2FA)を無効化しました', [
            'uid'  => $userId,
            'name' => is_array($user) ? ($user['user_name'] ?? '') : '',
            'code' => is_array($user) ? ($user['user_code'] ?? '') : '',
        ]);
    }
}
