<?php

namespace Tests\Feature;

use App\Jobs\ProcessVirtualTryon;
use App\Product;
use App\User;
use App\UserFacePhoto;
use App\VirtualTryonResult;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Storage;
use Tests\RefreshDatabaseWithoutSeeds;
use Tests\TestCase;

class VirtualTryonTest extends TestCase
{
    use RefreshDatabaseWithoutSeeds;

    protected $user;
    protected $product;

    protected function setUp(): void
    {
        parent::setUp();

        // In testing env, VirtualTryonImageService::diskName() returns 'tryon_local'.
        Storage::fake('tryon_local');
        Storage::fake('product_images');
        Queue::fake();

        $this->user = factory(User::class)->create();
        // tryon_consent_at is intentionally excluded from $fillable; assign directly.
        $this->user->tryon_consent_at = now();
        $this->user->save();

        $this->product = Product::create([
            'name' => 'テスト振袖',
            'code' => 'TEST-001',
            'slug' => 'test-kimono-001',
            'image' => 'test-product.jpg',
            'type' => 1,
            'status' => 1,
            'is_activated' => 1,
            'is_deleted' => 0,
        ]);

        // Populate the faked product_images disk so the controller's existence
        // check (Storage::disk('product_images')->exists()) succeeds.
        Storage::disk('product_images')->put('test-product.jpg', $this->minimalJpeg());
    }

    protected function tearDown(): void
    {
        parent::tearDown();
    }

    /**
     * Create a VirtualTryonResult in tests. `code` and `user_id` are not in
     * $fillable (security hardening), so assign them directly.
     */
    protected function createTryonResult(array $attrs): VirtualTryonResult
    {
        $result = new VirtualTryonResult();
        $result->code = $attrs['code'];
        $result->user_id = $attrs['user_id'];
        unset($attrs['code'], $attrs['user_id']);
        $result->fill($attrs);
        $result->save();
        return $result;
    }

    /**
     * Create a UserFacePhoto for tests with consistent storage_path.
     */
    protected function createFacePhoto(int $userId, string $suffix = ''): UserFacePhoto
    {
        return UserFacePhoto::create([
            'user_id' => $userId,
            'file_path' => 'face-photos/' . $userId . '/face' . $suffix . '.jpg',
            'storage_path' => 'face-photos/' . $userId . '/face' . $suffix . '.jpg',
            'is_default' => true,
        ]);
    }

    /**
     * Minimal valid JPEG binary (1x1 pixel).
     */
    protected function minimalJpeg(): string
    {
        return base64_decode(
            '/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoH' .
            'BwYIDAoMCwsKCwsNCQ0NDAsMDg8QEQ8ODwwQFRMTFBcbGxceFhoaIR4aIf/2wBDAQ' .
            'MEBAUEBQkFBQkaEA0QGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoa' .
            'GhoaGhoaGhoaGhoaGhoa/wAARCAABAAEDASIAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACf/' .
            'EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAA' .
            'AAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AKwA//9k='
        );
    }

    // ==========================================
    // 認証テスト
    // ==========================================

    public function test_unauthenticated_user_cannot_access_tryon_api()
    {
        $response = $this->getJson('/api/user/face-photos');
        $response->assertStatus(401);

        $response = $this->postJson('/api/virtual-tryon/start', []);
        $response->assertStatus(401);

        $response = $this->getJson('/api/virtual-tryon/history');
        $response->assertStatus(401);
    }

    // ==========================================
    // 顔写真管理テスト
    // ==========================================

    public function test_user_can_upload_face_photo()
    {
        if (!extension_loaded('gd')) {
            $this->markTestSkipped('GD extension required for image upload test');
        }

        // Use ->image() (not ->create()) so the test file is a real GD image
        // that passes getimagesize() / Intervention decoding.
        $file = UploadedFile::fake()->image('face.jpg', 400, 400);

        $response = $this->actingAs($this->user, 'api')
            ->postJson('/api/user/face-photos', [
                'file' => $file,
            ]);

        $response->assertStatus(200)
            ->assertJson(['status' => 'success']);

        $this->assertDatabaseHas('user_face_photos', [
            'user_id' => $this->user->id,
        ]);
    }

    public function test_user_cannot_upload_more_than_3_photos()
    {
        if (!extension_loaded('gd')) {
            $this->markTestSkipped('GD extension required for image upload test');
        }

        for ($i = 0; $i < 3; $i++) {
            UserFacePhoto::create([
                'user_id' => $this->user->id,
                'file_path' => 'https://cdn.example.com/face-' . $i . '.jpg',
                'storage_path' => 'face-photos/' . $this->user->id . '/face-' . $i . '.jpg',
                'is_default' => $i === 0,
            ]);
        }

        $file = UploadedFile::fake()->create('face4.jpg', 100, 'image/jpeg');

        $response = $this->actingAs($this->user, 'api')
            ->postJson('/api/user/face-photos', [
                'file' => $file,
            ]);

        $response->assertStatus(400);
        $this->assertEquals(3, UserFacePhoto::where('user_id', $this->user->id)->count());
    }

    public function test_user_can_list_face_photos()
    {
        UserFacePhoto::create([
            'user_id' => $this->user->id,
            'file_path' => 'https://cdn.example.com/face-1.jpg',
            'storage_path' => 'face-photos/' . $this->user->id . '/face-1.jpg',
            'is_default' => true,
        ]);

        $response = $this->actingAs($this->user, 'api')
            ->getJson('/api/user/face-photos');

        $response->assertStatus(200)
            ->assertJson(['status' => 'success'])
            ->assertJsonCount(1, 'data');
    }

    public function test_user_can_delete_face_photo()
    {
        $photo = UserFacePhoto::create([
            'user_id' => $this->user->id,
            'file_path' => 'https://cdn.example.com/face.jpg',
            'storage_path' => 'face-photos/' . $this->user->id . '/face.jpg',
            'is_default' => true,
        ]);

        $response = $this->actingAs($this->user, 'api')
            ->deleteJson('/api/user/face-photos/' . $photo->id);

        $response->assertStatus(200)
            ->assertJson(['status' => 'success']);

        $this->assertDatabaseMissing('user_face_photos', ['id' => $photo->id]);
    }

    // ==========================================
    // 試着開始テスト
    // ==========================================

    public function test_user_can_start_tryon()
    {
        $photo = UserFacePhoto::create([
            'user_id' => $this->user->id,
            'file_path' => 'https://cdn.example.com/face.jpg',
            'storage_path' => 'face-photos/' . $this->user->id . '/face.jpg',
            'is_default' => true,
        ]);

        $response = $this->actingAs($this->user, 'api')
            ->postJson('/api/virtual-tryon/start', [
                'product_id' => $this->product->id,
                'face_photo_id' => $photo->id,
            ]);

        $response->assertStatus(200)
            ->assertJson(['status' => 'progressing'])
            ->assertJsonStructure(['code']);

        $this->assertDatabaseHas('virtual_tryon_results', [
            'user_id' => $this->user->id,
            'product_id' => $this->product->id,
            'status' => 'pending',
        ]);

        Queue::assertPushed(ProcessVirtualTryon::class);
    }

    public function test_tryon_start_fails_with_invalid_product()
    {
        $photo = UserFacePhoto::create([
            'user_id' => $this->user->id,
            'file_path' => 'https://cdn.example.com/face.jpg',
            'storage_path' => 'face-photos/' . $this->user->id . '/face.jpg',
            'is_default' => true,
        ]);

        $response = $this->actingAs($this->user, 'api')
            ->postJson('/api/virtual-tryon/start', [
                'product_id' => 99999,
                'face_photo_id' => $photo->id,
            ]);

        // Controller returns 404 for a missing product (validator passes the
        // integer rule, then the lookup fails).
        $response->assertStatus(404);
    }

    public function test_tryon_requires_consent_for_first_time_user()
    {
        $userNoConsent = factory(User::class)->create();
        // Ensure consent is NOT set on this user.
        $userNoConsent->tryon_consent_at = null;
        $userNoConsent->save();

        $photo = UserFacePhoto::create([
            'user_id' => $userNoConsent->id,
            'file_path' => 'https://cdn.example.com/face.jpg',
            'storage_path' => 'face-photos/' . $userNoConsent->id . '/face.jpg',
            'is_default' => true,
        ]);

        $response = $this->actingAs($userNoConsent, 'api')
            ->postJson('/api/virtual-tryon/start', [
                'product_id' => $this->product->id,
                'face_photo_id' => $photo->id,
            ]);

        $response->assertStatus(422)
            ->assertJson(['requires_consent' => true]);

        $response = $this->actingAs($userNoConsent, 'api')
            ->postJson('/api/virtual-tryon/start', [
                'product_id' => $this->product->id,
                'face_photo_id' => $photo->id,
                'consent' => true,
            ]);

        $response->assertStatus(200)
            ->assertJson(['status' => 'progressing']);

        $this->assertNotNull($userNoConsent->fresh()->tryon_consent_at);
    }

    public function test_tryon_start_clears_stale_pending_jobs_before_quota_check()
    {
        $photo = UserFacePhoto::create([
            'user_id' => $this->user->id,
            'file_path' => 'https://cdn.example.com/face.jpg',
            'storage_path' => 'face-photos/' . $this->user->id . '/face.jpg',
            'is_default' => true,
        ]);

        for ($i = 0; $i < 3; $i++) {
            $stale = $this->createTryonResult([
                'code' => 'stale-job-' . $i,
                'user_id' => $this->user->id,
                'product_id' => $this->product->id,
                'face_photo_id' => $photo->id,
                'source_image' => 'test-product.jpg',
                'status' => 'processing',
                'expires_at' => now()->addDays(90),
            ]);
            $stale->created_at = now()->subMinutes(11);
            $stale->updated_at = now()->subMinutes(11);
            $stale->save();
        }

        $response = $this->actingAs($this->user, 'api')
            ->postJson('/api/virtual-tryon/start', [
                'product_id' => $this->product->id,
                'face_photo_id' => $photo->id,
            ]);

        $response->assertStatus(200)
            ->assertJson(['status' => 'progressing']);

        $this->assertEquals(3, VirtualTryonResult::where('user_id', $this->user->id)
            ->where('code', 'like', 'stale-job-%')
            ->where('status', 'failed')
            ->count());
    }

    // ==========================================
    // ステータス・履歴テスト
    // ==========================================

    public function test_status_polling_returns_correct_states()
    {
        $result = $this->createTryonResult([
            'code' => 'test-code-123',
            'user_id' => $this->user->id,
            'product_id' => $this->product->id,
            'source_image' => 'test-product.jpg',
            'status' => 'pending',
            'expires_at' => now()->addDays(90),
        ]);

        $response = $this->actingAs($this->user, 'api')
            ->getJson('/api/virtual-tryon/test-code-123/status');
        $response->assertJson(['status' => 'progressing']);

        $result->update([
            'status' => 'completed',
            'result_image' => 'virtual-tryon/results/test-code-123.jpg',
        ]);

        $response = $this->actingAs($this->user, 'api')
            ->getJson('/api/virtual-tryon/test-code-123/status');
        $response->assertJson(['status' => 'success']);

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

        $response = $this->actingAs($this->user, 'api')
            ->getJson('/api/virtual-tryon/test-code-123/status');
        $response->assertJson(['status' => 'error']);
    }

    public function test_user_can_save_tryon_result()
    {
        $result = $this->createTryonResult([
            'code' => 'save-test-code',
            'user_id' => $this->user->id,
            'product_id' => $this->product->id,
            'source_image' => 'test-product.jpg',
            'result_image' => 'virtual-tryon/results/save-test-code.jpg',
            'status' => 'completed',
            'is_saved' => false,
            'expires_at' => now()->addDays(90),
        ]);

        $response = $this->actingAs($this->user, 'api')
            ->postJson('/api/virtual-tryon/' . $result->id . '/save');

        $response->assertStatus(200)
            ->assertJson(['status' => 'success']);

        $this->assertTrue($result->fresh()->is_saved);
    }

    public function test_user_can_get_tryon_history()
    {
        for ($i = 0; $i < 3; $i++) {
            $this->createTryonResult([
                'code' => 'history-test-' . $i,
                'user_id' => $this->user->id,
                'product_id' => $this->product->id,
                'source_image' => 'test-product.jpg',
                'result_image' => 'virtual-tryon/results/history-test-' . $i . '.jpg',
                'status' => 'completed',
                'is_saved' => true,
                'expires_at' => now()->addDays(90),
            ]);
        }

        $this->createTryonResult([
            'code' => 'history-unsaved',
            'user_id' => $this->user->id,
            'product_id' => $this->product->id,
            'source_image' => 'test-product.jpg',
            'status' => 'completed',
            'is_saved' => false,
            'expires_at' => now()->addDays(90),
        ]);

        $response = $this->actingAs($this->user, 'api')
            ->getJson('/api/virtual-tryon/history');

        if ($response->status() === 500) {
            fwrite(STDERR, "History 500: " . $response->getContent() . "\n");
        }

        $response->assertStatus(200)
            ->assertJson(['status' => 'success']);

        $this->assertEquals(3, count($response->json('data')));
    }
}
