<?php

namespace App\Console\Commands;

use App\Models\Video;
use App\Models\VideoAudioTrack;
use App\Models\VideoSlide;
use App\Services\NasSyncService;
use Illuminate\Console\Command;

/**
 * One-time migration that moves existing songs/videos from the legacy flat
 * layout into the canonical type-segregated, per-track-folder layout described
 * in CLAUDE.md.
 *
 * Dry-run by default — pass --force to actually move files and update the DB.
 * Run this only after backing up the DB and confirming the dry-run plan looks
 * correct. The command is idempotent: rows already in the new layout are
 * skipped.
 */
class MigrateStorageLayout extends Command
{
    protected $signature   = 'storage:migrate-layout {--force : Apply changes (default is dry-run)} {--video= : Migrate only this video id}';
    protected $description = 'Move existing songs/videos to the type-segregated + per-track-folder layout';

    public function handle(NasSyncService $nas): int
    {
        $apply = (bool) $this->option('force');
        $only  = $this->option('video') ? (int) $this->option('video') : null;

        $this->info($apply ? '→ APPLY mode (files and DB will be modified)' : '→ DRY-RUN (no changes will be made)');

        $q = Video::query()->with(['user', 'audioTracks', 'slides']);
        if ($only) $q->where('id', $only);
        $videos = $q->orderBy('id')->get();

        $this->info("Found {$videos->count()} video(s) to inspect.");

        $stats = ['skipped' => 0, 'planned' => 0, 'applied' => 0, 'errors' => 0];

        foreach ($videos as $video) {
            try {
                $plan = $this->planVideo($video, $nas);
                if ($plan === null) { $stats['skipped']++; continue; }

                $this->line("");
                $this->info("Video #{$video->id} ({$video->type}): {$video->title}");
                foreach ($plan as $move) {
                    $this->line("  {$move['from']}  →  {$move['to']}");
                }
                $stats['planned']++;

                if ($apply) {
                    $this->applyPlan($video, $plan, $nas);
                    $stats['applied']++;
                }
            } catch (\Throwable $e) {
                $stats['errors']++;
                $this->error("Video #{$video->id}: " . $e->getMessage());
            }
        }

        $this->line("");
        $this->info("Done. " . json_encode($stats));
        return self::SUCCESS;
    }

    /**
     * Build the move list for one video. Returns null if it's already in the
     * target layout. Each plan entry is ['from' => oldPath, 'to' => newPath,
     * 'kind' => 'video'|'audio'|'slide'|'thumb', 'model' => modelOrNull,
     * 'slide_field' => 'filename' | 'path' | 'thumbnail'].
     */
    private function planVideo(Video $video, NasSyncService $nas): ?array
    {
        $path = (string) $video->path;
        if (! str_starts_with($path, 'users/')) {
            return null; // unorganised — outside this migration's scope
        }
        $segs = explode('/', $path);
        if (count($segs) < 4) return null;

        $expectedTypeFolder = $nas->typeFolder($video);
        $currentTypeFolder  = $segs[2] ?? null;
        $isMusic            = ($video->type === 'music');

        // Already in target layout? Music: tracks/{lang-id}/audio.{ext}. Others: video.{ext}.
        $inTracks      = ($segs[4] ?? null) === 'tracks';
        $canonicalPrimary = $isMusic ? 'audio' : 'video';
        $endsCanonical = preg_match("#/{$canonicalPrimary}\\.[a-z0-9]+$#i", $path) === 1;
        if ($currentTypeFolder === $expectedTypeFolder
            && (! $isMusic || $inTracks)
            && $endsCanonical) {
            return null; // already migrated
        }

        // Compute new song/video root: users/{slug}/{type-folder}/{slug}
        $userSlug  = $segs[1];
        $videoSlug = $isMusic ? ($segs[3] ?? null) : ($segs[3] ?? null);
        if (! $videoSlug) return null;
        $newRoot = "users/{$userSlug}/{$expectedTypeFolder}/{$videoSlug}";

        $ext = pathinfo($video->filename ?: $path, PATHINFO_EXTENSION) ?: 'mp4';

        $plan = [];

        // Primary file
        if ($isMusic) {
            $primaryFolder = $nas->trackFolderName($video, null);
            $plan[] = [
                'from'        => $path,
                'to'          => "{$newRoot}/tracks/{$primaryFolder}/audio.{$ext}",
                'kind'        => 'video',
                'model'       => $video,
                'set'         => ['path', 'filename'],
                'new_filename'=> "audio.{$ext}",
            ];
        } else {
            $plan[] = [
                'from'        => $path,
                'to'          => "{$newRoot}/video.{$ext}",
                'kind'        => 'video',
                'model'       => $video,
                'set'         => ['path', 'filename'],
                'new_filename'=> "video.{$ext}",
            ];
        }

        // Thumbnail
        if ($video->thumbnail && str_starts_with($video->thumbnail, 'users/')) {
            $thumbExt = pathinfo($video->thumbnail, PATHINFO_EXTENSION) ?: 'webp';
            $thumbDir = $isMusic
                ? "{$newRoot}/tracks/" . $nas->trackFolderName($video, null)
                : $newRoot;
            $plan[] = [
                'from'  => $video->thumbnail,
                'to'    => "{$thumbDir}/thumb.{$thumbExt}",
                'kind'  => 'thumb',
                'model' => $video,
                'set'   => ['thumbnail'],
            ];
        }

        // Extra audio tracks (music only)
        if ($isMusic) {
            foreach ($video->audioTracks as $track) {
                if (! str_starts_with((string) $track->path, 'users/')) continue;
                $tExt = pathinfo($track->filename ?: $track->path, PATHINFO_EXTENSION) ?: 'mp3';
                $trackFolder = $nas->trackFolderName($video, $track);
                $plan[] = [
                    'from'        => $track->path,
                    'to'          => "{$newRoot}/tracks/{$trackFolder}/audio.{$tExt}",
                    'kind'        => 'audio',
                    'model'       => $track,
                    'set'         => ['path', 'filename'],
                    'new_filename'=> "audio.{$tExt}",
                ];
            }

            // Slides (music only) — owners may be NULL (primary), or any track id
            foreach ($video->slides as $slide) {
                if (! str_starts_with((string) $slide->filename, 'users/')) continue;
                $sExt = pathinfo($slide->filename, PATHINFO_EXTENSION) ?: 'jpg';
                $ownerTrack = $slide->audio_track_id
                    ? $video->audioTracks->firstWhere('id', $slide->audio_track_id)
                    : null;
                $folder = $nas->trackFolderName($video, $ownerTrack);
                $plan[] = [
                    'from'  => $slide->filename,
                    'to'    => "{$newRoot}/tracks/{$folder}/slides/{$slide->position}.{$sExt}",
                    'kind'  => 'slide',
                    'model' => $slide,
                    'set'   => ['filename'],
                ];
            }
        }

        return $plan;
    }

    /**
     * Execute one video's plan. On NAS: copy then delete (smbclient has no rename).
     * On local: rename(). Either way, DB columns are updated after the move succeeds.
     */
    private function applyPlan(Video $video, array $plan, NasSyncService $nas): void
    {
        $nasOn = $nas->isEnabled();

        foreach ($plan as $move) {
            // Ensure target dir exists
            $targetDir = dirname($move['to']);
            if ($nasOn) {
                $nas->mkdirp($targetDir);
            }
            @mkdir(storage_path('app/' . $targetDir), 0755, true);

            // Move on local
            $localFrom = storage_path('app/' . $move['from']);
            $localTo   = storage_path('app/' . $move['to']);
            if (is_file($localFrom)) {
                @rename($localFrom, $localTo);
            }

            // Move on NAS via copy+delete
            if ($nasOn) {
                $tmp = tempnam(sys_get_temp_dir(), 'mig_');
                if ($nas->getContent($move['from']) !== null) {
                    // small file like .json — already cached as content; round-trip
                }
                // For binary files, pull → push → delete-source
                $localCache = storage_path('app/' . $move['from']);
                if (! is_file($localCache)) {
                    $nas->ensureLocalAsset($localCache, $move['from']);
                }
                if (is_file($localCache)) {
                    if ($nas->putFile($localCache, $move['to'])) {
                        $nas->deleteFile($move['from']);
                    }
                }
                @unlink($tmp);
            }

            // Update DB
            $model = $move['model'];
            $updates = [];
            foreach ($move['set'] as $col) {
                if ($col === 'path' || $col === 'thumbnail' || $col === 'filename') {
                    $updates[$col] = ($col === 'filename' && isset($move['new_filename']))
                        ? $move['new_filename']
                        : $move['to'];
                }
            }
            // VideoSlide stores the full path under `filename`
            if ($model instanceof VideoSlide) {
                $model->update(['filename' => $move['to']]);
            } else {
                $model->update($updates);
            }
        }
    }
}
