<?php

declare(strict_types=1);

use App\Models\NovaCard;
use App\Models\NovaCardCreatorPreset;
use App\Models\NovaCardExport;
use App\Models\User;
use App\Services\NovaCards\NovaCardCreatorPresetService;
use App\Services\NovaCards\NovaCardProjectNormalizer;
use App\Services\NovaCards\NovaCardRelatedCardsService;
use App\Services\NovaCards\NovaCardRisingService;
use Illuminate\Support\Str;

// ─── Helpers ──────────────────────────────────────────────────────────────────

function v3User(): User
{
    return User::factory()->create();
}

function v3Card(User $user, array $attributes = []): NovaCard
{
    return NovaCard::query()->create(array_merge([
        'user_id' => $user->id,
        'uuid' => Str::uuid()->toString(),
        'slug' => 'v3-card-' . Str::lower(Str::random(8)),
        'title' => 'Nova v3 Card',
        'quote_text' => 'A test quote for version three.',
        'format' => 'square',
        'status' => NovaCard::STATUS_PUBLISHED,
        'visibility' => NovaCard::VISIBILITY_PUBLIC,
        'moderation_status' => NovaCard::MOD_APPROVED,
        'published_at' => now(),
        'schema_version' => 3,
        'project_json' => [
            'schema_version' => 3,
            'meta' => ['editor' => 'nova-cards-v3'],
            'content' => ['title' => 'Nova v3 Card', 'quote_text' => 'A test quote for version three.'],
            'text_blocks' => [],
            'layout' => ['layout' => 'quote_heavy', 'position' => 'center', 'alignment' => 'center', 'padding' => 'comfortable', 'max_width' => 'balanced'],
            'typography' => ['font_preset' => 'modern-sans', 'text_color' => '#ffffff', 'accent_color' => '#e0f2fe', 'quote_size' => 72, 'author_size' => 28, 'line_height' => 1.2, 'shadow_preset' => 'soft', 'quote_mark_preset' => 'none', 'text_panel_style' => 'none'],
            'background' => ['type' => 'gradient', 'gradient_preset' => 'midnight-nova', 'gradient_colors' => ['#0f172a', '#1d4ed8']],
            'canvas' => ['density' => 'standard', 'safe_zone' => true],
            'frame' => ['preset' => 'none'],
            'effects' => ['color_grade' => 'none', 'effect_preset' => 'none', 'intensity' => 50],
            'export_preferences' => ['allow_export' => true, 'default_format' => 'preview'],
            'source_context' => ['style_family' => null, 'palette_family' => null],
            'decorations' => [],
            'assets' => ['pack_ids' => [], 'template_pack_ids' => [], 'items' => []],
        ],
        'allow_export' => true,
        'allow_background_reuse' => false,
    ], $attributes));
}

// ─── Schema normalizer v3 tests ───────────────────────────────────────────────

describe('NovaCardProjectNormalizer v3', function (): void {
    it('produces schema_version 3 for new projects', function (): void {
        $normalizer = app(NovaCardProjectNormalizer::class);
        $result = $normalizer->normalize(null, null, []);

        expect($result['schema_version'])->toBe(3);
        expect($result['meta']['editor'])->toBe('nova-cards-v3');
    });

    it('detects legacy v1/v2 projects as needing upgrade', function (): void {
        $normalizer = app(NovaCardProjectNormalizer::class);

        $v1 = ['schema_version' => 1, 'quote_text' => 'old quote'];
        expect($normalizer->isLegacyProject($v1))->toBeTrue();

        $v2 = ['schema_version' => 2, 'meta' => ['editor' => 'nova-cards-v2']];
        expect($normalizer->isLegacyProject($v2))->toBeTrue();

        $v3 = ['schema_version' => 3, 'meta' => ['editor' => 'nova-cards-v3']];
        expect($normalizer->isLegacyProject($v3))->toBeFalse();
    });

    it('upgradeToV3 includes all v3 sections', function (): void {
        $normalizer = app(NovaCardProjectNormalizer::class);
        $v2 = [
            'schema_version' => 2,
            'meta' => ['editor' => 'nova-cards-v2'],
            'layout' => ['layout' => 'centered'],
            'background' => ['type' => 'gradient', 'gradient_colors' => ['#000', '#fff']],
            'typography' => ['font_preset' => 'elegant-serif'],
        ];

        $result = $normalizer->upgradeToV3($v2);

        expect($result['schema_version'])->toBe(3);
        expect($result['meta']['editor'])->toBe('nova-cards-v3');
        // v2 content preserved
        expect($result['layout']['layout'])->toBe('centered');
        expect($result['background']['gradient_colors'])->toBe(['#000', '#fff']);
        expect($result['typography']['font_preset'])->toBe('elegant-serif');
        // v3 sections added
        expect($result)->toHaveKey('canvas');
        expect($result)->toHaveKey('frame');
        expect($result)->toHaveKey('effects');
        expect($result)->toHaveKey('export_preferences');
        expect($result)->toHaveKey('source_context');
        // v3 typography additions
        expect($result['typography'])->toHaveKey('quote_mark_preset');
        expect($result['typography'])->toHaveKey('text_panel_style');
    });

    it('normalizeForCard upgrades a v1 card to v3 on the fly', function (): void {
        $user = v3User();
        $card = NovaCard::query()->create([
            'user_id' => $user->id,
            'uuid' => Str::uuid()->toString(),
            'slug' => 'legacy-v1-' . Str::lower(Str::random(6)),
            'title' => 'Legacy card',
            'quote_text' => 'An old-school quote.',
            'format' => 'square',
            'status' => NovaCard::STATUS_DRAFT,
            'visibility' => NovaCard::VISIBILITY_PRIVATE,
            'moderation_status' => NovaCard::MOD_PENDING,
            'schema_version' => 1,
            'project_json' => ['schema_version' => 1, 'quote_text' => 'An old-school quote.'],
        ]);

        $normalizer = app(NovaCardProjectNormalizer::class);
        $result = $normalizer->normalizeForCard($card);

        expect($result['schema_version'])->toBe(3);
        expect($result)->toHaveKey('canvas');
        expect($result)->toHaveKey('frame');
        expect($result)->toHaveKey('effects');
    });
});

// ─── Creator preset tests ─────────────────────────────────────────────────────

describe('NovaCardCreatorPresetService', function (): void {
    it('creates a preset for a user', function (): void {
        $user = v3User();
        $service = app(NovaCardCreatorPresetService::class);

        $preset = $service->create($user, [
            'name' => 'My Midnight Style',
            'preset_type' => NovaCardCreatorPreset::TYPE_STYLE,
            'config_json' => ['typography' => ['font_preset' => 'bold-poster']],
        ]);

        expect($preset)->toBeInstanceOf(NovaCardCreatorPreset::class)
            ->and($preset->name)->toBe('My Midnight Style')
            ->and($preset->preset_type)->toBe(NovaCardCreatorPreset::TYPE_STYLE)
            ->and($preset->user_id)->toBe($user->id);
    });

    it('enforces per-type limit', function (): void {
        $user = v3User();
        $service = app(NovaCardCreatorPresetService::class);

        for ($i = 0; $i < NovaCardCreatorPresetService::MAX_PER_TYPE; $i++) {
            $service->create($user, [
                'name' => "Preset $i",
                'preset_type' => NovaCardCreatorPreset::TYPE_LAYOUT,
                'config_json' => [],
            ]);
        }

        expect(fn () => $service->create($user, [
            'name' => 'One too many',
            'preset_type' => NovaCardCreatorPreset::TYPE_LAYOUT,
            'config_json' => [],
        ]))->toThrow(\Symfony\Component\HttpKernel\Exception\HttpException::class);
    });

    it('captures preset fields from a card project_json', function (): void {
        $user = v3User();
        $card = v3Card($user);
        $service = app(NovaCardCreatorPresetService::class);

        $preset = $service->captureFromCard($user, $card, 'My Style Capture', NovaCardCreatorPreset::TYPE_STYLE);

        $config = $preset->config_json;
        // Style presets capture typography
        expect($config)->toHaveKey('typography');
    });

    it('applies a background preset to produce a project patch', function (): void {
        $user = v3User();
        $service = app(NovaCardCreatorPresetService::class);

        $preset = NovaCardCreatorPreset::query()->create([
            'user_id' => $user->id,
            'name' => 'Cinematic BG',
            'preset_type' => NovaCardCreatorPreset::TYPE_BACKGROUND,
            'config_json' => [
                'background' => ['type' => 'gradient', 'gradient_preset' => 'deep-cinema'],
            ],
            'is_default' => false,
        ]);

        $card = v3Card($user);
        $patch = $service->applyToProjectPatch($preset, $card);

        expect($patch)->toHaveKey('background')
            ->and($patch['background']['gradient_preset'])->toBe('deep-cinema');
    });

    it('sets a preset as the default for its type', function (): void {
        $user = v3User();
        $service = app(NovaCardCreatorPresetService::class);

        $presetA = $service->create($user, ['name' => 'A', 'preset_type' => NovaCardCreatorPreset::TYPE_TYPOGRAPHY, 'config_json' => []]);
        $presetB = $service->create($user, ['name' => 'B', 'preset_type' => NovaCardCreatorPreset::TYPE_TYPOGRAPHY, 'config_json' => []]);
        $service->setDefault($user, $presetB->id);

        expect(NovaCardCreatorPreset::query()->find($presetA->id)->is_default)->toBeFalse()
            ->and(NovaCardCreatorPreset::query()->find($presetB->id)->is_default)->toBeTrue();
    });
});

// ─── Rising service tests ─────────────────────────────────────────────────────

describe('NovaCardRisingService', function (): void {
    it('returns recently published cards with engagement', function (): void {
        $user = v3User();
        $rising = v3Card($user, [
            'published_at' => now()->subHours(12),
            'saves_count' => 30,
            'likes_count' => 20,
        ]);

        $old = v3Card($user, [
            'slug' => 'old-card-' . Str::lower(Str::random(6)),
            'published_at' => now()->subDays(10),
            'saves_count' => 100,
        ]);

        $service = app(NovaCardRisingService::class);
        $results = $service->risingCards(20, false);

        expect($results->pluck('id'))->toContain($rising->id)
            ->and($results->pluck('id'))->not->toContain($old->id);
    });

    it('invalidateCache does not throw', function (): void {
        $service = app(NovaCardRisingService::class);
        expect(fn () => $service->invalidateCache())->not->toThrow(\Throwable::class);
    });
});

// ─── Related cards service tests ───────────────────────────────────────────────

describe('NovaCardRelatedCardsService', function (): void {
    it('returns related cards for a given card', function (): void {
        $user = v3User();
        $source = v3Card($user, ['style_family' => 'minimal']);
        $related = v3Card($user, [
            'slug' => 'related-card-' . Str::lower(Str::random(6)),
            'style_family' => 'minimal',
            'published_at' => now()->subHours(2),
        ]);

        $service = app(NovaCardRelatedCardsService::class);
        $results = $service->related($source, 8, false);

        expect($results->pluck('id'))->toContain($related->id)
            ->and($results->pluck('id'))->not->toContain($source->id);
    });
});

// ─── Export model tests ────────────────────────────────────────────────────────

describe('NovaCardExport', function (): void {
    it('isReady returns true only when status is ready', function (): void {
        $export = new NovaCardExport(['status' => NovaCardExport::STATUS_READY]);
        expect($export->isReady())->toBeTrue();

        $pending = new NovaCardExport(['status' => NovaCardExport::STATUS_PENDING]);
        expect($pending->isReady())->toBeFalse();
    });

    it('isExpired returns true when expires_at is in the past', function (): void {
        $expired = new NovaCardExport(['expires_at' => now()->subHour()]);
        expect($expired->isExpired())->toBeTrue();

        $fresh = new NovaCardExport(['expires_at' => now()->addHour()]);
        expect($fresh->isExpired())->toBeFalse();
    });
});

// ─── API: preset routes ────────────────────────────────────────────────────────

describe('Nova Cards v3 API — presets', function (): void {
    it('lists presets for authenticated user', function (): void {
        $user = v3User();
        NovaCardCreatorPreset::query()->create([
            'user_id' => $user->id,
            'name' => 'API preset',
            'preset_type' => NovaCardCreatorPreset::TYPE_STYLE,
            'config_json' => [],
            'is_default' => false,
        ]);

        $response = $this->actingAs($user)->getJson(route('api.cards.presets.index'));
        $response->assertOk();
        $response->assertJsonFragment(['name' => 'API preset']);
    });

    it('creates a preset via API', function (): void {
        $user = v3User();
        $response = $this->actingAs($user)->postJson(route('api.cards.presets.store'), [
            'name' => 'API created preset',
            'preset_type' => NovaCardCreatorPreset::TYPE_BACKGROUND,
            'config_json' => ['background' => ['type' => 'gradient']],
        ]);

        $response->assertCreated();
        expect(NovaCardCreatorPreset::query()->where('user_id', $user->id)->count())->toBe(1);
    });

    it('deletes a preset via API', function (): void {
        $user = v3User();
        $preset = NovaCardCreatorPreset::query()->create([
            'user_id' => $user->id,
            'name' => 'To delete',
            'preset_type' => NovaCardCreatorPreset::TYPE_STYLE,
            'config_json' => [],
            'is_default' => false,
        ]);

        $response = $this->actingAs($user)->deleteJson(route('api.cards.presets.destroy', $preset->id));
        $response->assertOk();
        expect(NovaCardCreatorPreset::query()->find($preset->id))->toBeNull();
    });

    it('prevents deleting another user\'s preset', function (): void {
        $owner = v3User();
        $attacker = v3User();
        $preset = NovaCardCreatorPreset::query()->create([
            'user_id' => $owner->id,
            'name' => 'Owned preset',
            'preset_type' => NovaCardCreatorPreset::TYPE_STYLE,
            'config_json' => [],
            'is_default' => false,
        ]);

        $response = $this->actingAs($attacker)->deleteJson(route('api.cards.presets.destroy', $preset->id));
        $response->assertForbidden();
    });
});

// ─── Web: rising page ────────────────────────────────────────────────────────

describe('Nova Cards v3 web — rising page', function (): void {
    it('rising page returns 200', function (): void {
        $this->get(route('cards.rising'))->assertOk();
    });
});
