<?php

namespace Acms\Services\Setup\Install;

use Acms\Services\Database\Engine\PdoEngine;
use Acms\Services\Installer\DatabaseNameValidator;
use Acms\Services\Setup\Tool\ToolErrors;
use Field;

/**
 * インストール phase 3（データベース設定）。旧 setup/install.php の Install_Step_3。
 * DB 接続確認・DB 名/接頭辞の検証（W4）・DB 作成/変更を行い、接続情報を config.server.php に書き込む。
 */
class Step3 extends AbstractStep
{
    public function template(): string
    {
        return '@setup/install/database.twig';
    }

    public function check(Field $post): StepResult
    {
        $errors = $this->checkInput($post);

        // W4: DB 名・テーブル接頭辞を CREATE/ALTER DATABASE 前に許可リスト検証する。
        // 現状バッククォート囲みのみで、バッククォートを含む db_name で SQL を抜けられるため。
        // 空欄は checkInput が別途 *_input エラーにするので、非空のときだけ書式を検証する。
        $dbValidator = new DatabaseNameValidator();
        $dbName = $post->get('db_name');
        if ($dbName !== '' && !$dbValidator->isValidDatabaseName($dbName)) {
            $errors = $errors->with('db_name_invalid');
        }
        $dbPrefix = $post->get('db_prefix');
        if ($dbPrefix !== '' && !$dbValidator->isValidPrefix($dbPrefix)) {
            $errors = $errors->with('db_prefix_invalid');
        }
        // W4: db_collation も下の CREATE/ALTER DATABASE ... COLLATE に生で補間されるため検証する
        // （フォームの <select> はクライアント側の制約でしかない）。照合順序は英数字＋アンダースコアのみ。
        $dbCollation = $post->get('db_collation');
        if ($dbCollation !== '' && !preg_match('/^[A-Za-z0-9_]+$/', $dbCollation)) {
            $errors = $errors->with('db_collation_invalid');
        }

        // 入力/書式エラーがある間は接続・DB 作成に進まない（旧: if (empty($this->Error)) ガード）。
        if (!$errors->isEmpty()) {
            return new StepResult($errors, new ToolErrors());
        }

        $dsn = [
            'host' => $post->get('db_host'),
            'user' => $post->get('db_user'),
            'pass' => $post->get('db_pass'),
            'name' => $post->get('db_name'),
        ];
        // Why not Database ファサード: ファサードは 'db' シングルトン = PdoEngine::singleton(dsn()) に転送し、
        // その解決時に config.server.php の DB_* から作った dsn() で即接続する。インストール時点（特に新規）は
        // DB 設定が未確定で dsn() が無効なため、ファサードに触れた瞬間 PDOException で落ちる。このステップは
        // まだ永続化していないユーザー入力の資格情報を検証し DB を CREATE するのが役目なので、設定済み DB に
        // 束縛されたシングルトンではなく独立した使い捨てインスタンスで接続する必要がある（fix/import/update は
        // インストール済み＝DB 存在が前提なのでファサードを使う。ここは DB 確定前に動く例外）。この instance は
        // シングルトンではなくローカルで完結するため、グローバル状態を増やさない点でも直接生成が正しい。
        $DB = new PdoEngine();

        if (!$DB->checkConnection(dsn($dsn))) {
            return new StepResult($errors->with('server_connect'), new ToolErrors());
        }
        if ($post->get('db_create') === '' && !$DB->checkConnectDatabase(dsn($dsn))) {
            return new StepResult($errors->with('database_connect'), new ToolErrors());
        }

        $DB->setThrowException(true);
        try {
            $dsn['name'] = '';
            $DB->connect(dsn($dsn));
            $charset = $post->get('db_charset');
            $collation = $post->get('db_collation');
            $charsetName = strtolower($charset) === 'utf8mb4' ? 'utf8mb4' : 'utf8';

            if ($post->get('db_create') !== '') {
                $sql = [
                    'sql' => 'CREATE DATABASE `' . $post->get('db_name') . '` DEFAULT CHARACTER SET ' . $charsetName . ' COLLATE ' . $collation,
                    'params' => [],
                ];
                $DB->query($sql, 'exec');
            } else {
                $sql = [
                    'sql' => 'ALTER DATABASE `' . $post->get('db_name') . '` DEFAULT CHARACTER SET ' . $charsetName . ' COLLATE ' . $collation,
                    'params' => [],
                ];
                $DB->query($sql, 'exec');
            }
        } catch (\Exception $e) {
            return new StepResult($errors->with('create_database'), new ToolErrors());
        }
        $DB->setThrowException(false);

        return new StepResult($errors, new ToolErrors());
    }

    public function commit(Field $post): StepResult
    {
        $errors = new ToolErrors();
        $errors = $errors
            ->merge($this->writeConfig('DB_HOST', $post->get('db_host')))
            ->merge($this->writeConfig('DB_NAME', $post->get('db_name')))
            ->merge($this->writeConfig('DB_USER', $post->get('db_user')))
            ->merge($this->writeConfig('DB_PASS', $post->get('db_pass')))
            ->merge($this->writeConfig('DB_PREFIX', $post->get('db_prefix')));

        if ($post->get('db_charset') === 'utf8mb4') {
            $errors = $errors
                ->merge($this->writeConfig('DB_CHARSET', 'UTF-8'))
                ->merge($this->writeConfig('DB_CONNECTION_CHARSET', 'utf8mb4'));
        } else {
            $errors = $errors->merge($this->writeConfig('DB_CHARSET', $post->get('db_charset')));
        }

        return new StepResult($errors, new ToolErrors());
    }
}
