<?php

namespace App\Services;

use App\Contracts\FaceSwapDriverInterface;
use App\VirtualTryonResult;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use RuntimeException;

class FaceSwapManager
{
    protected FaceSwapDriverInterface $driver;

    public function __construct(FaceSwapDriverInterface $driver)
    {
        $this->driver = $driver;
    }

    /**
     * Private disk for face photos + results.
     */
    public function diskName(): string
    {
        $mode = config('faceswap.storage', 'local');

        if ($mode === 'cloud') {
            if (!config('filesystems.disks.do_spaces_private.key')) {
                throw new RuntimeException(
                    'VIRTUAL_TRYON_STORAGE=cloud requires DO_SPACES_PRIVATE_* env to be set.'
                );
            }
            return 'do_spaces_private';
        }

        // "local" mode (default): server-local private disk. Works everywhere,
        // no cloud bucket required.
        return 'tryon_local';
    }

    /**
     * Execute face swap and store result.
     */
    public function swap(VirtualTryonResult $result): void
    {
        try {
            $fresh = $result->fresh();
            if (!$fresh || $fresh->status === 'cancelled') {
                Log::info('Virtual try-on skipped (cancelled)', ['code' => $result->code]);
                return;
            }

            $result->update(['status' => 'processing']);

            $faceImageData = $this->getFaceImageData($result);
            $productImageData = $this->getProductImageData($result->source_image);

            $resultImageData = $this->driver->process($faceImageData, $productImageData);

            $this->storeResult($result, $resultImageData);

            Log::info('Virtual try-on completed', [
                'result_id' => $result->id,
                'code' => $result->code,
                'driver' => config('faceswap.driver'),
            ]);

        } catch (\Exception $e) {
            $logPayload = [
                'result_id' => $result->id ?? null,
                'code' => $result->code ?? null,
                'error' => $e->getMessage(),
            ];
            if (app()->environment(['local', 'testing'])) {
                $logPayload['trace'] = $e->getTraceAsString();
            }
            Log::error('Virtual try-on failed', $logPayload);

            // Do NOT set status='failed' here — Laravel queue will retry this
            // job up to $tries times (2), and writing 'failed' on the first
            // failed attempt causes the frontend poller to show an error
            // dialog before the retry succeeds. The Job's failed() callback
            // (ProcessVirtualTryon::failed) is the authoritative place to mark
            // the result failed after ALL attempts are exhausted.
            //
            // The status is left as 'processing' (set at the top of swap())
            // so the frontend keeps spinning through the backoff window.

            throw $e;
        }
    }

    /**
     * Store result image on the private disk.
     */
    protected function storeResult(VirtualTryonResult $result, string $imageData): void
    {
        $resultPath = 'virtual-tryon/results/' . $result->code . '.jpg';
        Storage::disk($this->diskName())->put($resultPath, $imageData, 'private');

        $result->update([
            'result_image' => $resultPath,
            'status' => 'completed',
        ]);
    }

    /**
     * Get the user's face photo as raw bytes.
     */
    protected function getFaceImageData(VirtualTryonResult $result): string
    {
        if (!$result->facePhoto) {
            throw new \Exception('Face photo not found');
        }

        $imageData = Storage::disk($this->diskName())->get($result->facePhoto->storage_path);

        if (!$imageData) {
            throw new \Exception('Failed to read face photo from storage');
        }

        return $imageData;
    }

    /**
     * Get the product model image as raw bytes (resized if needed).
     */
    protected function getProductImageData(string $imagePath): string
    {
        $imagePath = basename($imagePath);

        if (!Storage::disk('product_images')->exists($imagePath)) {
            throw new \Exception('Product image not found');
        }

        $imageData = Storage::disk('product_images')->get($imagePath);

        if (function_exists('imagecreatefromstring')) {
            $imageData = $this->resizeIfNeeded($imageData, 1024);
        }

        return $imageData;
    }

    /**
     * Resize image if larger than max dimension.
     */
    protected function resizeIfNeeded(string $imageData, int $maxDimension): string
    {
        try {
            $image = \Intervention\Image\Facades\Image::make($imageData);
        } catch (\Intervention\Image\Exception\NotReadableException $e) {
            throw new \Exception('Invalid product image format');
        }

        if ($image->width() > $maxDimension || $image->height() > $maxDimension) {
            $image->resize($maxDimension, $maxDimension, function ($constraint) {
                $constraint->aspectRatio();
                $constraint->upsize();
            });

            return (string) $image->encode('jpg', 85);
        }

        return $imageData;
    }
}
