<?php

namespace Acms\Services\Setup;

use Acms\Services\Facades\Application;
use Acms\Services\Facades\Database;
use Acms\Services\Setup\Http\Request;
use Acms\Services\Setup\Tool\SetupTool;
use Acms\Services\Setup\Tool\ToolErrors;
use Acms\Services\Setup\Tool\ToolView;
use Acms\Services\SiteSeed\Logger\WebSiteSeedLogger;
use Acms\Services\SiteSeed\SiteSeedException;
use Acms\Services\SiteSeed\SiteSeedOptions;
use Field;
use SQL;

/**
 * ブログインポートツール（import.php）。旧 setup/import.php の Surfacer_Import。
 *
 * setup/bin/<name>/ のエクスポートデータを、指定ブログへ取り込む（既存ブログへ上書き）か、
 * 子ブログとして新規作成する。取り込み本体は site-seed-cli 由来の SiteSeeder（site.seed）へ委譲する。
 * 旧来の Setup\Import ベースのマージ/差分取り込み（config トグル・行頭スペース保持マーカー）は
 * SiteSeeder が持たないため廃止し、full-replace のシードに一本化している。
 */
class ImportController implements SetupTool
{
    public function template(): string
    {
        return '@setup/import.twig';
    }

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

    public function handle(Request $request, SetupSession $session): ToolView
    {
        @set_time_limit(0);

        $post = $request->post();
        $data = $this->viewData($post);

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

        return $this->import($post, $session, $data);
    }

    /**
     * ブログ選択・bin 一覧・再表示入力値。フォームは全画面共通なので常に付与する。
     *
     * @return array<string, mixed>
     */
    private function viewData(Field $post): array
    {
        $SQL = SQL::newSelect('blog');
        $all = Database::query($SQL->get(dsn()), 'all');

        $blogs = [];
        foreach ($all as $row) {
            $blogs[] = [
                'bid' => $row['blog_id'],
                'name' => $row['blog_name'],
                'parent' => $row['blog_parent'],
            ];
        }

        $names = $this->listBinNames();

        return [
            'blogs' => $blogs,
            'names' => $names,
            'hasBin' => $names !== [],
            'currentBid' => (int)$post->get('bid'),
            'currentName' => $post->get('name'),
            'rootDir' => ROOT_DIR,
            'setupDir' => SETUP_DIR,
            'httpRoot' => SetupCommon::fixPathSeparator(HTTP_ROOT),
            'bid' => $post->get('bid'),
            // エラー再表示用の入力値
            'blogName' => $post->get('blog_name'),
            'blogCode' => $post->get('blog_code'),
            'parentChecked' => $post->get('parent') === 'on',
        ];
    }

    /**
     * setup/bin/<name>/ のインポート可能データ名を列挙する。
     *
     * SiteSeeder（site.seed）が持つ SiteSeedPathResolver::listAvailableBins() と同一の走査ロジックの
     * ため、二重実装を避けてそちらへ委譲する。
     *
     * @return list<string>
     */
    private function listBinNames(): array
    {
        /** @var \Acms\Services\SiteSeed\SiteSeeder $seeder */
        $seeder = Application::make('site.seed');

        return $seeder->paths()->listAvailableBins();
    }

    private function validate(Field $post): ToolErrors
    {
        $errors = new ToolErrors();

        // bid は取り込み先（またはその親）ブログ ID。未選択・非正整数は弾く。ここで通すと後段の
        // SiteSeedOptions が targetBlogId<=1 で InvalidArgumentException を投げ、import() の
        // catch(SiteSeedException) を素通りして未捕捉の致命エラーになる。
        if ($post->isNull('bid') || (int)$post->get('bid') < 1) {
            $errors = $errors->with('blog_select');
        }
        if ($post->isNull('name')) {
            $errors = $errors->with('name_select');
        }

        if (!$post->isNull('parent')) {
            if ($post->get('blog_code') === '') {
                $errors = $errors->with('blog_code_input');
            }
            if ($post->get('blog_name') === '') {
                $errors = $errors->with('blog_name_input');
            }
        }

        if (!SetupCommon::checkPermission(PATH_ARCHIVES)) {
            $errors = $errors->with('archives_permission');
        }
        if (!SetupCommon::checkPermission(PATH_THEMES)) {
            $errors = $errors->with('themes_permission');
        }

        /**
         * confliction check of blog_code
         */
        $SQL = SQL::newSelect('blog');
        if (!$post->isNull('parent')) {
            $SQL->addWhereOpr('blog_id', $post->get('bid'));
        } else {
            $SQL->addWhereOpr('blog_id', 0);
        }
        $PARENT = Database::query($SQL->get(dsn()), 'row');

        $SQL = SQL::newSelect('blog');
        $SQL->setSelect('blog_code');
        if (isset($PARENT['blog_domain']) && !!$PARENT['blog_domain']) {
            $SQL->addWhereOpr('blog_domain', $PARENT['blog_domain']);
        }
        $SQL->addWhereOpr('blog_code', $post->get('blog_code'));
        $exe = Database::query($SQL->get(dsn()), 'one');
        if ($exe !== false && $exe !== null && $exe !== '') {
            $errors = $errors->with('blog_code_conflict');
        }

        return $errors;
    }

    /**
     * @param array<string, mixed> $data
     */
    private function import(Field $post, SetupSession $session, array $data): ToolView
    {
        $asChild = !$post->isNull('parent');
        $options = new SiteSeedOptions(
            binName: $post->get('name'),
            asChild: $asChild,
            targetBlogId: (int)$post->get('bid'),
            blogCode: $asChild ? $post->get('blog_code') : null,
            blogName: $asChild ? $post->get('blog_name') : null,
            blogDomain: null,
            userId: $session->userId(),
            resetSchema: false,
        );

        try {
            /** @var \Acms\Services\SiteSeed\SiteSeeder $seeder */
            $seeder = Application::make('site.seed');
            $result = $seeder->seed($options, new WebSiteSeedLogger());
            // 完了画面のリンク（httpRoot/bid/{bid}/）は取り込み先の新しいブログ ID を指す。
            $data['bid'] = (string)$result->blogId;
            return ToolView::done($data);
        } catch (SiteSeedException $e) {
            return ToolView::invalid((new ToolErrors())->with('seed_failed', $e->getMessage()), $data);
        }
    }
}
