<?php

declare(strict_types=1);


namespace App\Console\Commands;

use App\Blog\Services\AttachmentImporter;
use App\Models\Category;
use App\Models\Comment;
use App\Models\Link;
use App\Models\Post;
use App\Models\Setting;
use App\Models\User;
use App\Support\SlugGenerator;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use PDO;
use Spatie\Tags\Tag;

class SablogImport extends Command
{
    protected $signature = 'sablog:import
        {--driver=mysql : 老库驱动（mysql / sqlite，测试可用 sqlite）}
        {--host=127.0.0.1 : 老库主机}
        {--port=3306 : 老库端口}
        {--database= : 老库名（必填）}
        {--username=root : 老库用户名}
        {--password= : 老库密码}
        {--prefix=sablog_ : 老表前缀}
        {--attachments-dir= : 老 attachments 目录（用于同步文件，可选）}
        {--sync-attachments : 同步附件文件到新存储}
        {--convert-markdown : 导入后把 HTML 内容转为 Markdown（[attach=xx] 转成 {{attach:xx}} 令牌，渲染时解析）}
        {--fresh : 清空目标表后重新导入}';

    protected $description = '从 SaBlog-X 老库脚本迁移数据到 laralog';

    private PDO $db;

    private string $prefix;

    private array $report = [];

    public function handle(): int
    {
        $database = $this->option('database');
        if (! $database) {
            $this->error('必须指定 --database 老库名');

            return self::FAILURE;
        }

        $this->prefix = rtrim($this->option('prefix'), '_').'_';

        $this->info("连接老库 {$database} ...");
        $this->connect($database);

        if ($this->option('fresh')) {
            $this->freshTarget();
        }

        $this->importSettings();
        $this->importCategories();
        $this->importUsers();
        $this->importPosts();
        $this->importTags();
        $this->importComments();
        $this->importLinks();
        $this->importAttachments();

        if ($this->option('convert-markdown')) {
            $this->convertToMarkdown();
        }

        $this->syncCounters();

        $this->newLine();
        $this->info('迁移完成，报告：');
        foreach ($this->report as $table => $count) {
            $this->line(sprintf('  %-16s %d 条', $table, $count));
        }

        return self::SUCCESS;
    }

    private function connect(string $database): void
    {
        $driver = $this->option('driver');

        if ($driver === 'sqlite') {
            $this->db = new PDO('sqlite:'.$database, null, null, [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            ]);

            return;
        }

        $dsn = sprintf(
            'mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4',
            $this->option('host'),
            $this->option('port'),
            $database
        );

        $this->db = new PDO($dsn, $this->option('username'), $this->option('password'), [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    }

    private function hasTable(string $table): bool
    {
        try {
            $this->db->query("SELECT COUNT(*) FROM `{$this->prefix}{$table}` LIMIT 1");

            return true;
        } catch (\Throwable) {
            return false;
        }
    }

    private function rows(string $table): array
    {
        return $this->db->query('SELECT * FROM `'.$this->prefix.$table.'`')->fetchAll();
    }

    private function freshTarget(): void
    {
        $this->warn('--fresh：清空目标表...');

        DB::table('media')->delete();
        DB::table('taggables')->delete();
        Tag::where('type', 'post')->delete();
        DB::table('posts')->delete();
        DB::table('categories')->delete();
        DB::table('comments')->delete();
        DB::table('links')->delete();
        Setting::where('key', 'like', 'sablog.%')->delete();
    }

    /**
     * 不迁移的 sablog 表（过时/垃圾化设计，由现代方案取代）：
     *   trackbacks/trackbacklog —— 引用通告，垃圾来源
     *   searchindex —— 搜索缓存，现代 SQL 全文检索足够
     *   sessions —— Laravel 原生会话
     *   statistics —— 实时统计
     *   stylevars —— 老式广告位变量
     *   seccode / 验证码 —— 由 AI 审核 + 蜜罐 + 频控取代
     */
    private function importSettings(): void
    {
        $map = [
            'name' => 'site_name',
            'description' => 'site_description',
            'icp' => 'site_icp',
            'templatename' => 'active_theme',
            'audit_comment' => 'comment_audit',
            'comment_min_len' => 'comment_min_len',
            'comment_max_len' => 'comment_max_len',
            'comment_post_space' => 'comment_post_space',
            'comment_order' => 'comment_order',
            'rss_num' => 'rss_num',
            'title_keywords' => 'seo_default_keywords',
            'meta_keywords' => 'seo_default_keywords',
            'meta_description' => 'seo_default_description',
            'close' => 'site_closed',
            'close_note' => 'site_closed_note',
            'banip_enable' => 'comment_ban_ip_enabled',
            'ban_ip' => 'comment_ban_ip',
        ];

        $count = 0;
        foreach ($this->rows('settings') as $row) {
            $key = $map[$row['title']] ?? 'sablog.'.$row['title'];
            if ($key === 'active_theme' && $row['value'] === 'default') {
                $row['value'] = 'sablog';
            }
            Setting::updateOrCreate(['key' => $key], ['value' => $row['value']]);
            $count++;
        }

        $this->report['settings'] = $count;
    }

    private function importCategories(): void
    {
        $count = 0;
        foreach ($this->rows('categories') as $row) {
            $slug = SlugGenerator::make($row['name'], 'categories', 'slug', ignoreId: (int) $row['cid'], fallback: 'category-'.$row['cid']);
            DB::table('categories')->updateOrInsert(['id' => (int) $row['cid']], [
                'name' => $row['name'],
                'slug' => $slug,
                'display_order' => (int) $row['displayorder'],
                'post_count' => (int) $row['articles'],
                'created_at' => now(),
                'updated_at' => now(),
            ]);
            $count++;
        }

        $this->report['categories'] = $count;
    }

    private function importUsers(): void
    {
        $groupMap = [
            1 => 'admin',
            2 => 'editor',
            3 => 'member',
        ];

        $count = 0;
        foreach ($this->rows('users') as $row) {
            $email = $this->legacyEmail($row['username'], $count);
            DB::table('users')->updateOrInsert(['id' => (int) $row['userid']], [
                'name' => $row['username'],
                'email' => $email,
                'password' => null,
                'legacy_md5' => $row['password'],
                'url' => $row['url'] ?: null,
                'logincount' => (int) $row['logincount'],
                'loginip' => $row['loginip'] ?: null,
                'regip' => $row['regip'] ?: null,
                'logintime' => $row['logintime'] ? date('Y-m-d H:i:s', (int) $row['logintime']) : null,
                'lastpost_at' => $row['lastpost'] ? date('Y-m-d H:i:s', (int) $row['lastpost']) : null,
                'created_at' => ($ts = (int) $row['regdateline']) > 0 ? date('Y-m-d H:i:s', $ts) : now(),
                'updated_at' => now(),
            ]);

            $user = User::find((int) $row['userid']);
            $role = $groupMap[(int) $row['groupid']] ?? 'member';
            if ($user && ! $user->hasRole($role)) {
                $user->assignRole($role);
            }
            $count++;
        }

        $this->report['users'] = $count;
    }

    private function legacyEmail(string $username, int $index): string
    {
        $base = strtolower(preg_replace('/[^a-z0-9._-]/i', '', $username)) ?: 'user';

        return $index === 0 ? $base.'@legacy.local' : $base.'-'.$index.'@legacy.local';
    }

    private function importPosts(): void
    {
        $count = 0;
        foreach ($this->rows('articles') as $row) {
            $slug = SlugGenerator::make($row['title'], 'posts', 'slug', ignoreId: (int) $row['articleid'], fallback: 'post-'.$row['articleid']);
            $status = (int) $row['visible'] === 1 ? 'published' : 'draft';
            $dateline = ($ts = (int) $row['dateline']) > 0 ? date('Y-m-d H:i:s', $ts) : now()->toDateTimeString();

            // 老库存在已删除分类的孤儿文章；目标列可空，置 null
            $categoryId = (int) $row['cid'] ?: null;
            if ($categoryId !== null && ! DB::table('categories')->where('id', $categoryId)->exists()) {
                $categoryId = null;
            }

            DB::table('posts')->updateOrInsert(['id' => (int) $row['articleid']], [
                'category_id' => $categoryId,
                'user_id' => (int) $row['uid'] ?: null,
                'title' => $row['title'],
                'slug' => $slug,
                'excerpt' => $row['description'] ?: null,
                'content' => $row['content'],
                'content_format' => 'html',
                'keywords' => $row['keywords'] ?: null,
                'status' => $status,
                'is_sticky' => (bool) $row['stick'],
                'close_comment' => (bool) $row['closecomment'],
                'read_password' => $row['readpassword'] ?: null,
                'views' => (int) $row['views'],
                'comment_count' => (int) $row['comments'],
                'published_at' => $status === 'published' ? $dateline : null,
                'created_at' => $dateline,
                'updated_at' => $dateline,
            ]);
            $count++;
        }

        $this->report['posts'] = $count;
    }

    private function importTags(): void
    {
        $legacyMap = [];
        $count = 0;
        foreach ($this->rows('tags') as $row) {
            $tagName = trim($row['tag']);
            if (! $tagName) {
                continue;
            }

            $tag = Tag::query()->where('name', $tagName)->where('type', 'post')->first();
            if (! $tag) {
                $tag = new Tag(['name' => $tagName, 'type' => 'post']);
                $tag->slug = SlugGenerator::make($tagName, 'tags', 'slug', fallback: 'tag-'.crc32($tagName));
                $tag->save();
            }

            $legacyMap[(int) $row['tagid']] = $tag->id;

            foreach (explode(',', (string) $row['aids']) as $aid) {
                $post = Post::find((int) trim($aid));
                if ($post && ! $post->tags()->where('tags.id', $tag->id)->exists()) {
                    $post->tags()->attach($tag->id);
                }
            }
            $count++;
        }

        if ($legacyMap) {
            Setting::set('legacy_tags', $legacyMap);
        }

        $this->report['tags'] = $count;
    }

    private function importComments(): void
    {
        $count = 0;
        foreach ($this->rows('comments') as $row) {
            DB::table('comments')->updateOrInsert(['id' => (int) $row['commentid']], [
                'post_id' => (int) $row['articleid'],
                'author_name' => $row['author'] ?: '匿名',
                'author_url' => $row['url'] ?: null,
                'content' => $row['content'],
                'ip' => $row['ipaddress'] ?: null,
                'status' => (int) $row['visible'] === 1 ? 'published' : 'pending',
                'created_at' => ($ts = (int) $row['dateline']) > 0 ? date('Y-m-d H:i:s', $ts) : null,
            ]);
            $count++;
        }

        $this->report['comments'] = $count;
    }

    private function importLinks(): void
    {
        $count = 0;
        foreach ($this->rows('links') as $row) {
            DB::table('links')->updateOrInsert(['id' => (int) $row['linkid']], [
                'name' => $row['name'],
                'url' => $row['url'],
                'note' => $row['note'] ?: null,
                'display_order' => (int) $row['displayorder'],
                'visible' => (bool) $row['visible'],
                'created_at' => now(),
                'updated_at' => now(),
            ]);
            $count++;
        }

        $this->report['links'] = $count;
    }

    private function convertToMarkdown(): void
    {
        $this->info('转换文章内容为 Markdown ...');

        $converter = new \League\HTMLToMarkdown\HtmlConverter([
            'strip_tags' => false,
            'hard_break' => true,
        ]);
        $renderer = app(\App\Blog\Services\PostContentRenderer::class);

        $count = 0;
        foreach (Post::query()->where('content_format', 'html')->cursor() as $post) {
            $post->content = $converter->convert($renderer->toMarkdownSafeTokens($post->content));
            $post->content_format = 'markdown';
            $post->save();
            $count++;
        }

        $this->report['markdown_converted'] = $count;
    }

    private function importAttachments(): void
    {
        if (! $this->hasTable('attachments')) {
            return;
        }

        $importer = app(AttachmentImporter::class);
        $dir = $this->option('attachments-dir');
        $sync = $this->option('sync-attachments');

        $count = 0;
        foreach ($this->rows('attachments') as $row) {
            $importer->create((int) $row['articleid'], $row, $dir ?: null, $sync);
            $count++;
        }

        $this->report['attachments'] = $count;
    }

    private function syncCounters(): void
    {
        // 以导入的真实数据为准重算统计计数
        DB::table('posts')->update(['comment_count' => DB::raw('(SELECT COUNT(*) FROM comments WHERE comments.post_id = posts.id AND comments.status = "published")')]);
        DB::table('categories')->update(['post_count' => DB::raw('(SELECT COUNT(*) FROM posts WHERE posts.category_id = categories.id AND posts.status = "published")')]);
    }
}
