<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use App\Models\ForumCategory;
use App\Models\User;
use App\Models\ForumThread;
use App\Models\ForumPost;
use Exception;
use App\Services\BbcodeConverter;

class ForumMigrateOld extends Command
{
    protected $signature = 'forum:migrate-old {--dry-run} {--only=} {--limit=} {--chunk=500} {--report} {--repair-orphans}';

    protected $description = 'Migrate legacy forum data from legacy DB into new forum tables';

    protected string $logPath;

    protected ?int $limit = null;

    protected ?int $deletedUserId = null;

    /** @var array<int,int> */
    protected array $missingUserIds = [];

    public function __construct()
    {
        parent::__construct();
        $this->logPath = storage_path('logs/forum_migration.log');
    }

    public function handle(): int
    {
        $this->info('Starting forum migration');
        $this->log('Starting forum migration');

        $dry = $this->option('dry-run');
        $only = $this->option('only');
        $chunk = (int)$this->option('chunk');
        $this->limit = $this->option('limit') !== null ? max(0, (int) $this->option('limit')) : null;

        $only = $only === 'attachments' ? 'gallery' : $only;
        if ($only && !in_array($only, ['categories', 'threads', 'posts', 'gallery', 'repair-orphans'], true)) {
            $this->error('Invalid --only value. Allowed: categories, threads, posts, gallery (or attachments), repair-orphans.');
            return 1;
        }

        if ($chunk < 1) {
            $chunk = 500;
        }

        try {
            if (!$only || $only === 'categories') {
                $this->migrateCategories($dry);
            }

            if (!$only || $only === 'threads') {
                $this->migrateThreads($dry, $chunk);
            }

            if (!$only || $only === 'posts') {
                $this->migratePosts($dry, $chunk);
            }

            if (!$only || $only === 'gallery') {
                $this->migrateGallery($dry, $chunk);
            }

            if ($this->option('repair-orphans') || $only === 'repair-orphans') {
                $this->repairOrphanPosts($dry);
            }

            if ($this->option('report')) {
                $this->generateReport();
            }

            $this->info('Forum migration finished');
            $this->log('Forum migration finished');
            return 0;
        } catch (Exception $e) {
            $this->error('Migration failed: ' . $e->getMessage());
            $this->log('Migration failed: ' . $e->getMessage());
            return 1;
        }
    }

    protected function migrateCategories(bool $dry)
    {
        $this->info('Migrating categories');
        $legacy = DB::connection('legacy');

        $roots = $legacy->table('forum_topics')
            ->select('root_id')
            ->distinct()
            ->where('root_id', '>', 0)
            ->orderBy('root_id')
            ->pluck('root_id');

        if ($this->limit !== null && $this->limit > 0) {
            $roots = $roots->take($this->limit);
        }

        $this->info('Found ' . $roots->count() . ' legacy root ids');

        foreach ($roots as $rootId) {
            $row = $legacy->table('forum_topics')->where('topic_id', $rootId)->first();
            $name = $row->topic ?? 'Category ' . $rootId;
            $slug = Str::slug(substr($name, 0, 150));

            $this->line("-> root {$rootId}: {$name}");

            if ($dry) {
                $this->log("[dry] create category {$name} ({$slug})");
                continue;
            }

            DB::transaction(function () use ($rootId, $name, $slug) {
                ForumCategory::updateOrCreate(
                    ['id' => $rootId],
                    ['name' => $name, 'slug' => $slug]
                );
            }, 3);
        }

        $this->info('Categories migrated');
    }

    protected function migrateThreads(bool $dry, int $chunk)
    {
        $this->info('Migrating threads');
        $legacy = DB::connection('legacy');

        $query = $legacy->table('forum_topics')->orderBy('topic_id');

        $total = $query->count();
        if ($this->limit !== null && $this->limit > 0) {
            $total = min($total, $this->limit);
        }
        $this->info("Total threads to process: {$total}");

        $bar = $this->output->createProgressBar($total);
        $bar->start();

        $processed = 0;
        $limit = $this->limit;

        // chunk by legacy primary key `topic_id`
        $query->chunkById($chunk, function ($rows) use ($dry, $bar, &$processed, $limit) {
            foreach ($rows as $r) {
                if ($limit !== null && $limit > 0 && $processed >= $limit) {
                    return false;
                }

                $bar->advance();
                $processed++;

                $data = [
                    'id' => $r->topic_id,
                    'category_id' => $this->resolveCategoryId($r->root_id ?? null, $r->topic_id),
                    // resolve user id or assign to system user (1) when missing or not found
                    'user_id' => $this->resolveUserId($r->user_id ?? null),
                    'title' => $r->topic,
                    'slug' => $this->uniqueSlug(Str::slug(substr($r->topic,0,200)) ?: 'thread-'.$r->topic_id, $r->topic_id),
                    'content' => $r->preview ?? '',
                    'views' => $r->views ?? 0,
                    'is_locked' => isset($r->open) ? !((bool)$r->open) : false,
                    'is_pinned' => false,
                    'visibility' => $this->mapPrivilegeToVisibility($r->privilege ?? 0),
                    'last_post_at' => $this->normalizeDate($r->last_update ?? null),
                ];

                if ($dry) {
                    $this->log('[dry] thread: ' . $r->topic_id . ' - ' . $r->topic);
                    continue;
                }

                DB::transaction(function () use ($data) {
                    ForumThread::updateOrCreate(['id' => $data['id']], $data);
                }, 3);
            }
        }, 'topic_id');

        $bar->finish();
        $this->line('');
        $this->info('Threads migrated');
    }

    protected function migratePosts(bool $dry, int $chunk)
    {
        $this->info('Migrating posts');
        $legacy = DB::connection('legacy');

        $query = $legacy->table('forum_posts')->orderBy('post_id');
        $total = $query->count();
        if ($this->limit !== null && $this->limit > 0) {
            $total = min($total, $this->limit);
        }
        $this->info("Total posts to process: {$total}");

        $bar = $this->output->createProgressBar($total);
        $bar->start();

        $processed = 0;
        $limit = $this->limit;

        // legacy forum_posts uses `post_id` as primary key
        $query->chunkById($chunk, function ($rows) use ($dry, $bar, &$processed, $limit) {
            foreach ($rows as $r) {
                if ($limit !== null && $limit > 0 && $processed >= $limit) {
                    return false;
                }

                $bar->advance();
                $processed++;

                $data = [
                    'id' => $r->post_id,
                    'thread_id' => $r->topic_id,
                    'user_id' => $r->user_id ?? null,
                    'content' => $this->convertLegacyMessage($r->message ?? ''),
                    'is_edited' => isset($r->isupdated) ? (bool)$r->isupdated : false,
                    'edited_at' => $r->updated ?? null,
                ];

                if ($dry) {
                    $this->log('[dry] post: ' . $r->post_id);
                    continue;
                }

                DB::transaction(function () use ($data) {
                    ForumPost::updateOrCreate(['id' => $data['id']], $data);
                }, 3);
            }
        }, 'post_id');

        $bar->finish();
        $this->line('');
        $this->info('Posts migrated');
    }

    protected function mapPrivilegeToVisibility($priv)
    {
        // legacy privilege: 0 public, 1 members, 4 staff? adjust mapping conservatively
        if ($priv >= 4) return 'staff';
        if ($priv >= 1) return 'members';
        return 'public';
    }

    protected function normalizeDate($val)
    {
        if (empty($val)) return null;
        $s = trim((string)$val);
        // legacy sometimes contains sentinel invalid dates like -0001-11-30 or zero dates
        if (strpos($s, '-0001') !== false) return null;
        if (strpos($s, '0000-00-00') !== false) return null;
        if (strtotime($s) === false) return null;
        return date('Y-m-d H:i:s', strtotime($s));
    }

    protected function uniqueSlug(string $base, int $id)
    {
        $slug = $base;
        $i = 0;
        while (ForumThread::where('slug', $slug)->where('id', '<>', $id)->exists()) {
            $i++;
            $slug = $base . '-' . $id;
            // if somehow still exists, append counter
            if (ForumThread::where('slug', $slug)->where('id', '<>', $id)->exists()) {
                $slug = $base . '-' . $id . '-' . $i;
            }
        }
        return $slug;
    }

    protected function resolveCategoryId($rootId, $topicId)
    {
        // prefer explicit rootId
        if (!empty($rootId)) {
            // ensure category exists
            if (ForumCategory::where('id', $rootId)->exists()) return $rootId;
        }

        // if this topic itself is a category
        if (ForumCategory::where('id', $topicId)->exists()) return $topicId;

        // fallback: use first available category
        $first = ForumCategory::first();
        if ($first) return $first->id;

        // as last resort, create Uncategorized
        $cat = ForumCategory::create(['name' => 'Uncategorized', 'slug' => 'uncategorized']);
        return $cat->id;
    }

    protected function resolveUserId($userId)
    {
        if (empty($userId)) {
            return $this->resolveDeletedUserId();
        }

        // check users table in default connection
        if (DB::table('users')->where('id', $userId)->exists()) {
            return $userId;
        }

        $uid = (int) $userId;
        if ($uid > 0 && !in_array($uid, $this->missingUserIds, true)) {
            $this->missingUserIds[] = $uid;
        }

        return $this->resolveDeletedUserId();
    }

    protected function resolveDeletedUserId(): int
    {
        if ($this->deletedUserId !== null) {
            return $this->deletedUserId;
        }

        $userOne = User::query()->find(1);
        if ($userOne) {
            $this->deletedUserId = 1;
            return $this->deletedUserId;
        }

        $fallback = User::query()->orderBy('id')->first();
        if ($fallback) {
            $this->deletedUserId = (int) $fallback->id;
            return $this->deletedUserId;
        }

        $created = User::query()->create([
            'name' => 'Deleted User',
            'email' => 'deleted-user+forum@skinbase.local',
            'password' => Hash::make(Str::random(64)),
            'role' => 'user',
        ]);

        $this->deletedUserId = (int) $created->id;

        return $this->deletedUserId;
    }

    protected function convertLegacyMessage($msg)
    {
        $converter = new BbcodeConverter();
        return $converter->convert($msg);
    }

    protected function repairOrphanPosts(bool $dry): void
    {
        $this->info('Repairing orphan posts');

        $orphansQuery = ForumPost::query()->whereDoesntHave('thread')->orderBy('id');
        $orphanCount = (clone $orphansQuery)->count();

        if ($orphanCount === 0) {
            $this->info('No orphan posts found.');
            return;
        }

        $this->warn("Found {$orphanCount} orphan posts.");

        $repairThread = $this->resolveOrCreateOrphanRepairThread($dry);

        if ($repairThread === null) {
            $this->warn('Unable to resolve/create repair thread in dry-run mode. Reporting only.');
            (clone $orphansQuery)->limit(20)->get(['id', 'thread_id', 'user_id', 'created_at'])->each(function (ForumPost $post): void {
                $this->line("- orphan post id={$post->id} thread_id={$post->thread_id} user_id={$post->user_id}");
            });
            return;
        }

        $this->line("Repair target thread: {$repairThread->id} ({$repairThread->slug})");

        if ($dry) {
            $this->info("[dry] Would reassign {$orphanCount} orphan posts to thread {$repairThread->id}");
            (clone $orphansQuery)->limit(20)->get(['id', 'thread_id', 'user_id', 'created_at'])->each(function (ForumPost $post) use ($repairThread): void {
                $this->log("[dry] orphan post {$post->id}: {$post->thread_id} -> {$repairThread->id}");
            });
            return;
        }

        $updated = 0;

        (clone $orphansQuery)->chunkById(500, function ($posts) use ($repairThread, &$updated): void {
            DB::transaction(function () use ($posts, $repairThread, &$updated): void {
                /** @var ForumPost $post */
                foreach ($posts as $post) {
                    $post->thread_id = $repairThread->id;
                    $post->is_edited = true;
                    $post->edited_at = $post->edited_at ?: now();
                    $post->save();
                    $updated++;
                }
            }, 3);
        }, 'id');

        $latestPostAt = ForumPost::query()
            ->where('thread_id', $repairThread->id)
            ->max('created_at');

        if ($latestPostAt) {
            $repairThread->last_post_at = $latestPostAt;
            $repairThread->save();
        }

        $this->info("Repaired orphan posts: {$updated}");
        $this->log("Repaired orphan posts: {$updated} -> thread {$repairThread->id}");
    }

    protected function resolveOrCreateOrphanRepairThread(bool $dry): ?ForumThread
    {
        $slug = 'migration-orphaned-posts';

        $existing = ForumThread::query()->where('slug', $slug)->first();
        if ($existing) {
            return $existing;
        }

        $category = ForumCategory::query()->ordered()->first();

        if (!$category && !$dry) {
            $category = ForumCategory::query()->create([
                'name' => 'Migration Repairs',
                'slug' => 'migration-repairs',
                'parent_id' => null,
                'position' => 9999,
            ]);
        }

        if (!$category) {
            return null;
        }

        if ($dry) {
            return new ForumThread([
                'id' => 0,
                'slug' => $slug,
                'category_id' => $category->id,
            ]);
        }

        return ForumThread::query()->create([
            'category_id' => $category->id,
            'user_id' => $this->resolveDeletedUserId(),
            'title' => 'Migration: Orphaned Posts Recovery',
            'slug' => $slug,
            'content' => 'Automatic recovery thread for legacy posts whose source thread no longer exists after migration.',
            'views' => 0,
            'is_locked' => false,
            'is_pinned' => false,
            'visibility' => 'staff',
            'last_post_at' => now(),
        ]);
    }

    protected function generateReport()
    {
        $this->info('Generating migration report');
        $legacy = DB::connection('legacy');

        $legacyCounts = [
            'categories' => $legacy->table('forum_topics')->where('root_id','>',0)->distinct('root_id')->count('root_id'),
            'threads' => $legacy->table('forum_topics')->count(),
            'posts' => $legacy->table('forum_posts')->count(),
        ];

        $newCounts = [
            'categories' => ForumCategory::count(),
            'threads' => ForumThread::count(),
            'posts' => ForumPost::count(),
            'attachments' => DB::table('forum_attachments')->count(),
        ];

        $orphans = ForumPost::query()
            ->whereDoesntHave('thread')
            ->count();

        $legacyThreadsWithLastUpdate = $legacy->table('forum_topics')->whereNotNull('last_update')->count();
        $newThreadsWithLastPost = ForumThread::query()->whereNotNull('last_post_at')->count();
        $legacyPostsWithPostDate = $legacy->table('forum_posts')->whereNotNull('post_date')->count();
        $newPostsWithCreatedAt = ForumPost::query()->whereNotNull('created_at')->count();

        $report = [
            'missing_users_count' => count($this->missingUserIds),
            'missing_users' => $this->missingUserIds,
            'orphan_posts' => $orphans,
            'timestamp_mismatches' => [
                'threads_last_post_gap' => max(0, $legacyThreadsWithLastUpdate - $newThreadsWithLastPost),
                'posts_created_at_gap' => max(0, $legacyPostsWithPostDate - $newPostsWithCreatedAt),
            ],
        ];

        $this->info('Legacy counts: ' . json_encode($legacyCounts));
        $this->info('New counts: ' . json_encode($newCounts));
        $this->info('Report: ' . json_encode($report));
        $this->log('Report: legacy=' . json_encode($legacyCounts) . ' new=' . json_encode($newCounts) . ' extra=' . json_encode($report));
    }

    protected function log(string $msg)
    {
        $line = '[' . date('c') . '] ' . $msg . "\n";
        file_put_contents($this->logPath, $line, FILE_APPEND | LOCK_EX);
    }

    protected function migrateGallery(bool $dry, int $chunk)
    {
        $this->info('Migrating gallery (forum_topics_gallery → forum_attachments)');
        $legacy = DB::connection('legacy');

        if (!$legacy->getSchemaBuilder()->hasTable('forum_topics_gallery')) {
            $this->info('No legacy forum_topics_gallery table found, skipping');
            return;
        }

        $query = $legacy->table('forum_topics_gallery')->orderBy('id');
        $total = $query->count();
        if ($this->limit !== null && $this->limit > 0) {
            $total = min($total, $this->limit);
        }
        $this->info("Total gallery items to process: {$total}");

        $bar = $this->output->createProgressBar($total);
        $bar->start();

        $processed = 0;
        $limit = $this->limit;

        $query->chunkById($chunk, function ($rows) use ($dry, $bar, &$processed, $limit) {
            foreach ($rows as $r) {
                if ($limit !== null && $limit > 0 && $processed >= $limit) {
                    return false;
                }

                $bar->advance();
                $processed++;

                // expected legacy fields: id, name, category (topic id), folder, datum, description
                $topicId = $r->category ?? ($r->topic_id ?? null);
                $fileName = $r->name ?? null;
                if (empty($topicId) || empty($fileName)) {
                    $this->log('Skipping gallery row with missing topic or name: ' . json_encode($r));
                    continue;
                }

                $nid = floor($topicId / 100);
                $relativePath = "files/news/{$nid}/{$topicId}/{$fileName}";
                $publicPath = public_path($relativePath);

                $fileSize = null;
                $mimeType = null;
                $width = null;
                $height = null;

                if (file_exists($publicPath)) {
                    $fileSize = filesize($publicPath);
                    $img = @getimagesize($publicPath);
                    if ($img !== false) {
                        $width = $img[0];
                        $height = $img[1];
                        $mimeType = $img['mime'] ?? null;
                    } else {
                        $finfo = finfo_open(FILEINFO_MIME_TYPE);
                        $mimeType = finfo_file($finfo, $publicPath);
                        finfo_close($finfo);
                    }
                }

                // find legacy first post id for this topic
                $legacy = DB::connection('legacy');
                $firstPostId = $legacy->table('forum_posts')
                    ->where('topic_id', $topicId)
                    ->orderBy('post_date')
                    ->value('post_id');

                // map to new forum_posts id (we preserved ids when migrating)
                $postId = null;
                if ($firstPostId && \App\Models\ForumPost::where('id', $firstPostId)->exists()) {
                    $postId = $firstPostId;
                } else {
                    // fallback: find any post in new DB for thread
                    $post = \App\Models\ForumPost::where('thread_id', $topicId)->orderBy('created_at')->first();
                    if ($post) $postId = $post->id;
                }

                if (empty($postId)) {
                    $this->log('No target post found for gallery item: topic ' . $topicId . ' file ' . $fileName);
                    continue;
                }

                if ($dry) {
                    $this->log("[dry] attach {$relativePath} -> post {$postId}");
                    continue;
                }

                DB::transaction(function () use ($postId, $relativePath, $fileSize, $mimeType, $width, $height) {
                    \App\Models\ForumAttachment::query()->updateOrCreate(
                        [
                            'post_id' => $postId,
                            'file_path' => $relativePath,
                        ],
                        [
                            'file_size' => $fileSize ?? 0,
                            'mime_type' => $mimeType,
                            'width' => $width,
                            'height' => $height,
                            'updated_at' => now(),
                        ]
                    );
                }, 3);
            }
        }, 'id');

        $bar->finish();
        $this->line('');
        $this->info('Gallery migrated');
    }
}
