<?php

namespace App\Console\Commands;

use App\Models\Category;
use App\Models\Locations\CityLocale;
use App\Models\Locations\CountryLocale;
use App\Models\Locations\DistrictLocale;
use App\Models\Locations\DivisionLocale;
use App\Models\Locations\Location;
use App\Models\Meeting;
use App\Models\Post;
use App\Models\PostTranslation;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use ZipArchive;

class RestorePosts extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'posts:restore
                            {file : Path to the backup file (ZIP archive or JSON file)}
                            {--profile-id= : Profile ID to assign as post owner (overrides active session)}
                            {--profile-type= : Profile type (User, Organization, Bank, Admin) to assign as post owner}
                            {--dry-run : Show what would be imported without making changes}
                            {--skip-existing : Skip posts with duplicate slugs instead of failing}
                            {--skip-media : Skip media restoration even if backup contains media files}
                            {--select : Interactively select which posts to restore}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Restore posts, post_translations, meetings, and media from a backup file (ZIP or JSON)';

    /**
     * Execute the console command.
     */
    public function handle(): int
    {
        $filePath = $this->argument('file');
        $dryRun = $this->option('dry-run');
        $skipExisting = $this->option('skip-existing');
        $skipMedia = $this->option('skip-media');

        // Validate file exists
        if (!File::exists($filePath)) {
            $this->error("Backup file not found: {$filePath}");
            return Command::FAILURE;
        }

        // Determine file type and extract if necessary
        $isZip = strtolower(pathinfo($filePath, PATHINFO_EXTENSION)) === 'zip';
        $extractDir = null;
        $backupData = null;

        if ($isZip) {
            // Extract ZIP archive
            if (!class_exists('ZipArchive')) {
                $this->error('ZipArchive extension is not available. Install php-zip extension.');
                return Command::FAILURE;
            }

            $extractDir = storage_path('app/temp/restore_' . uniqid());
            File::makeDirectory($extractDir, 0755, true);

            $zip = new ZipArchive();
            if ($zip->open($filePath) !== true) {
                $this->error("Failed to open ZIP archive: {$filePath}");
                File::deleteDirectory($extractDir);
                return Command::FAILURE;
            }

            $zip->extractTo($extractDir);
            $zip->close();

            $this->info("Extracted ZIP archive to temporary directory");

            // Read backup.json from extracted directory
            $jsonPath = "{$extractDir}/backup.json";
            if (!File::exists($jsonPath)) {
                $this->error("Invalid ZIP archive: missing backup.json");
                File::deleteDirectory($extractDir);
                return Command::FAILURE;
            }

            $json = File::get($jsonPath);
        } else {
            // Read JSON file directly
            $json = File::get($filePath);
        }

        $backupData = json_decode($json, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            $this->error("Invalid JSON file: " . json_last_error_msg());
            if ($extractDir) {
                File::deleteDirectory($extractDir);
            }
            return Command::FAILURE;
        }

        // Validate backup format
        if (!isset($backupData['meta']) || !isset($backupData['posts'])) {
            $this->error("Invalid backup file format. Missing 'meta' or 'posts' keys.");
            if ($extractDir) {
                File::deleteDirectory($extractDir);
            }
            return Command::FAILURE;
        }

        $includesMedia = $backupData['meta']['includes_media'] ?? false;
        $mediaCount = $backupData['meta']['counts']['media_files'] ?? 0;

        $this->info("Backup file info:");
        $this->table(
            ['Property', 'Value'],
            [
                ['Version', $backupData['meta']['version'] ?? 'unknown'],
                ['Created', $backupData['meta']['created_at'] ?? 'unknown'],
                ['Source Database', $backupData['meta']['source_database'] ?? 'unknown'],
                ['Posts', $backupData['meta']['counts']['posts'] ?? count($backupData['posts'])],
                ['Translations', $backupData['meta']['counts']['post_translations'] ?? 'unknown'],
                ['Meetings', $backupData['meta']['counts']['meetings'] ?? 'unknown'],
                ['Media Files', $mediaCount],
                ['Includes Media', $includesMedia ? 'Yes' : 'No'],
            ]
        );

        if ($includesMedia && $skipMedia) {
            $this->warn("Media restoration will be skipped (--skip-media flag)");
        }

        // Handle post selection with --select flag
        $postsToRestore = $backupData['posts'];

        if ($this->option('select')) {
            $baseLocale = config('app.locale');

            // Build numbered list for display
            $tableRows = [];
            foreach ($backupData['posts'] as $index => $post) {
                $title = null;
                $locales = [];
                foreach ($post['translations'] ?? [] as $translation) {
                    $locales[] = $translation['locale'];
                    if ($translation['locale'] === $baseLocale) {
                        $title = $translation['title'];
                    }
                }
                if ($title === null && !empty($post['translations'])) {
                    $title = $post['translations'][0]['title'];
                }

                $indicators = [];
                if (!empty($post['meeting'])) $indicators[] = 'meeting';
                if (!empty($post['media'])) $indicators[] = 'media';
                $indicatorStr = $indicators ? ' [' . implode(', ', $indicators) . ']' : '';

                $tableRows[] = [
                    $index + 1,
                    ($title ?? 'Untitled') . $indicatorStr,
                    implode(', ', $locales),
                ];
            }

            $this->newLine();
            $this->info('Available posts:');
            $this->table(['#', 'Title', 'Locales'], $tableRows);

            $this->info("Enter post numbers to restore (comma-separated, ranges with dash, or 'all').");
            $this->info("Examples: 1,3,5  or  1-10  or  1-5,8,12-15  or  all");
            $input = $this->ask('Selection');

            if (strtolower(trim($input)) !== 'all') {
                $selectedIndices = $this->parseSelection($input, count($backupData['posts']));

                if (empty($selectedIndices)) {
                    $this->error('No valid posts selected.');
                    if ($extractDir) {
                        File::deleteDirectory($extractDir);
                    }
                    return Command::FAILURE;
                }

                $postsToRestore = [];
                foreach ($selectedIndices as $idx) {
                    $postsToRestore[] = $backupData['posts'][$idx];
                }

                $this->info("Selected " . count($postsToRestore) . " of " . count($backupData['posts']) . " posts.");
            }
        }

        // Determine profile for post ownership
        $profileId = $this->option('profile-id');
        $profileType = $this->option('profile-type');

        if ($profileId && $profileType) {
            // Use provided profile
            $profileType = $this->resolveProfileType($profileType);
            if (!$profileType) {
                $this->error("Invalid profile type. Use: User, Organization, Bank, or Admin");
                return Command::FAILURE;
            }
        } else {
            // Try to get from session (won't work in CLI, but check anyway)
            $profileId = session('activeProfileId');
            $profileType = session('activeProfileType');

            if (!$profileId || !$profileType) {
                $this->error("No active profile in session. Please provide --profile-id and --profile-type options.");
                $this->info("Example: php artisan posts:restore backup.json --profile-id=1 --profile-type=User");
                return Command::FAILURE;
            }
        }

        // Validate profile exists
        if (!class_exists($profileType)) {
            $this->error("Profile type class not found: {$profileType}");
            if ($extractDir) {
                File::deleteDirectory($extractDir);
            }
            return Command::FAILURE;
        }

        $profile = $profileType::find($profileId);
        if (!$profile) {
            $this->error("Profile not found: {$profileType} with ID {$profileId}");
            if ($extractDir) {
                File::deleteDirectory($extractDir);
            }
            return Command::FAILURE;
        }

        $this->info("Posts will be assigned to: {$profile->name} ({$profileType} #{$profileId})");

        // Build category type => id lookup for the target database
        $categoryLookup = Category::pluck('id', 'type')->toArray();

        if ($dryRun) {
            $this->warn("DRY RUN MODE - No changes will be made");
        }

        if (!$dryRun && !$this->confirm("Do you want to proceed with the restore?")) {
            $this->info("Restore cancelled.");
            if ($extractDir) {
                File::deleteDirectory($extractDir);
            }
            return Command::SUCCESS;
        }

        $stats = [
            'posts_created' => 0,
            'posts_skipped' => 0,
            'posts_overwritten' => 0,
            'translations_created' => 0,
            'meetings_created' => 0,
            'media_restored' => 0,
            'media_skipped' => 0,
            'category_not_found' => 0,
        ];

        // Track "all" choices for duplicate handling
        $skipAll = null;

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

        DB::beginTransaction();

        try {
            foreach ($postsToRestore as $postData) {
                // Look up category_id by category_type
                $categoryId = null;
                if (!empty($postData['category_type'])) {
                    $categoryId = $categoryLookup[$postData['category_type']] ?? null;
                    if ($categoryId === null) {
                        $this->newLine();
                        $this->warn("Category type not found: {$postData['category_type']}");
                        $stats['category_not_found']++;
                    }
                }

                // Check for existing slugs
                if (!empty($postData['translations'])) {
                    $existingSlugs = PostTranslation::withTrashed()
                        ->whereIn('slug', array_column($postData['translations'], 'slug'))
                        ->pluck('slug')
                        ->toArray();

                    if (!empty($existingSlugs)) {
                        $this->newLine();
                        $this->warn("Duplicate slug(s) found: " . implode(', ', $existingSlugs));

                        // Determine action based on flags or prompt
                        $action = $skipAll ?? null;

                        if ($action === null && !$skipExisting) {
                            $action = $this->choice(
                                'What would you like to do?',
                                [
                                    'skip' => 'Skip this post',
                                    'overwrite' => 'Overwrite existing post(s)',
                                    'skip_all' => 'Skip all duplicates',
                                    'overwrite_all' => 'Overwrite all duplicates',
                                ],
                                'skip'
                            );

                            if ($action === 'skip_all') {
                                $skipAll = 'skip';
                                $action = 'skip';
                            } elseif ($action === 'overwrite_all') {
                                $skipAll = 'overwrite';
                                $action = 'overwrite';
                            }
                        } elseif ($skipExisting) {
                            $action = 'skip';
                        }

                        if ($action === 'skip') {
                            $stats['posts_skipped']++;
                            $bar->advance();
                            continue;
                        } elseif ($action === 'overwrite') {
                            // Delete existing translations and their posts if they become empty
                            $existingTranslations = PostTranslation::withTrashed()
                                ->whereIn('slug', $existingSlugs)
                                ->get();

                            foreach ($existingTranslations as $existingTranslation) {
                                $postId = $existingTranslation->post_id;
                                $existingTranslation->forceDelete();

                                // Check if post has no more translations and delete it too
                                $remainingTranslations = PostTranslation::withTrashed()
                                    ->where('post_id', $postId)
                                    ->count();

                                if ($remainingTranslations === 0) {
                                    $existingPost = Post::withTrashed()->find($postId);
                                    if ($existingPost) {
                                        // Delete related meetings first
                                        Meeting::withTrashed()->where('post_id', $postId)->forceDelete();
                                        $existingPost->forceDelete();
                                    }
                                }
                            }
                            $stats['posts_overwritten']++;
                        }
                    }
                }

                if (!$dryRun) {
                    // Create post with profile ownership
                    $post = new Post();
                    $post->postable_id = $profileId;
                    $post->postable_type = $profileType;
                    $post->category_id = $categoryId;
                    // Don't set love_reactant_id - let PostObserver register it as reactant
                    $post->author_id = $postData['author_id'];
                    $post->author_model = $postData['author_model'];
                    $post->created_at = $postData['created_at'] ? new \DateTime($postData['created_at']) : now();
                    $post->updated_at = $postData['updated_at'] ? new \DateTime($postData['updated_at']) : now();
                    $post->save();

                    // Ensure post is registered as reactant (in case observer didn't fire)
                    if (!$post->isRegisteredAsLoveReactant()) {
                        $post->registerAsLoveReactant();
                    }

                    // Create translations
                    foreach ($postData['translations'] as $translationData) {
                        $translation = new PostTranslation();
                        $translation->post_id = $post->id;
                        $translation->locale = $translationData['locale'];
                        $translation->slug = $translationData['slug'];
                        $translation->title = $translationData['title'];
                        $translation->excerpt = $translationData['excerpt'];
                        $translation->content = $translationData['content'];
                        $translation->status = $translationData['status'];
                        $translation->updated_by_user_id = $translationData['updated_by_user_id'];
                        $translation->from = $translationData['from'] ? new \DateTime($translationData['from']) : null;
                        $translation->till = $translationData['till'] ? new \DateTime($translationData['till']) : null;
                        $translation->created_at = $translationData['created_at'] ? new \DateTime($translationData['created_at']) : now();
                        $translation->updated_at = $translationData['updated_at'] ? new \DateTime($translationData['updated_at']) : now();
                        $translation->save();

                        $stats['translations_created']++;
                    }

                    // Create meeting (hasOne relationship)
                    if (!empty($postData['meeting'])) {
                        $meetingData = $postData['meeting'];

                        // Look up meetingable by name and type
                        $meetingableId = null;
                        $meetingableType = null;
                        // Whitelist of allowed meetingable types to prevent arbitrary class instantiation
                        $allowedMeetingableTypes = [
                            \App\Models\User::class,
                            \App\Models\Organization::class,
                            \App\Models\Bank::class,
                        ];
                        if (!empty($meetingData['meetingable_type']) && !empty($meetingData['meetingable_name'])) {
                            $meetingableType = $meetingData['meetingable_type'];
                            if (in_array($meetingableType, $allowedMeetingableTypes, true)) {
                                $meetingable = $meetingableType::where('name', $meetingData['meetingable_name'])->first();
                                if ($meetingable) {
                                    $meetingableId = $meetingable->id;
                                }
                            }
                        }

                        $meeting = new Meeting();
                        $meeting->post_id = $post->id;
                        $meeting->meetingable_id = $meetingableId;
                        $meeting->meetingable_type = $meetingableId ? $meetingableType : null;
                        $meeting->venue = $meetingData['venue'];
                        $meeting->address = $meetingData['address'];
                        $meeting->price = $meetingData['price'];
                        $meeting->based_on_quantity = $meetingData['based_on_quantity'];
                        $meeting->transaction_type_id = $meetingData['transaction_type_id'];
                        $meeting->status = $meetingData['status'];
                        $meeting->from = $meetingData['from'] ? new \DateTime($meetingData['from']) : null;
                        $meeting->till = $meetingData['till'] ? new \DateTime($meetingData['till']) : null;
                        $meeting->created_at = $meetingData['created_at'] ? new \DateTime($meetingData['created_at']) : now();
                        $meeting->updated_at = $meetingData['updated_at'] ? new \DateTime($meetingData['updated_at']) : now();
                        $meeting->save();

                        // Create location if location data exists
                        if (!empty($meetingData['location'])) {
                            $locationIds = $this->lookupLocationIds($meetingData['location']);
                            if ($locationIds['country_id'] || $locationIds['division_id'] || $locationIds['city_id'] || $locationIds['district_id']) {
                                $location = new Location();
                                $location->locatable_id = $meeting->id;
                                $location->locatable_type = Meeting::class;
                                $location->country_id = $locationIds['country_id'];
                                $location->division_id = $locationIds['division_id'];
                                $location->city_id = $locationIds['city_id'];
                                $location->district_id = $locationIds['district_id'];
                                $location->save();
                            }
                        }

                        $stats['meetings_created']++;
                    }

                    // Restore media if available and not skipped
                    if (!$skipMedia && $extractDir && !empty($postData['media'])) {
                        $mediaData = $postData['media'];
                        $mediaPath = "{$extractDir}/{$mediaData['archive_path']}";

                        if (File::exists($mediaPath)) {
                            try {
                                $media = $post->addMedia($mediaPath)
                                    ->usingName($mediaData['name'])
                                    ->usingFileName($mediaData['file_name'])
                                    ->withCustomProperties($mediaData['custom_properties'] ?? [])
                                    ->toMediaCollection('posts');

                                // Dispatch conversion job to queue
                                $conversionCollection = \Spatie\MediaLibrary\Conversions\ConversionCollection::createForMedia($media);
                                if ($conversionCollection->isNotEmpty()) {
                                    dispatch(new \Spatie\MediaLibrary\Conversions\Jobs\PerformConversionsJob($conversionCollection, $media, false))
                                        ->onQueue('low');
                                }

                                $stats['media_restored']++;
                            } catch (\Exception $e) {
                                $this->newLine();
                                $this->warn("Failed to restore media for post {$post->id}: " . $e->getMessage());
                                $stats['media_skipped']++;
                            }
                        } else {
                            $this->newLine();
                            $this->warn("Media file not found in archive: {$mediaData['archive_path']}");
                            $stats['media_skipped']++;
                        }
                    }
                } else {
                    // Dry run - just count
                    $stats['translations_created'] += count($postData['translations']);
                    $stats['meetings_created'] += !empty($postData['meeting']) ? 1 : 0;
                    if (!empty($postData['media'])) {
                        $stats['media_restored']++;
                    }
                }

                $stats['posts_created']++;
                $bar->advance();
            }

            if (!$dryRun) {
                DB::commit();
            }

        } catch (\Exception $e) {
            DB::rollBack();
            $this->newLine();
            $this->error("Restore failed: " . $e->getMessage());
            if ($extractDir) {
                File::deleteDirectory($extractDir);
            }
            return Command::FAILURE;
        }

        // Clean up extracted files
        if ($extractDir) {
            File::deleteDirectory($extractDir);
            $this->info("Cleaned up temporary files");
        }

        $bar->finish();
        $this->newLine(2);

        $this->info($dryRun ? "Dry run completed!" : "Restore completed successfully!");
        $this->table(
            ['Metric', 'Value'],
            [
                ['Posts Created', $stats['posts_created']],
                ['Posts Skipped', $stats['posts_skipped']],
                ['Posts Overwritten', $stats['posts_overwritten']],
                ['Translations Created', $stats['translations_created']],
                ['Meetings Created', $stats['meetings_created']],
                ['Media Restored', $stats['media_restored']],
                ['Media Skipped', $stats['media_skipped']],
                ['Categories Not Found', $stats['category_not_found']],
            ]
        );

        if ($stats['category_not_found'] > 0) {
            $this->warn("Some posts were created without a category. You may need to assign categories manually.");
        }

        return Command::SUCCESS;
    }

    /**
     * Resolve profile type string to full class name.
     */
    private function resolveProfileType(string $type): ?string
    {
        $typeMap = [
            'user' => \App\Models\User::class,
            'organization' => \App\Models\Organization::class,
            'bank' => \App\Models\Bank::class,
            'admin' => \App\Models\Admin::class,
        ];

        $normalized = strtolower(trim($type));

        // Handle full class names - only allow known model classes
        if (str_contains($type, '\\')) {
            return in_array($type, $typeMap, true) ? $type : null;
        }

        return $typeMap[$normalized] ?? null;
    }

    /**
     * Look up location IDs by names in the app's base locale.
     * Returns null for any location component that cannot be found.
     */
    private function lookupLocationIds(array $locationData): array
    {
        $baseLocale = config('app.locale');

        $result = [
            'country_id' => null,
            'division_id' => null,
            'city_id' => null,
            'district_id' => null,
        ];

        // Look up country by name
        if (!empty($locationData['country_name'])) {
            $countryLocale = CountryLocale::withoutGlobalScopes()
                ->where('name', $locationData['country_name'])
                ->where('locale', $baseLocale)
                ->first();
            $result['country_id'] = $countryLocale?->country_id;
        }

        // Look up division by name
        if (!empty($locationData['division_name'])) {
            $divisionLocale = DivisionLocale::withoutGlobalScopes()
                ->where('name', $locationData['division_name'])
                ->where('locale', $baseLocale)
                ->first();
            $result['division_id'] = $divisionLocale?->division_id;
        }

        // Look up city by name
        if (!empty($locationData['city_name'])) {
            $cityLocale = CityLocale::withoutGlobalScopes()
                ->where('name', $locationData['city_name'])
                ->where('locale', $baseLocale)
                ->first();
            $result['city_id'] = $cityLocale?->city_id;
        }

        // Look up district by name
        if (!empty($locationData['district_name'])) {
            $districtLocale = DistrictLocale::withoutGlobalScopes()
                ->where('name', $locationData['district_name'])
                ->where('locale', $baseLocale)
                ->first();
            $result['district_id'] = $districtLocale?->district_id;
        }

        return $result;
    }

    /**
     * Parse a user selection string like "1,3,5-10" into an array of 0-based indices.
     */
    private function parseSelection(string $input, int $total): array
    {
        $indices = [];
        $parts = preg_split('/\s*,\s*/', trim($input));

        foreach ($parts as $part) {
            $part = trim($part);
            if (preg_match('/^(\d+)-(\d+)$/', $part, $matches)) {
                $start = max(1, (int) $matches[1]);
                $end = min($total, (int) $matches[2]);
                for ($i = $start; $i <= $end; $i++) {
                    $indices[] = $i - 1;
                }
            } elseif (preg_match('/^\d+$/', $part)) {
                $num = (int) $part;
                if ($num >= 1 && $num <= $total) {
                    $indices[] = $num - 1;
                }
            }
        }

        return array_values(array_unique($indices));
    }
}
