<?php

use App\Models\Operator;
use App\Services\Deployment\Release;
use Illuminate\Support\Facades\File;

/**
 * What the console says it is running. The point of the distinction is that a
 * bug report has to name the right code: a deployment somewhere after 1.0.0 is
 * not 1.0.0, and reporting it as one sends someone reading the wrong source.
 */
function writeManifest(array $manifest): void
{
    File::ensureDirectoryExists(storage_path('app'));
    File::put(storage_path('app/deployment.json'), json_encode($manifest));
}

afterEach(fn () => File::delete(storage_path('app/deployment.json')));

it('reads the release number from the VERSION file', function () {
    // One source of truth, and it is a file — a source archive, a shallow CI
    // checkout and a container built without .git have no tags to describe.
    expect(config('app.version'))->toBe(trim(File::get(base_path('VERSION'))))
        ->and(config('app.version'))->toMatch('/^\d+\.\d+\.\d+$/');
});

it('reports a pinned release as that release', function () {
    writeManifest([
        'version' => config('app.version'),
        'commit' => 'abc1234567890',
        'source' => 'refs/tags/v'.config('app.version'),
        'mode' => 'release',
        'deployed_at' => now()->toIso8601String(),
    ]);

    $release = Release::current();

    expect($release->isTaggedRelease)->toBeTrue()
        ->and($release->label())->toBe(config('app.version').' (abc1234)');
});

it('does not call a deployment after the release by the release\'s name', function () {
    // Every commit after v1.0.0 still carries VERSION=1.0.0 and is not 1.0.0.
    writeManifest([
        'commit' => 'def4567890123',
        'source' => 'main',
        'mode' => 'branch',
        'deployed_at' => now()->toIso8601String(),
    ]);

    $release = Release::current();

    expect($release->isTaggedRelease)->toBeFalse()
        ->and($release->label())->toBe(config('app.version').'-dev (def4567) · main');
});

it('keeps reporting the deployed version when an update fails halfway', function () {
    // The checkout moves before the migrations run, so a run that dies in
    // between leaves a newer VERSION on disk than is actually serving. Pairing
    // that number with the still-deployed commit would describe a build that
    // never existed.
    writeManifest([
        'version' => '0.9.0',
        'commit' => 'aaa1111222333',
        'source' => 'refs/tags/v0.9.0',
        'mode' => 'release',
        'deployed_at' => now()->subDay()->toIso8601String(),
    ]);

    $release = Release::current();

    expect($release->version)->toBe('0.9.0')
        ->and($release->version)->not->toBe(config('app.version'))
        // Still a clean release label: 0.9.0 is genuinely what is running.
        ->and($release->isTaggedRelease)->toBeTrue()
        ->and($release->label())->toBe('0.9.0 (aaa1111)');
});

it('says something sensible before anything has been deployed', function () {
    File::delete(storage_path('app/deployment.json'));

    expect(Release::current()->commit)->toBeNull()
        ->and(Release::current()->label())->toBe(config('app.version').'-dev');
});

it('does not take the console down over an unparseable timestamp', function () {
    writeManifest([
        'version' => '1.0.0',
        'commit' => 'abc1234567890',
        'source' => 'main',
        'mode' => 'branch',
        'deployed_at' => 'not-a-date',
    ]);

    // A line of small print in the sidebar must not 500 every admin page.
    expect(Release::current()->deployedAt)->toBeNull()
        ->and(Release::current()->label())->toBe('1.0.0-dev (abc1234) · main');

    $this->actingAs(Operator::factory()->role()->create(), 'operator')
        ->get(route('admin.overview'))
        ->assertOk();
});

it('survives a manifest that is not readable json', function () {
    File::ensureDirectoryExists(storage_path('app'));
    File::put(storage_path('app/deployment.json'), '{ half-written');

    // The console reporting nothing is a nuisance; the console 500ing because
    // a deployment was interrupted mid-write is an outage.
    expect(Release::current()->label())->toBe(config('app.version').'-dev');
});

it('shows the running version in the console', function () {
    writeManifest([
        'commit' => 'abc1234567890',
        'source' => 'refs/tags/v'.config('app.version'),
        'mode' => 'release',
        'deployed_at' => now()->toIso8601String(),
    ]);

    $this->actingAs(Operator::factory()->role()->create(), 'operator')
        ->get(route('admin.overview'))
        ->assertOk()
        ->assertSee(config('app.version').' (abc1234)');
});
