<?php

namespace App\Http\Controllers\Api\Site;

use App\Http\Controllers\Controller;
use App\Jobs\ProcessVirtualTryon;
use App\Product;
use App\Services\VirtualTryonImageService;
use App\UserFacePhoto;
use App\VirtualTryonResult;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;

class VirtualTryonController extends Controller
{
    const MAX_PENDING_JOBS = 3;
    const STALE_PENDING_MINUTES = 10;

    protected $imageService;

    public function __construct(VirtualTryonImageService $imageService)
    {
        $this->imageService = $imageService;
    }

    /**
     * Redacted error logger — stack traces may contain payloads / headers,
     * so emit them only in local/testing.
     */
    protected function logError(string $context, \Throwable $e): void
    {
        $payload = ['error' => $e->getMessage()];
        if (app()->environment(['local', 'testing'])) {
            $payload['trace'] = $e->getTraceAsString();
        }
        Log::error($context, $payload);
    }

    /**
     * Decorate a UserFacePhoto with its short-lived signed proxy URL.
     */
    protected function decorateFacePhoto(UserFacePhoto $photo): array
    {
        $data = $photo->toArray();
        $data['file_path'] = $this->imageService->facePhotoSignedUrl($photo);
        return $data;
    }

    /**
     * List user's face photos (max 3).
     */
    public function listFacePhotos()
    {
        try {
            $user = Auth::user();
            if (!$user) {
                return response()->json(['error' => 'Unauthorized'], 401);
            }

            $photos = $user->facePhotos()->get()->map(function ($photo) {
                return $this->decorateFacePhoto($photo);
            });

            return response()->json([
                'status' => 'success',
                'data' => $photos,
                'max' => VirtualTryonImageService::MAX_FACE_PHOTOS,
            ]);
        } catch (\Exception $e) {
            $this->logError('VirtualTryon error', $e);
            return response()->json(['error' => 'エラーが発生しました。しばらくしてからお試しください。'], 500);
        }
    }

    /**
     * Upload a new face photo.
     */
    public function uploadFacePhoto(Request $request)
    {
        try {
            $user = Auth::user();
            if (!$user) {
                return response()->json(['error' => 'Unauthorized'], 401);
            }

            $validator = Validator::make($request->all(), [
                'file' => ['required', 'image', 'mimes:jpeg,png,jpg', 'max:10240'],
            ]);

            if ($validator->fails()) {
                return response()->json(['errors' => $validator->errors()], 422);
            }

            $photo = $this->imageService->uploadFacePhoto($request->file('file'), $user->id);

            return response()->json([
                'status' => 'success',
                'data' => $this->decorateFacePhoto($photo),
            ]);
        } catch (\Exception $e) {
            // Log for diagnostics, but return a sanitized message to the client.
            $this->logError('VirtualTryon upload', $e);
            $msg = str_contains($e->getMessage(), '最大')
                || str_contains($e->getMessage(), '画像サイズ')
                ? $e->getMessage()
                : 'アップロードに失敗しました';
            return response()->json(['error' => $msg], 400);
        }
    }

    /**
     * Delete a face photo.
     */
    public function deleteFacePhoto($id)
    {
        try {
            $user = Auth::user();
            if (!$user) {
                return response()->json(['error' => 'Unauthorized'], 401);
            }

            $photo = UserFacePhoto::where('id', $id)->where('user_id', $user->id)->first();
            if (!$photo) {
                return response()->json(['error' => '写真が見つかりません'], 404);
            }

            $this->imageService->deleteFacePhoto($photo);

            return response()->json(['status' => 'success']);
        } catch (\Exception $e) {
            $this->logError('VirtualTryon error', $e);
            return response()->json(['error' => 'エラーが発生しました。しばらくしてからお試しください。'], 500);
        }
    }

    /**
     * Stream a face photo through a signed proxy URL.
     * Enforces both signed-URL integrity (via `signed` middleware on the route)
     * and DB-level ownership so a leaked URL cannot reach another user's asset.
     */
    public function streamFacePhoto(Request $request, $id)
    {
        try {
            $photo = UserFacePhoto::find($id);
            if (!$photo) {
                return response()->json(['error' => '画像が見つかりません'], 404);
            }

            // Session auth is optional here because the URL is signed; we still
            // enforce ownership when an authenticated user is present, which
            // catches accidental URL reuse across accounts.
            $user = Auth::user();
            if ($user && $photo->user_id !== $user->id) {
                return response()->json(['error' => 'Forbidden'], 403);
            }

            return $this->imageService->streamFacePhoto($photo);
        } catch (\Exception $e) {
            $this->logError('VirtualTryon stream face photo', $e);
            return response()->json(['error' => 'エラーが発生しました'], 500);
        }
    }

    /**
     * Stream a temporary input image so an external face-swap API (FaceMint,
     * WaveSpeed, etc.) can fetch it via a signed, short-lived URL when running
     * in VIRTUAL_TRYON_STORAGE=local mode.
     *
     * The route is signed (60s–10min TTL) so leaking the URL after expiry is
     * harmless. No DB / ownership check — any caller with a valid signature
     * within TTL gets the bytes. Files live on the private tryon_local disk
     * and are deleted by the driver in finally{} after the API call.
     */
    public function streamTemp(Request $request, $token)
    {
        $path = \App\Services\FaceSwap\TempImageHost::tokenToPath($token);
        if (!$path || !\Illuminate\Support\Facades\Storage::disk('tryon_local')->exists($path)) {
            return response()->json(['error' => 'Not found'], 404);
        }
        return \Illuminate\Support\Facades\Storage::disk('tryon_local')->response($path);
    }

    /**
     * Stream a try-on result image through a signed proxy URL.
     */
    public function streamResultImage(Request $request, $code)
    {
        try {
            $result = VirtualTryonResult::where('code', $code)->first();
            if (!$result) {
                return response()->json(['error' => '画像が見つかりません'], 404);
            }

            $user = Auth::user();
            if ($user && $result->user_id !== $user->id) {
                return response()->json(['error' => 'Forbidden'], 403);
            }

            return $this->imageService->streamResultImage($result);
        } catch (\Exception $e) {
            $this->logError('VirtualTryon stream result', $e);
            return response()->json(['error' => 'エラーが発生しました'], 500);
        }
    }

    /**
     * Start virtual try-on processing.
     */
    public function start(Request $request)
    {
        try {
            $user = Auth::user();
            if (!$user) {
                return response()->json(['error' => 'Unauthorized'], 401);
            }

            $validator = Validator::make($request->all(), [
                'product_id' => ['required', 'integer'],
                'face_photo_id' => ['required', 'integer'],
                'consent' => ['sometimes', 'boolean'],
            ]);

            if ($validator->fails()) {
                return response()->json(['errors' => $validator->errors()], 422);
            }

            // Verify face photo belongs to user (no exists rule - prevents IDOR enumeration)
            $facePhoto = UserFacePhoto::where('id', $request->face_photo_id)
                ->where('user_id', $user->id)
                ->first();

            if (!$facePhoto) {
                return response()->json(['error' => '顔写真が見つかりません'], 404);
            }

            // Verify product
            $product = Product::where('id', $request->product_id)
                ->where('is_deleted', 0)
                ->first();

            if (!$product) {
                return response()->json(['error' => '商品が見つかりません'], 404);
            }

            // 試着用モデル写真があれば優先、なければ商品メイン画像にフォールバック
            $sourceImage = basename($product->tryon_model_image ?: ($product->image ?? '')); // prevent path traversal

            // In production, require image file. In mock mode (no API key), allow missing files.
            $hasApiKey = !empty(config('faceswap.segmind_api_key'));
            if ($hasApiKey && (!$sourceImage || !\Illuminate\Support\Facades\Storage::disk('product_images')->exists($sourceImage))) {
                return response()->json(['error' => '商品画像が見つかりません'], 404);
            }

            // Handle consent (set explicitly, NOT via mass-assignment — tryon_consent_at
            // is intentionally kept out of $fillable so it can't be set through profile
            // update endpoints and silently bypass the consent UX).
            if (!$user->tryon_consent_at) {
                if (!$request->input('consent', false)) {
                    return response()->json([
                        'error' => '試着機能のご利用には同意が必要です',
                        'requires_consent' => true,
                    ], 422);
                }
            }

            // Atomically enforce the pending-job quota and create the record.
            // Previously this was a check-then-create without a lock, allowing
            // parallel requests to exceed MAX_PENDING_JOBS (Segmind cost abuse).
            $result = DB::transaction(function () use ($user, $facePhoto, $product, $sourceImage, $request) {
                VirtualTryonResult::where('user_id', $user->id)
                    ->whereIn('status', ['pending', 'processing'])
                    ->where('created_at', '<', now()->subMinutes(self::STALE_PENDING_MINUTES))
                    ->update([
                        'status' => 'failed',
                        'error' => 'Processing timed out',
                    ]);

                $pendingCount = VirtualTryonResult::where('user_id', $user->id)
                    ->whereIn('status', ['pending', 'processing'])
                    ->lockForUpdate()
                    ->count();

                if ($pendingCount >= self::MAX_PENDING_JOBS) {
                    return null;
                }

                // Set consent within the same transaction so it's consistent with the job creation.
                if (!$user->tryon_consent_at && $request->input('consent', false)) {
                    $user->tryon_consent_at = now();
                    $user->save();
                }

                $newResult = new VirtualTryonResult();
                // Assign protected fields directly; they are intentionally excluded from $fillable.
                $newResult->code = \Illuminate\Support\Str::uuid()->toString();
                $newResult->user_id = $user->id;
                $newResult->fill([
                    'product_id' => $product->id,
                    'face_photo_id' => $facePhoto->id,
                    'source_image' => $sourceImage,
                    'status' => 'pending',
                    'expires_at' => now()->addDays(90),
                ]);
                $newResult->save();

                return $newResult;
            });

            if ($result === null) {
                return response()->json([
                    'error' => '処理中の試着が多すぎます。完了後にお試しください。',
                ], 429);
            }

            // Dispatch queue job
            ProcessVirtualTryon::dispatch($result);

            return response()->json([
                'status' => 'progressing',
                'code' => $result->code,
            ]);
        } catch (\Exception $e) {
            $this->logError('VirtualTryon error', $e);
            return response()->json(['error' => 'エラーが発生しました。しばらくしてからお試しください。'], 500);
        }
    }

    /**
     * Cancel a pending / processing try-on.
     *
     * We mark the DB row as `cancelled` rather than deleting it so that an
     * already-started queue worker's completion write (status=completed) will
     * race benignly — the UI ignores results whose status is `cancelled`.
     * Note: the Segmind HTTP call, if already in flight, still completes and
     * bills; this endpoint just stops the UI from waiting and prevents the
     * result from counting toward the user's pending-job quota.
     */
    public function cancel($code)
    {
        try {
            $user = Auth::user();
            if (!$user) {
                return response()->json(['error' => 'Unauthorized'], 401);
            }

            $result = VirtualTryonResult::where('code', $code)
                ->where('user_id', $user->id)
                ->first();

            if (!$result) {
                return response()->json(['error' => '試着結果が見つかりません'], 404);
            }

            if (in_array($result->status, ['pending', 'processing'], true)) {
                $result->update(['status' => 'cancelled']);
            }

            return response()->json(['status' => 'success']);
        } catch (\Exception $e) {
            $this->logError('VirtualTryon cancel', $e);
            return response()->json(['error' => 'エラーが発生しました'], 500);
        }
    }

    /**
     * Check processing status (polling endpoint).
     *
     * Returns HTTP 200 for every outcome — including not-found and failure —
     * so the frontend polling loop can handle them via `body.status` rather
     * than triggering the global Axios 4xx error toast. The ownership check
     * (`user_id = auth`) still prevents cross-user leakage.
     */
    public function checkStatus($code)
    {
        $user = Auth::user();
        if (!$user) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        $result = VirtualTryonResult::where('code', $code)
            ->where('user_id', $user->id)
            ->first();

        if (!$result) {
            return response()->json([
                'status' => 'error',
                'code' => $code,
                'message' => '試着結果が見つかりません。',
            ]);
        }

        if ($result->status === 'failed') {
            return response()->json([
                'status' => 'error',
                'code' => $code,
                'message' => '画像処理に失敗しました。もう一度お試しください。',
            ]);
        }

        if ($result->status === 'cancelled') {
            return response()->json([
                'status' => 'cancelled',
                'code' => $code,
            ]);
        }

        if ($result->status === 'completed') {
            return response()->json([
                'status' => 'success',
                'code' => $code,
                'result_id' => $result->id,
                'result_image' => $this->imageService->resultImageSignedUrl($result),
                'source_image' => $result->source_image,
                'product_id' => $result->product_id,
            ]);
        }

        return response()->json([
            'status' => 'progressing',
            'code' => $code,
        ]);
    }

    /**
     * Save try-on result to history.
     */
    public function save($id)
    {
        try {
            $user = Auth::user();
            if (!$user) {
                return response()->json(['error' => 'Unauthorized'], 401);
            }

            $result = VirtualTryonResult::where('id', $id)
                ->where('user_id', $user->id)
                ->first();

            if (!$result) {
                return response()->json(['error' => '試着結果が見つかりません'], 404);
            }

            $result->update(['is_saved' => true]);

            return response()->json(['status' => 'success']);
        } catch (\Exception $e) {
            $this->logError('VirtualTryon error', $e);
            return response()->json(['error' => 'エラーが発生しました。しばらくしてからお試しください。'], 500);
        }
    }

    /**
     * Get user's try-on history.
     */
    public function history(Request $request)
    {
        try {
            $user = Auth::user();
            if (!$user) {
                return response()->json(['error' => 'Unauthorized'], 401);
            }

            $limit = max(1, min((int) $request->input('limit', 12), 50));
            $results = VirtualTryonResult::where('user_id', $user->id)
                ->where('is_saved', true)
                ->active()
                ->with('product:id,name,image')
                ->orderBy('created_at', 'desc')
                ->paginate($limit);

            $results->getCollection()->transform(function ($item) {
                $item->result_image_url = $this->imageService->resultImageSignedUrl($item);
                return $item;
            });

            return response()->json([
                'status' => 'success',
                'data' => $results->items(),
                'meta' => [
                    'total' => $results->total(),
                    'current_page' => $results->currentPage(),
                    'last_page' => $results->lastPage(),
                    'per_page' => $results->perPage(),
                ],
            ]);
        } catch (\Exception $e) {
            $this->logError('VirtualTryon error', $e);
            return response()->json(['error' => 'エラーが発生しました。しばらくしてからお試しください。'], 500);
        }
    }

    /**
     * Download result image with watermark.
     */
    public function download($id)
    {
        try {
            $user = Auth::user();
            if (!$user) {
                return response()->json(['error' => 'Unauthorized'], 401);
            }

            $result = VirtualTryonResult::where('id', $id)
                ->where('user_id', $user->id)
                ->first();

            if (!$result) {
                return response()->json(['error' => '試着結果が見つかりません'], 404);
            }

            return $this->imageService->downloadWithWatermark($result);
        } catch (\Exception $e) {
            $this->logError('VirtualTryon error', $e);
            return response()->json(['error' => 'エラーが発生しました。しばらくしてからお試しください。'], 500);
        }
    }
}
