<?php

namespace App\Services;

use App\VirtualTryonResult;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use RuntimeException;

class SegmindFaceSwapService
{
    /**
     * Private disk for face photos + results.
     *
     * Uses do_spaces_private when cloud is configured; otherwise local
     * (only allowed in local/testing env — throws in production).
     */
    public function diskName(): string
    {
        if (config('filesystems.disks.do_spaces_private.key')) {
            return 'do_spaces_private';
        }

        if (app()->environment(['local', 'testing'])) {
            return 'tryon_local';
        }

        throw new RuntimeException(
            'Private storage for virtual try-on is not configured. Set DO_SPACES_PRIVATE_* (or DO_SPACES_*) in .env.'
        );
    }

    /**
     * Check if Segmind API is configured.
     */
    protected function isApiConfigured(): bool
    {
        return !empty(config('faceswap.segmind_api_key'));
    }

    /**
     * Execute face swap and store result.
     * Uses Segmind API when configured, mock mode otherwise.
     */
    public function swap(VirtualTryonResult $result)
    {
        try {
            // Skip if the user cancelled between dispatch and handle.
            $fresh = $result->fresh();
            if (!$fresh || $fresh->status === 'cancelled') {
                Log::info('Virtual try-on skipped (cancelled)', ['code' => $result->code]);
                return;
            }

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

            if ($this->isApiConfigured()) {
                $this->swapWithSegmind($result);
            } else {
                $this->swapMock($result);
            }

            Log::info('Virtual try-on completed', [
                'result_id' => $result->id,
                'code' => $result->code,
                'mode' => $this->isApiConfigured() ? 'segmind' : 'mock',
            ]);

        } catch (\Exception $e) {
            // Log the message only — full stack traces may contain payload/API
            // headers. Trace is only emitted in local/testing for debugging.
            $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);

            $result->update([
                'status' => 'failed',
                'error' => 'Processing failed',
            ]);

            throw $e;
        }
    }

    /**
     * Real Segmind API call.
     */
    protected function swapWithSegmind(VirtualTryonResult $result): void
    {
        $faceImageBase64 = $this->getFaceImageBase64($result);
        $productImageBase64 = $this->getProductImageBase64($result->source_image);

        $response = Http::timeout(config('faceswap.segmind_timeout'))
            ->withHeaders([
                'x-api-key' => config('faceswap.segmind_api_key'),
                'Content-Type' => 'application/json',
            ])
            ->post(config('faceswap.segmind_api_url'), [
                // Segmind faceswap-v4 公式パラメータ名（旧 source_img/target_img は v3 系）
                'source_image' => $faceImageBase64,
                'target_image' => $productImageBase64,
                // 30 ステップの高品質モード（既定の speed=8 ステップだと口元など細部の精度が落ちる）
                'model_type' => config('faceswap.segmind_model_type', 'quality'),
                // 顔のスタイル（表情）はソース画像を尊重して維持する
                'style_type' => config('faceswap.segmind_style_type', 'normal'),
                // head: 顔まわり全体を置換（face のみだと境界が目立つことがある）
                'swap_type' => config('faceswap.segmind_swap_type', 'head'),
                'image_format' => 'jpeg',
                'image_quality' => (int) config('faceswap.segmind_image_quality', 95),
                'base64' => true,
            ]);

        if (!$response->successful()) {
            throw new \Exception('Segmind API error: HTTP ' . $response->status());
        }

        $body = $response->body();
        $contentType = strtolower((string) $response->header('Content-Type'));

        if (str_contains($contentType, 'application/json')) {
            $decoded = json_decode($body, true);
            // faceswap-v4 のレスポンスキーは現状 'image' を想定しているが、Segmind 側の
            // バージョン違いで output / data / image_url 等が返るケースに備えてフォールバック
            $imageBase64 = null;
            if (is_array($decoded)) {
                foreach (['image', 'output', 'data', 'image_base64'] as $key) {
                    if (!empty($decoded[$key]) && is_string($decoded[$key])) {
                        $imageBase64 = $decoded[$key];
                        break;
                    }
                }
            }
            if ($imageBase64 === null) {
                // デバッグ用にレスポンス先頭を切り出してログ出力
                throw new \Exception('Segmind API returned unexpected JSON payload: ' . substr($body, 0, 200));
            }
            $imageData = base64_decode($imageBase64);
        } else {
            $imageData = $body;
        }

        $this->storeResult($result, $imageData);
    }

    /**
     * Mock mode: use product image as result (for local development/demo).
     */
    protected function swapMock(VirtualTryonResult $result): void
    {
        Log::info('Virtual try-on mock mode (SEGMIND_API_KEY not set)', ['code' => $result->code]);

        // Simulate processing delay
        sleep(2);

        $imagePath = basename($result->source_image);

        if (Storage::disk('product_images')->exists($imagePath)) {
            $imageData = Storage::disk('product_images')->get($imagePath);
        } else {
            $imageData = $this->generatePlaceholderImage($result);
        }

        $this->storeResult($result, $imageData);
    }

    /**
     * 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',
        ]);
    }

    /**
     * Generate a placeholder image when no product image available.
     */
    protected function generatePlaceholderImage(VirtualTryonResult $result): string
    {
        if (function_exists('imagecreatetruecolor')) {
            $img = imagecreatetruecolor(400, 500);
            $bg = imagecolorallocate($img, 253, 240, 243);
            $text = imagecolorallocate($img, 201, 114, 140);
            imagefilledrectangle($img, 0, 0, 400, 500, $bg);
            imagestring($img, 5, 100, 220, 'Virtual Try-On', $text);
            imagestring($img, 3, 130, 250, 'Mock Result', $text);
            ob_start();
            imagejpeg($img, null, 85);
            $data = ob_get_clean();
            imagedestroy($img);
            return $data;
        }

        // Minimal 1x1 JPEG fallback
        return base64_decode('/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMCwsKCwsNCQ0NDAsMDg8QEQ8ODwwQFRMTFBcbGxceFhoaIR4aIf/2wBDAQMEBAUEBQkFBQkaEA0QGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoa/wAARCAABAAEDASIAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACf/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AKwA//9k=');
    }

    /**
     * Get the user's face photo as base64.
     */
    protected function getFaceImageBase64(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 'data:image/jpeg;base64,' . base64_encode($imageData);
    }

    /**
     * Get the product model image as base64 from the product_images disk.
     *
     * Note: product images are currently stored on the local filesystem (public/uploads/files/)
     * for legacy reasons. In multi-node deployments, all workers must have access to this
     * directory (shared volume / synced assets). Migrating product images to cloud storage is
     * tracked as a follow-up task.
     */
    protected function getProductImageBase64(string $imagePath): string
    {
        $imagePath = basename($imagePath); // prevent path traversal

        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);
        }

        $mimeType = Storage::disk('product_images')->mimeType($imagePath) ?: 'image/jpeg';

        return 'data:' . $mimeType . ';base64,' . base64_encode($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;
    }
}
