<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\DB;
use App\Models\User;
use App\Models\UserProfile;
use Intervention\Image\ImageManagerStatic as Image;
use Carbon\Carbon;

class AvatarsMigrate extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'avatars:migrate
        {--dry-run : Do not write files or update database}
        {--force : Overwrite existing migrated avatars}
        {--remove-legacy : Remove legacy files after successful migration}
        {--path=public/files/usericons : Legacy path to scan}
        {--user-id= : Only migrate a single user by ID}
    ';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Migrate legacy avatars from public/files/usericons to storage/app/public/avatars and generate sizes (WebP)';

    /**
     * Allowed MIME types for source images.
     *
     * @var array
     */
    protected $allowed = [
        'image/jpeg',
        'image/png',
        'image/gif',
        'image/webp',
    ];

    /**
     * Target sizes to generate.
     *
     * @var int[]
     */
    protected $sizes = [32, 40, 64, 80, 96, 128, 256, 512];

    public function handle(): int
    {
        $dry = $this->option('dry-run');
        $force = $this->option('force');
        $removeLegacy = $this->option('remove-legacy');
        $legacyPath = base_path($this->option('path'));
        $userId = $this->option('user-id') ? (int) $this->option('user-id') : null;
        $verbose = $this->output->isVerbose();

        $this->info('Starting avatar migration' . ($dry ? ' (dry-run)' : '') . ($userId ? " for user={$userId}" : ''));

        // Detect processing backend: Intervention preferred, GD fallback
        $useIntervention = class_exists('Intervention\\Image\\ImageManagerStatic');
        if ($useIntervention) {
            Image::configure(['driver' => extension_loaded('imagick') ? 'imagick' : 'gd']);
        }

        $bar = null;

        $query = User::with('profile');
        if ($userId) {
            $query->where('id', $userId);
        }

        $query->chunk(100, function ($users) use ($dry, $force, $removeLegacy, $legacyPath, &$bar, $useIntervention, $verbose) {
            foreach ($users as $user) {
                /** @var UserProfile|null $profile */
                $profile = $user->profile;

                if (!$profile) {
                    continue;
                }

                // Skip if already migrated unless --force
                if (!$force && !empty($profile->avatar_hash)) {
                    $this->line("[skip] user={$user->id} already migrated");
                    continue;
                }

                $source = $this->findLegacyFile($profile, $user->id, $legacyPath, 'legacy');

                //dd($source);
                if (!$source) {
                    if ($verbose) {
                        $this->line("[noop] user={$user->id} no legacy file found");
                    }
                    continue;
                }

                try {
                    $this->line("[proc] user={$user->id} file={$source}");

                    if ($useIntervention) {
                        $img = Image::make($source);
                        $mime = $img->mime();
                    } else {
                        $info = @getimagesize($source);
                        $mime = $info['mime'] ?? null;
                    }

                    if (!in_array($mime, $this->allowed, true)) {
                        $this->line("[reject] user={$user->id} unsupported mime={$mime}");
                        continue;
                    }

                    // Re-encode full original to webp (strip metadata)
                    if ($useIntervention) {
                        $originalBlob = (string) $img->encode('webp', 82);
                    } else {
                        $originalBlob = $this->gdEncodeWebp($source, 82);
                    }

                    // Hybrid hash: deterministic user-id fingerprint + short content fingerprint
                    // idPart = sha1(zero-padded user id), contentPart = first 12 chars of sha1(original webp blob)
                    $idPart = sha1(sprintf('%08d', $user->id));
                    $contentPart = substr(sha1($originalBlob), 0, 12);
                    $hash = sprintf('%s_%s', $idPart, $contentPart);

                    // Precompute storage dir for dry-run and real run
                    $hashPrefix1 = substr($hash, 0, 2);
                    $hashPrefix2 = substr($hash, 2, 2);
                    $dir = "avatars/{$hashPrefix1}/{$hashPrefix2}/{$hash}";

                    // CDN base for public URLs
                    $cdnBase = rtrim((string) config('cdn.avatar_url', 'https://files.skinbase.org'), '/');

                    if ($dry) {
                        $absPathDry = Storage::disk('public')->path("{$dir}/original.webp");
                        $publicUrlDry = sprintf('%s/%s/original.webp?v=%s', $cdnBase, $dir, $hash);
                        $this->line("[dry] user={$user->id} would write avatars for hash={$hash} path={$absPathDry} url={$publicUrlDry}");
                    } else {

                        // Save original.webp
                        Storage::disk('public')->put("{$dir}/original.webp", $originalBlob);

                        // Generate sizes
                        foreach ($this->sizes as $size) {
                            if ($useIntervention) {
                                $thumb = Image::make($source)->fit($size, $size, function ($constraint) {
                                    $constraint->upsize();
                                });

                                $thumbBlob = (string) $thumb->encode('webp', 82);
                            } else {
                                $thumbBlob = $this->gdCreateThumbnailWebp($source, $size, 82);
                            }
                            Storage::disk('public')->put("{$dir}/{$size}.webp", $thumbBlob);
                        }

                        // Update DB
                        $profile->avatar_hash = $hash;
                        $profile->avatar_mime = 'image/webp';
                        $profile->avatar_updated_at = Carbon::now();
                        $profile->save();

                        $absPath = Storage::disk('public')->path("{$dir}/original.webp");
                        $publicUrl = sprintf('%s/%s/original.webp?v=%s', $cdnBase, $dir, $hash);
                        $this->line("[ok] user={$user->id} migrated hash={$hash} path={$absPath} url={$publicUrl}");

                        if ($removeLegacy && !empty($profile->avatar_legacy)) {
                            $legacyFile = base_path("public/files/usericons/{$profile->avatar_legacy}");
                            if (file_exists($legacyFile)) {
                                @unlink($legacyFile);
                                $this->line("[rm] removed legacy file {$legacyFile}");
                            }
                        }
                    }
                } catch (\Exception $e) {
                    $this->error("[error] user={$user->id} {$e->getMessage()}");
                    continue;
                }
            }
        });

        $this->info('Avatar migration complete');

        return 0;
    }

    /**
     * Try to find a legacy avatar file for a user/profile.
     *
     * @param UserProfile $profile
     * @param int $userId
     * @param string $legacyBase
     * @return string|null
     */
    protected function findLegacyFile(UserProfile $profile, int $userId, string $legacyBase, ?string $legacyConnection = null): ?string
    {

        $avatar = DB::connection('legacy')->table('users')->where('user_id', $userId)->value('icon');

        if (!empty($profile->avatar_legacy)) {
            $p = $legacyBase . DIRECTORY_SEPARATOR . $avatar;
            if (file_exists($p)) {
                return $p;
            }
        }


        // 1) If profile->avatar_legacy looks like a filename, try it
        if (!empty($profile->avatar_legacy)) {
            $p = $legacyBase . DIRECTORY_SEPARATOR . $profile->avatar_legacy;
            if (file_exists($p)) {
                return $p;
            }
        }

        // 2) Try files named by user id with common extensions
        $exts = ['png','jpg','jpeg','webp','gif'];
        foreach ($exts as $ext) {
            $p = $legacyBase . DIRECTORY_SEPARATOR . "{$userId}.{$ext}";
            if (file_exists($p)) {
                return $p;
            }
        }

        // 3) Try any file under legacy dir that contains the user id in name
        if (is_dir($legacyBase)) {
            $files = glob($legacyBase . DIRECTORY_SEPARATOR . "*{$userId}*.*");
            if (!empty($files)) {
                return $files[0];
            }
        }

        // 4) Fallback: try legacy database connection (connection name 'legacy')
        // If a legacy DB connection is configured, query `users.icon` for avatar filename.
        try {
            $conn = $legacyConnection ?: (config('database.connections.legacy') ? 'legacy' : null);
            if ($conn) {
                $icon = DB::connection($conn)->table('users')->where('id', $userId)->value('icon');
                if (!empty($icon)) {
                    // If icon looks like an absolute path, use it directly; otherwise resolve under legacy base path
                    $p = $icon;
                    if (!file_exists($p)) {
                        $p = $legacyBase . DIRECTORY_SEPARATOR . ltrim($icon, '\/');
                    }

                    if (file_exists($p)) {
                        if ($this->output->isVerbose()) {
                            $this->line("[legacy-db] user={$userId} icon={$icon} resolved={$p}");
                        }
                        return $p;
                    }
                    if ($this->output->isVerbose()) {
                        $this->line("[legacy-db] user={$userId} icon={$icon} not found at resolved path {$p}");
                    }
                }
            }
        } catch (\Throwable $e) {
            // Non-fatal: just skip legacy DB if query fails or connection missing
        }

        return null;
    }

    /**
     * GD-based encode to WebP binary blob.
     *
     * @param string $path
     * @param int $quality
     * @return string
     */
    protected function gdEncodeWebp(string $path, int $quality = 82): string
    {
        if (!function_exists('imagewebp')) {
            throw new \RuntimeException('GD imagewebp function is not available. Install Intervention Image or enable GD WebP support.');
        }

        $src = $this->gdCreateResource($path);
        if (!$src) {
            throw new \RuntimeException('Unable to read image for GD processing: ' . $path);
        }

        ob_start();
        imagewebp($src, null, $quality);
        $data = ob_get_clean();
        imagedestroy($src);

        return $data;
    }

    /**
     * Create a center-cropped square thumbnail and return WebP binary.
     *
     * @param string $path
     * @param int $size
     * @param int $quality
     * @return string
     */
    protected function gdCreateThumbnailWebp(string $path, int $size, int $quality = 82): string
    {
        if (!function_exists('imagewebp')) {
            throw new \RuntimeException('GD imagewebp function is not available. Install Intervention Image or enable GD WebP support.');
        }

        $src = $this->gdCreateResource($path);
        if (!$src) {
            throw new \RuntimeException('Unable to read image for GD processing: ' . $path);
        }

        $w = imagesx($src);
        $h = imagesy($src);
        $min = min($w, $h);
        $srcX = (int) floor(($w - $min) / 2);
        $srcY = (int) floor(($h - $min) / 2);

        $dst = imagecreatetruecolor($size, $size);
        // preserve transparency
        imagealphablending($dst, false);
        imagesavealpha($dst, true);

        imagecopyresampled($dst, $src, 0, 0, $srcX, $srcY, $size, $size, $min, $min);

        ob_start();
        imagewebp($dst, null, $quality);
        $data = ob_get_clean();

        imagedestroy($src);
        imagedestroy($dst);

        return $data;
    }

    /**
     * Create GD image resource from file path.
     *
     * @param string $path
     * @return resource|false
     */
    protected function gdCreateResource(string $path)
    {
        $info = @getimagesize($path);
        if (!$info) {
            return false;
        }

        $mime = $info['mime'] ?? '';

        switch ($mime) {
            case 'image/jpeg':
                return imagecreatefromjpeg($path);
            case 'image/png':
                return imagecreatefrompng($path);
            case 'image/webp':
                if (function_exists('imagecreatefromwebp')) {
                    return imagecreatefromwebp($path);
                }
                return false;
            case 'image/gif':
                if (function_exists('imagecreatefromgif')) {
                    $res = imagecreatefromgif($path);
                    if (!$res) {
                        return false;
                    }

                    // Ensure returned resource is truecolor (WebP requires truecolor)
                    if (!imageistruecolor($res)) {
                        $w = imagesx($res);
                        $h = imagesy($res);
                        $true = imagecreatetruecolor($w, $h);

                        // Preserve transparency where possible
                        imagealphablending($true, false);
                        imagesavealpha($true, true);

                        // Fill with fully transparent color
                        $transparent = imagecolorallocatealpha($true, 0, 0, 0, 127);
                        imagefilledrectangle($true, 0, 0, $w, $h, $transparent);

                        // If the source has an indexed transparent color, try to preserve it
                        $transIndex = imagecolortransparent($res);
                        if ($transIndex >= 0) {
                            try {
                                $colorTotal = imagecolorstotal($res);
                                if ($transIndex >= 0 && $transIndex < $colorTotal) {
                                    $colors = imagecolorsforindex($res, $transIndex);
                                    if (is_array($colors)) {
                                        $alphaColor = imagecolorallocatealpha($true, $colors['red'], $colors['green'], $colors['blue'], 127);
                                        imagefilledrectangle($true, 0, 0, $w, $h, $alphaColor);
                                    }
                                }
                            } catch (\Throwable $e) {
                                // Non-fatal: skip preserving indexed transparent color
                            }
                        }

                        // Copy pixels
                        imagecopy($true, $res, 0, 0, 0, 0, $w, $h);
                        imagedestroy($res);
                        return $true;
                    }

                    return $res;
                }
                return false;
            default:
                return false;
        }
    }
}

