<?php

use App\Enums\QrCodeType;
use App\Enums\QrRenderFormat;
use App\Models\QrCode;
use App\Models\QrCodeRender;
use App\Services\QrCode\QrImageRenderer;
use App\Services\QrCode\QrPayloadBuilder;

// QrPayloadBuilder

test('dynamic qr codes encode their public redirect url regardless of type', function () {
    $qrCode = QrCode::factory()->url()->create();

    $payload = app(QrPayloadBuilder::class)->build($qrCode);

    expect($payload)->toBe($qrCode->publicUrl());
});

test('url type payload encodes the raw target url', function () {
    $qrCode = QrCode::factory()->create([
        'type' => QrCodeType::Url,
        'is_dynamic' => false,
        'target_url' => 'https://example.com/menu',
        'payload' => null,
    ]);

    expect(app(QrPayloadBuilder::class)->build($qrCode))->toBe('https://example.com/menu');
});

test('text type payload encodes the raw text', function () {
    $qrCode = QrCode::factory()->create([
        'type' => QrCodeType::Text,
        'is_dynamic' => false,
        'target_url' => null,
        'payload' => ['text' => 'Hello, world!'],
    ]);

    expect(app(QrPayloadBuilder::class)->build($qrCode))->toBe('Hello, world!');
});

test('email type payload builds a mailto uri with subject and body', function () {
    $qrCode = QrCode::factory()->create([
        'type' => QrCodeType::Email,
        'is_dynamic' => false,
        'target_url' => null,
        'payload' => ['address' => 'shop@pushpvatika.test', 'subject' => 'Order enquiry', 'body' => 'Hi there'],
    ]);

    $payload = app(QrPayloadBuilder::class)->build($qrCode);

    expect($payload)->toStartWith('mailto:shop@pushpvatika.test?')
        ->and($payload)->toContain('subject=Order%20enquiry')
        ->and($payload)->toContain('body=Hi%20there');
});

test('email type payload omits the query string when subject and body are absent', function () {
    $qrCode = QrCode::factory()->create([
        'type' => QrCodeType::Email,
        'is_dynamic' => false,
        'target_url' => null,
        'payload' => ['address' => 'shop@pushpvatika.test'],
    ]);

    expect(app(QrPayloadBuilder::class)->build($qrCode))->toBe('mailto:shop@pushpvatika.test');
});

test('phone type payload builds a tel uri', function () {
    $qrCode = QrCode::factory()->create([
        'type' => QrCodeType::Phone,
        'is_dynamic' => false,
        'target_url' => null,
        'payload' => ['number' => '+919879991799'],
    ]);

    expect(app(QrPayloadBuilder::class)->build($qrCode))->toBe('tel:+919879991799');
});

test('sms type payload builds an SMSTO payload', function () {
    $qrCode = QrCode::factory()->create([
        'type' => QrCodeType::Sms,
        'is_dynamic' => false,
        'target_url' => null,
        'payload' => ['number' => '+919879991799', 'message' => 'Hi there'],
    ]);

    expect(app(QrPayloadBuilder::class)->build($qrCode))->toBe('SMSTO:+919879991799:Hi there');
});

test('wifi type payload escapes special characters in ssid and password', function () {
    $ssid = 'Shop;Wifi';
    $password = 'p:a"s\\s,w';

    $qrCode = QrCode::factory()->create([
        'type' => QrCodeType::Wifi,
        'is_dynamic' => false,
        'target_url' => null,
        'payload' => ['ssid' => $ssid, 'password' => $password],
    ]);

    $payload = app(QrPayloadBuilder::class)->build($qrCode);

    // Built the same way the implementation escapes: any of \ ; , : " gets
    // a backslash prefix, per the de-facto WIFI: QR spec.
    $escapedSsid = addcslashes($ssid, '\\;,:"');
    $escapedPassword = addcslashes($password, '\\;,:"');

    expect($payload)->toBe("WIFI:T:WPA;S:{$escapedSsid};P:{$escapedPassword};H:false;;")
        ->and($escapedSsid)->not->toBe($ssid)
        ->and($escapedPassword)->not->toBe($password);
});

test('wifi type payload marks the network hidden when requested', function () {
    $qrCode = QrCode::factory()->create([
        'type' => QrCodeType::Wifi,
        'is_dynamic' => false,
        'target_url' => null,
        'payload' => ['ssid' => 'Hidden', 'password' => 'secret', 'hidden' => true],
    ]);

    expect(app(QrPayloadBuilder::class)->build($qrCode))->toBe('WIFI:T:WPA;S:Hidden;P:secret;H:true;;');
});

test('vcard type payload uses crlf line endings, not bare newlines', function () {
    $qrCode = QrCode::factory()->create([
        'type' => QrCodeType::VCard,
        'is_dynamic' => false,
        'target_url' => null,
        'payload' => ['first_name' => 'Jay', 'last_name' => 'Patel', 'phone' => '+919879991799'],
    ]);

    $payload = app(QrPayloadBuilder::class)->build($qrCode);

    expect($payload)->toStartWith("BEGIN:VCARD\r\nVERSION:3.0\r\n")
        ->and($payload)->toContain("N:Patel;Jay;;;\r\n")
        ->and($payload)->toContain("FN:Jay Patel\r\n")
        ->and($payload)->toContain("TEL:+919879991799\r\n")
        ->and($payload)->toEndWith('END:VCARD')
        ->and(str_replace("\r\n", '', $payload))->not->toContain("\n");
});

// QrImageRenderer

test('render produces valid png bytes and logs a render row', function () {
    $qrCode = QrCode::factory()->url()->create();

    $result = app(QrImageRenderer::class)->render($qrCode, QrRenderFormat::Png, ipAddress: '203.0.113.5');

    expect($result['mimeType'])->toBe('image/png')
        ->and(substr($result['bytes'], 0, 8))->toBe("\x89PNG\r\n\x1a\n");

    $render = QrCodeRender::query()->where('qr_code_id', $qrCode->id)->firstOrFail();

    expect($render->format)->toBe(QrRenderFormat::Png)
        ->and($render->ip_address)->toBe('203.0.113.5');
});

test('render produces valid svg bytes and logs a render row', function () {
    $qrCode = QrCode::factory()->url()->create();

    $result = app(QrImageRenderer::class)->render($qrCode, QrRenderFormat::Svg);

    expect($result['mimeType'])->toBe('image/svg+xml')
        ->and(trim($result['bytes']))->toStartWith('<?xml')
        ->and($result['bytes'])->toContain('<svg');

    $render = QrCodeRender::query()->where('qr_code_id', $qrCode->id)->firstOrFail();

    expect($render->format)->toBe(QrRenderFormat::Svg)
        ->and($render->ip_address)->toBeNull();
});
