<?php

namespace Acms\Services\Setup\Install;

use Acms\Services\Installer\HtaccessGuardStatus;
use Acms\Services\Setup\SetupCommon;
use Acms\Services\Setup\Tool\ToolErrors;
use Acms\Services\Facades\LocalStorage;
use Field;

/**
 * インストール phase 1（動作要件チェック）。旧 setup/install.php の Install_Step_1。
 * 必須/推奨 PHP 拡張、必須ファイルの存在・パーミッション、.htaccess の有効化（W7）を検査する。
 */
class Step1 extends AbstractStep
{
    /**
     * 必須PHP拡張の定義
     * composer.json の require セクションに対応
     */
    private const REQUIRED_EXTENSIONS = [
        'curl',
        'dom',
        'fileinfo',
        'filter',
        'gd',
        'hash',
        'json',
        'mbstring',
        'pcre',
        'pdo',
        'pdo_mysql',
        'session',
        'xmlreader',
        'xmlwriter',
        'zip',
    ];

    /**
     * オプションPHP拡張の定義（警告レベル）
     * composer.json の suggest セクションに対応
     */
    private const OPTIONAL_EXTENSIONS = [
        'imagick',
        'exif',
        'opcache',
        'apcu',
        'redis',
        'gettext',
        'openssl',
    ];

    /**
     * オプション機能のチェック（警告レベル）
     * 拡張はあるが特定の関数が存在しない場合など
     */
    private const OPTIONAL_FUNCTIONS = [
        'imagerotate' => 'imagerotate',
    ];

    public function template(): string
    {
        return '@setup/install/requirements.twig';
    }

    public function check(Field $post): StepResult
    {
        $errors = new ToolErrors();
        $notices = new ToolErrors();

        // 必須拡張のチェック
        foreach (self::REQUIRED_EXTENSIONS as $ext) {
            if (!extension_loaded($ext)) {
                $errors = $errors->with($ext . '_exists');
            }
        }

        // オプション拡張のチェック（警告）
        foreach (self::OPTIONAL_EXTENSIONS as $ext) {
            if (!extension_loaded($ext)) {
                $notices = $notices->with($ext . '_exists');
            }
        }

        // オプション機能のチェック（特定関数の存在確認・警告）
        foreach (self::OPTIONAL_FUNCTIONS as $key => $func) {
            if (!function_exists($func)) {
                $notices = $notices->with($key . '_exists');
            }
        }

        // exists
        $errors = $errors
            ->merge($this->fileExists('config_exists', PATH_CONFIG))
            ->merge($this->fileExists('license_exists', PATH_LICENSE))
            ->merge($this->fileExists('archives_exists', PATH_ARCHIVES))
            ->merge($this->fileExists('cache_exists', PATH_CACHE))
            ->merge($this->fileExists('themes_exists', PATH_THEMES));

        // W7: 直アクセス防止 .htaccess を「配置対象すべて」で必須化する（Apache/LiteSpeed のとき）。
        // .htaccess は Apache 系でのみ有効なので非 Apache（nginx 等）は一律 skip（インストールは
        // 止めず、別途サーバ設定で直アクセスを塞ぐ運用）。判定は HtaccessGuardStatus（純粋）。
        //
        // 2 系統ある:
        // (1) 静的 .htaccess を直同梱するのは php/lang のみ。更新の moveList で dir ごと再配布される
        //     ため、更新のたびに自動で再有効化される（＝既存サイトにも届く）。純粋なコード dir で
        //     ユーザーが個別 FTP しないので dotfile 欠落リスクも低い。
        // (2) それ以外（root / extension / データ系 archives/media/storage/private/cache/cron/themes）は
        //     更新で配布されない＋ユーザー管理＋dotfile が転送で欠落しやすいので htaccess.txt を同梱し
        //     .htaccess へリネームさせる（extension も moveList 外＝自動更新されないため此方）。
        // 配布形態は違うが「インストール時点で全部 .htaccess が有効」であることを本チェックで揃える
        // （インストーラーのセキュリティ強化）。すべて required（欠けていれば Apache では止める）。
        //
        // htaccess.txt → .htaccess のリネームはユーザーに手動でさせず、インストーラーが肩代わりする
        // （HtaccessActivator。サーバサイドのファイル移動なので dotfile 転送問題も起きない）。書き込み
        // 権限が無くリネームできなかったものだけ、下の存在チェックがパーミッションエラーとして提示する。
        //
        // archives/media/storage/cache/themes は必ず PATH_ARCHIVES 等（= SCRIPT_DIR . {XXX}_DIR、他の
        // fileExists/checkPermission と同じ実体パス）を使う。過去に literal のトップレベル固定
        // （'archives/' 等）を使っていたことがあり、config.server.php で ARCHIVES_DIR 等が
        // 既定値以外に設定されている状態（config.server.php を使い回しての再インストール等）で
        // active化 / 必須チェック双方が実際に使われるディレクトリとズレ、本来保護されていないディレクトリを「有効化済み」と誤判定して
        // インストールを通してしまう不具合があったため、定数経由の実パスに統一した。
        (new HtaccessActivator())->activate([
            SCRIPT_DIR,
            SCRIPT_DIR . 'extension/',
            PATH_ARCHIVES,
            PATH_MEDIA,
            PATH_STORAGE,
            PATH_CACHE,
            SCRIPT_DIR . 'private/',
            SCRIPT_DIR . 'cron/',
            PATH_THEMES,
        ]);

        $serverSoftware = strtolower((string)($_SERVER['SERVER_SOFTWARE'] ?? ''));
        $isApache = str_contains($serverSoftware, 'apache') || str_contains($serverSoftware, 'litespeed');
        $htaccessGuard = new HtaccessGuardStatus();
        $htaccessTargets = [
            // 同梱 .htaccess（更新で再配布される php/lang のみ）
            ['path' => 'php_htaccess_exists', 'present' => LocalStorage::exists(SCRIPT_DIR . PHP_DIR . '.htaccess'), 'required' => true],
            ['path' => 'lang_htaccess_exists', 'present' => LocalStorage::exists(SCRIPT_DIR . 'lang/.htaccess'), 'required' => true],
            // htaccess.txt → .htaccess リネーム対象（root / extension / データ系）。
            // archives/media/storage/cache/themes は上記 activate() と同じ PATH_* 定数で判定する。
            ['path' => 'htaccess_exists', 'present' => LocalStorage::exists(PATH_HTACCESS), 'required' => true],
            ['path' => 'extension_htaccess_exists', 'present' => LocalStorage::exists(SCRIPT_DIR . 'extension/.htaccess'), 'required' => true],
            ['path' => 'archives_htaccess_exists', 'present' => LocalStorage::exists(PATH_ARCHIVES . '.htaccess'), 'required' => true],
            ['path' => 'media_htaccess_exists', 'present' => LocalStorage::exists(PATH_MEDIA . '.htaccess'), 'required' => true],
            ['path' => 'storage_htaccess_exists', 'present' => LocalStorage::exists(PATH_STORAGE . '.htaccess'), 'required' => true],
            ['path' => 'cache_htaccess_exists', 'present' => LocalStorage::exists(PATH_CACHE . '.htaccess'), 'required' => true],
            ['path' => 'private_htaccess_exists', 'present' => LocalStorage::exists(SCRIPT_DIR . 'private/.htaccess'), 'required' => true],
            ['path' => 'cron_htaccess_exists', 'present' => LocalStorage::exists(SCRIPT_DIR . 'cron/.htaccess'), 'required' => true],
            ['path' => 'themes_htaccess_exists', 'present' => LocalStorage::exists(PATH_THEMES . '.htaccess'), 'required' => true],
        ];
        foreach ($htaccessGuard->evaluate($htaccessTargets, $isApache) as $htaccessKey => $htaccessStatus) {
            if ($htaccessStatus === HtaccessGuardStatus::STATUS_ERROR) {
                $errors = $errors->with($htaccessKey);
            }
        }

        // permission
        if (!SetupCommon::checkPermission(PATH_ARCHIVES)) {
            $errors = $errors->with('archives_permission');
        }
        if (!SetupCommon::checkPermission(PATH_MEDIA)) {
            $errors = $errors->with('media_permission');
        }
        if (!SetupCommon::checkPermission(PATH_STORAGE)) {
            $errors = $errors->with('storage_permission');
        }
        if (!SetupCommon::checkPermission(PATH_CACHE)) {
            $errors = $errors->with('cache_permission');
        }
        if (!SetupCommon::checkPermission(PATH_CONFIG, '666')) {
            $errors = $errors->with('config_permission');
        }

        return new StepResult($errors, $notices, ['environmentReport' => $this->buildEnvironmentReport()]);
    }

    /**
     * 結果画面で「何が正しく設定されているか」を肯定的に提示する環境レポートを組み立てる。
     * 旧インストーラーの動作環境チェック表示（パーミッション / GD / .htaccess / .env）に相当する。
     *
     * 結果画面は必須チェックを通過したときだけ表示されるため、必須項目（GD・パーミッション・Apache 時の
     * .htaccess）は基本 OK になる。非必須（.env、非 Apache 時の .htaccess）は実状態で提示する。
     * PHP バージョンと拡張の注意（OPcache 等）は既存の phpVersion / noticeMessages で表示済みのため、
     * ここでは扱わない（重複回避）。
     *
     * @return list<array{title: string, items: list<array{ok: bool, message: string}>}>
     */
    private function buildEnvironmentReport(): array
    {
        $serverSoftware = strtolower((string)($_SERVER['SERVER_SOFTWARE'] ?? ''));
        $isApache = str_contains($serverSoftware, 'apache') || str_contains($serverSoftware, 'litespeed');

        $configPermission = SetupCommon::checkPermission(PATH_CONFIG, '666');
        $uploadPermission = SetupCommon::checkPermission(PATH_ARCHIVES)
            && SetupCommon::checkPermission(PATH_MEDIA)
            && SetupCommon::checkPermission(PATH_STORAGE)
            && SetupCommon::checkPermission(PATH_CACHE);
        $gd = extension_loaded('gd');
        $env = LocalStorage::exists(SCRIPT_DIR . '.env');

        $htaccessItems = [];
        if ($isApache) {
            // どこが有効化済み/未有効かが分かるよう、1 ディレクトリ 1 行で個別に提示する（まとめない）。
            // private / cron は dir 定数が無いためリテラル。他は check() の必須チェックと同じ PATH_* 定数を使う
            // （literal のトップレベル固定にすると、config.server.php で ARCHIVES_DIR 等が変更されている場合に
            // 実際に使われるディレクトリと表示がズレるため）。
            $htaccessTargets = [
                ['label' => 'ルートディレクトリ', 'path' => PATH_HTACCESS],
                ['label' => 'php ディレクトリ', 'path' => SCRIPT_DIR . PHP_DIR . '.htaccess'],
                ['label' => 'lang ディレクトリ', 'path' => SCRIPT_DIR . 'lang/.htaccess'],
                ['label' => 'extension ディレクトリ', 'path' => SCRIPT_DIR . 'extension/.htaccess'],
                ['label' => 'archives ディレクトリ', 'path' => PATH_ARCHIVES . '.htaccess'],
                ['label' => 'media ディレクトリ', 'path' => PATH_MEDIA . '.htaccess'],
                ['label' => 'storage ディレクトリ', 'path' => PATH_STORAGE . '.htaccess'],
                ['label' => 'cache ディレクトリ', 'path' => PATH_CACHE . '.htaccess'],
                ['label' => 'private ディレクトリ', 'path' => SCRIPT_DIR . 'private/.htaccess'],
                ['label' => 'cron ディレクトリ', 'path' => SCRIPT_DIR . 'cron/.htaccess'],
                ['label' => 'themes ディレクトリ', 'path' => PATH_THEMES . '.htaccess'],
            ];
            foreach ($htaccessTargets as $target) {
                $ok = LocalStorage::exists($target['path']);
                $htaccessItems[] = [
                    'ok' => $ok,
                    'message' => $ok
                        ? sprintf('%sの .htaccess は有効化されています。', $target['label'])
                        : sprintf('%sの .htaccess が有効化されていません。有効化を強くお勧めします。', $target['label']),
                ];
            }
        } else {
            $htaccessItems[] = [
                'ok' => false,
                'message' => 'Apache 系以外のサーバーでは .htaccess は使用されません。別途サーバー設定で直接アクセスを制限してください。',
            ];
        }

        return [
            [
                'title' => 'パーミッション',
                'items' => [
                    [
                        'ok' => $configPermission,
                        'message' => $configPermission
                            ? 'config.server.php のパーミッションは、正しく設定されています。'
                            : 'config.server.php のパーミッションが正しく設定されていません。',
                    ],
                    [
                        'ok' => $uploadPermission,
                        'message' => $uploadPermission
                            ? 'ファイルのアップロード用のディレクトリのパーミッションは、正しく設定されています。'
                            : 'ファイルのアップロード用のディレクトリのパーミッションが正しく設定されていません。',
                    ],
                ],
            ],
            [
                'title' => 'GD',
                'items' => [
                    [
                        'ok' => $gd,
                        'message' => $gd ? 'GD を利用する設定になっています。' : 'GD を利用できません。',
                    ],
                ],
            ],
            [
                'title' => '.htaccess',
                'items' => $htaccessItems,
            ],
            [
                'title' => '.env',
                'items' => [
                    [
                        'ok' => $env,
                        'message' => $env
                            ? 'ルートディレクトリの .env は有効化されています。'
                            : 'ルートディレクトリの .env は有効化されていません（任意）。',
                    ],
                ],
            ],
        ];
    }
}
