<?php

use App\Jobs\ServerStorageSaveJob;
use App\Models\Application;
use App\Models\Environment;
use App\Models\InstanceSettings;
use App\Models\LocalFileVolume;
use App\Models\LocalPersistentVolume;
use App\Models\Project;
use App\Models\Server;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\StandaloneDocker;
use App\Models\StandalonePostgresql;
use App\Models\Team;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Str;

uses(RefreshDatabase::class);

beforeEach(function () {
    config(['app.maintenance.store' => 'array', 'cache.default' => 'array']);
    Bus::fake();
    InstanceSettings::unguarded(fn () => InstanceSettings::updateOrCreate(['id' => 0], ['id' => 0]));

    $this->team = Team::factory()->create();
    $this->user = User::factory()->create();
    $this->team->members()->attach($this->user->id, ['role' => 'owner']);

    $plainTextToken = Str::random(40);
    $token = $this->user->tokens()->create([
        'name' => 'test-token',
        'token' => hash('sha256', $plainTextToken),
        'abilities' => ['*'],
        'team_id' => $this->team->id,
    ]);
    $this->bearerToken = $token->getKey().'|'.$plainTextToken;

    $this->server = Server::factory()->create(['team_id' => $this->team->id]);
    $this->destination = StandaloneDocker::where('server_id', $this->server->id)->first();
    $this->project = Project::factory()->create(['team_id' => $this->team->id]);
    $this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
});

function createTestApplication($context): Application
{
    return Application::create([
        'name' => 'test-storage-app',
        'git_repository' => 'https://github.com/test/test',
        'git_branch' => 'main',
        'build_pack' => 'nixpacks',
        'ports_exposes' => '3000',
        'environment_id' => $context->environment->id,
        'destination_id' => $context->destination->id,
        'destination_type' => $context->destination->getMorphClass(),
    ]);
}

function createTestDatabase($context): StandalonePostgresql
{
    return StandalonePostgresql::create([
        'name' => 'test-postgres',
        'image' => 'postgres:15-alpine',
        'postgres_user' => 'postgres',
        'postgres_password' => 'password',
        'postgres_db' => 'postgres',
        'environment_id' => $context->environment->id,
        'destination_id' => $context->destination->id,
        'destination_type' => $context->destination->getMorphClass(),
    ]);
}

function createStorageApiToken($context, array $abilities): string
{
    $plainTextToken = Str::random(40);
    $token = $context->user->tokens()->create([
        'name' => 'storage-test-token',
        'token' => hash('sha256', $plainTextToken),
        'abilities' => $abilities,
        'team_id' => $context->team->id,
    ]);

    return $token->getKey().'|'.$plainTextToken;
}

function createTestServiceApplication($context): array
{
    $service = Service::factory()->create([
        'environment_id' => $context->environment->id,
        'destination_id' => $context->destination->id,
        'destination_type' => $context->destination->getMorphClass(),
    ]);

    $serviceApplication = ServiceApplication::create([
        'uuid' => (string) Str::uuid(),
        'name' => 'test-service-app',
        'service_id' => $service->id,
        'image' => 'nginx:alpine',
    ]);

    return [$service, $serviceApplication];
}

// ──────────────────────────────────────────────────────────────
// Application Storage Endpoints
// ──────────────────────────────────────────────────────────────

describe('GET /api/v1/applications/{uuid}/storages', function () {
    test('lists storages for an application', function () {
        $app = createTestApplication($this);

        LocalPersistentVolume::create([
            'name' => $app->uuid.'-test-vol',
            'mount_path' => '/data',
            'resource_id' => $app->id,
            'resource_type' => $app->getMorphClass(),
        ]);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
        ])->getJson("/api/v1/applications/{$app->uuid}/storages");

        $response->assertStatus(200);
        $response->assertJsonCount(1, 'persistent_storages');
        $response->assertJsonCount(0, 'file_storages');
    });

    test('returns 404 for non-existent application', function () {
        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
        ])->getJson('/api/v1/applications/non-existent-uuid/storages');

        $response->assertStatus(404);
    });

    test('hides file storage content for read tokens and reveals it for sensitive tokens', function () {
        $app = createTestApplication($this);

        LocalFileVolume::create([
            'fs_path' => '/tmp/config.json',
            'mount_path' => '/app/config.json',
            'content' => '{"secret": "hidden"}',
            'is_directory' => false,
            'resource_id' => $app->id,
            'resource_type' => $app->getMorphClass(),
        ]);

        $readToken = createStorageApiToken($this, ['read']);
        $sensitiveToken = createStorageApiToken($this, ['read', 'read:sensitive']);

        $readResponse = $this->withHeaders([
            'Authorization' => 'Bearer '.$readToken,
        ])->getJson("/api/v1/applications/{$app->uuid}/storages");

        auth()->forgetGuards();

        $sensitiveResponse = $this->withHeaders([
            'Authorization' => 'Bearer '.$sensitiveToken,
        ])->getJson("/api/v1/applications/{$app->uuid}/storages");

        $readResponse->assertSuccessful();
        $sensitiveResponse->assertSuccessful();

        expect($readResponse->getContent())->not->toContain('"content":')
            ->and($sensitiveResponse->getContent())->toContain('"content":')
            ->and($sensitiveResponse->getContent())->toContain('hidden');
    });
});

describe('POST /api/v1/applications/{uuid}/storages', function () {
    test('creates a persistent storage', function () {
        $app = createTestApplication($this);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
            'Content-Type' => 'application/json',
        ])->postJson("/api/v1/applications/{$app->uuid}/storages", [
            'type' => 'persistent',
            'name' => 'my-volume',
            'mount_path' => '/data',
        ]);

        $response->assertStatus(201);

        $vol = LocalPersistentVolume::where('resource_id', $app->id)
            ->where('resource_type', $app->getMorphClass())
            ->first();

        expect($vol)->not->toBeNull();
        expect($vol->name)->toBe($app->uuid.'-my-volume');
        expect($vol->mount_path)->toBe('/data');
        expect($vol->uuid)->not->toBeNull();
    });

    test('creates a file storage', function () {
        $app = createTestApplication($this);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
            'Content-Type' => 'application/json',
        ])->postJson("/api/v1/applications/{$app->uuid}/storages", [
            'type' => 'file',
            'mount_path' => '/app/config.json',
            'content' => '{"key": "value"}',
        ]);

        $response->assertStatus(201);

        $vol = LocalFileVolume::where('resource_id', $app->id)
            ->where('resource_type', get_class($app))
            ->first();

        expect($vol)->not->toBeNull();
        expect($vol->mount_path)->toBe('/app/config.json');
        expect($vol->is_directory)->toBeFalse();
        expect($vol->fs_path)->toBe(application_configuration_dir().'/'.$app->uuid.'/app/config.json');
    });

    test('creates bind only host file storage for application', function () {
        $app = createTestApplication($this);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
            'Content-Type' => 'application/json',
        ])->postJson("/api/v1/applications/{$app->uuid}/storages", [
            'type' => 'file',
            'is_host_file' => true,
            'fs_path' => '/etc/nginx/nginx.conf',
            'mount_path' => '/etc/nginx/nginx.conf',
        ]);

        $response->assertStatus(201);

        $vol = LocalFileVolume::where('resource_id', $app->id)
            ->where('resource_type', get_class($app))
            ->first();

        expect($vol)->not->toBeNull();
        expect($vol->fs_path)->toBe('/etc/nginx/nginx.conf');
        expect($vol->mount_path)->toBe('/etc/nginx/nginx.conf');
        expect($vol->content)->toBeNull();
        expect($vol->is_host_file)->toBeTrue();
        expect($vol->is_directory)->toBeFalse();

        Bus::assertNotDispatched(ServerStorageSaveJob::class);
    });

    test('rejects file storage paths with parent segments', function () {
        $app = createTestApplication($this);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
            'Content-Type' => 'application/json',
        ])->postJson("/api/v1/applications/{$app->uuid}/storages", [
            'type' => 'file',
            'mount_path' => '/../../../../../../etc/example.conf',
            'content' => 'owned',
        ]);

        $response->assertStatus(422);
        $response->assertJsonPath('message', 'Validation failed.');

        expect(LocalFileVolume::where('resource_id', $app->id)
            ->where('resource_type', get_class($app))
            ->exists())->toBeFalse();
    });

    test('hides created file storage content for write tokens and reveals it for sensitive tokens', function () {
        $app = createTestApplication($this);
        $writeToken = createStorageApiToken($this, ['write']);
        $sensitiveToken = createStorageApiToken($this, ['write', 'read:sensitive']);

        $writeResponse = $this->withHeaders([
            'Authorization' => 'Bearer '.$writeToken,
            'Content-Type' => 'application/json',
        ])->postJson("/api/v1/applications/{$app->uuid}/storages", [
            'type' => 'file',
            'mount_path' => '/app/hidden.json',
            'content' => '{"secret": "write-hidden"}',
        ]);

        auth()->forgetGuards();

        $sensitiveResponse = $this->withHeaders([
            'Authorization' => 'Bearer '.$sensitiveToken,
            'Content-Type' => 'application/json',
        ])->postJson("/api/v1/applications/{$app->uuid}/storages", [
            'type' => 'file',
            'mount_path' => '/app/visible.json',
            'content' => '{"secret": "write-visible"}',
        ]);

        $writeResponse->assertCreated();
        $sensitiveResponse->assertCreated();

        expect($writeResponse->getContent())->not->toContain('"content":')
            ->and($sensitiveResponse->getContent())->toContain('"content":')
            ->and($sensitiveResponse->getContent())->toContain('write-visible');
    });

    test('rejects persistent storage without name', function () {
        $app = createTestApplication($this);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
            'Content-Type' => 'application/json',
        ])->postJson("/api/v1/applications/{$app->uuid}/storages", [
            'type' => 'persistent',
            'mount_path' => '/data',
        ]);

        $response->assertStatus(422);
    });

    test('rejects invalid type-specific fields', function () {
        $app = createTestApplication($this);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
            'Content-Type' => 'application/json',
        ])->postJson("/api/v1/applications/{$app->uuid}/storages", [
            'type' => 'persistent',
            'name' => 'vol',
            'mount_path' => '/data',
            'content' => 'should not be here',
        ]);

        $response->assertStatus(422);
    });
});

describe('PATCH /api/v1/applications/{uuid}/storages', function () {
    test('updates a persistent storage by uuid', function () {
        $app = createTestApplication($this);

        $vol = LocalPersistentVolume::create([
            'name' => $app->uuid.'-test-vol',
            'mount_path' => '/data',
            'resource_id' => $app->id,
            'resource_type' => $app->getMorphClass(),
        ]);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
            'Content-Type' => 'application/json',
        ])->patchJson("/api/v1/applications/{$app->uuid}/storages", [
            'uuid' => $vol->uuid,
            'type' => 'persistent',
            'mount_path' => '/new-data',
        ]);

        $response->assertStatus(200);
        expect($vol->fresh()->mount_path)->toBe('/new-data');
    });

    test('updates a persistent storage by id (backwards compat)', function () {
        $app = createTestApplication($this);

        $vol = LocalPersistentVolume::create([
            'name' => $app->uuid.'-test-vol',
            'mount_path' => '/data',
            'resource_id' => $app->id,
            'resource_type' => $app->getMorphClass(),
        ]);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
            'Content-Type' => 'application/json',
        ])->patchJson("/api/v1/applications/{$app->uuid}/storages", [
            'id' => $vol->id,
            'type' => 'persistent',
            'mount_path' => '/updated',
        ]);

        $response->assertStatus(200);
        expect($vol->fresh()->mount_path)->toBe('/updated');
    });

    test('returns 422 when neither uuid nor id is provided', function () {
        $app = createTestApplication($this);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
            'Content-Type' => 'application/json',
        ])->patchJson("/api/v1/applications/{$app->uuid}/storages", [
            'type' => 'persistent',
            'mount_path' => '/data',
        ]);

        $response->assertStatus(422);
    });
});

describe('DELETE /api/v1/applications/{uuid}/storages/{storage_uuid}', function () {
    test('deletes a persistent storage', function () {
        $app = createTestApplication($this);

        $vol = LocalPersistentVolume::create([
            'name' => $app->uuid.'-test-vol',
            'mount_path' => '/data',
            'resource_id' => $app->id,
            'resource_type' => $app->getMorphClass(),
        ]);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
        ])->deleteJson("/api/v1/applications/{$app->uuid}/storages/{$vol->uuid}");

        $response->assertStatus(200);
        $response->assertJson(['message' => 'Storage deleted.']);
        expect(LocalPersistentVolume::find($vol->id))->toBeNull();
    });

    test('finds file storage without type param and calls deleteStorageOnServer', function () {
        $app = createTestApplication($this);

        $vol = LocalFileVolume::create([
            'fs_path' => '/tmp/test',
            'mount_path' => '/app/config.json',
            'content' => '{}',
            'is_directory' => false,
            'resource_id' => $app->id,
            'resource_type' => get_class($app),
        ]);

        // Verify the storage is found via fileStorages (not persistentStorages)
        $freshApp = Application::find($app->id);
        expect($freshApp->persistentStorages->where('uuid', $vol->uuid)->first())->toBeNull();
        expect($freshApp->fileStorages->where('uuid', $vol->uuid)->first())->not->toBeNull();
        expect($vol)->toBeInstanceOf(LocalFileVolume::class);
    });

    test('returns 404 for non-existent storage', function () {
        $app = createTestApplication($this);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
        ])->deleteJson("/api/v1/applications/{$app->uuid}/storages/non-existent");

        $response->assertStatus(404);
    });
});

// ──────────────────────────────────────────────────────────────
// Database Storage Endpoints
// ──────────────────────────────────────────────────────────────

describe('GET /api/v1/databases/{uuid}/storages', function () {
    test('lists storages for a database', function () {
        $db = createTestDatabase($this);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
        ])->getJson("/api/v1/databases/{$db->uuid}/storages");

        $response->assertStatus(200);
        $response->assertJsonStructure(['persistent_storages', 'file_storages']);
        // Database auto-creates a default persistent volume
        $response->assertJsonCount(1, 'persistent_storages');
    });

    test('returns 404 for non-existent database', function () {
        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
        ])->getJson('/api/v1/databases/non-existent-uuid/storages');

        $response->assertStatus(404);
    });
});

describe('POST /api/v1/databases/{uuid}/storages', function () {
    test('creates a persistent storage for a database', function () {
        $db = createTestDatabase($this);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
            'Content-Type' => 'application/json',
        ])->postJson("/api/v1/databases/{$db->uuid}/storages", [
            'type' => 'persistent',
            'name' => 'extra-data',
            'mount_path' => '/extra',
        ]);

        $response->assertStatus(201);

        $vol = LocalPersistentVolume::where('name', $db->uuid.'-extra-data')->first();
        expect($vol)->not->toBeNull();
        expect($vol->mount_path)->toBe('/extra');
    });

    test('creates a file storage for a database under the database configuration root', function () {
        $db = createTestDatabase($this);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
            'Content-Type' => 'application/json',
        ])->postJson("/api/v1/databases/{$db->uuid}/storages", [
            'type' => 'file',
            'mount_path' => '/postgres/postgresql.conf',
            'content' => 'listen_addresses = "*"',
        ]);

        $response->assertStatus(201);

        $vol = LocalFileVolume::where('resource_id', $db->id)
            ->where('resource_type', get_class($db))
            ->first();

        expect($vol)->not->toBeNull();
        expect($vol->fs_path)->toBe(database_configuration_dir().'/'.$db->uuid.'/postgres/postgresql.conf');
    });

    test('rejects file storage paths with parent segments for a database', function () {
        $db = createTestDatabase($this);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
            'Content-Type' => 'application/json',
        ])->postJson("/api/v1/databases/{$db->uuid}/storages", [
            'type' => 'file',
            'mount_path' => '/postgres/../../../etc/shadow',
            'content' => 'owned',
        ]);

        $response->assertStatus(422);

        expect(LocalFileVolume::where('resource_id', $db->id)
            ->where('resource_type', get_class($db))
            ->exists())->toBeFalse();
    });
});

describe('POST /api/v1/services/{uuid}/storages', function () {
    test('creates a file storage for a service resource under the service configuration root', function () {
        [$service, $serviceApplication] = createTestServiceApplication($this);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
            'Content-Type' => 'application/json',
        ])->postJson("/api/v1/services/{$service->uuid}/storages", [
            'type' => 'file',
            'resource_uuid' => $serviceApplication->uuid,
            'mount_path' => '/etc/nginx/nginx.conf',
            'content' => 'server {}',
        ]);

        $response->assertStatus(201);

        $vol = LocalFileVolume::where('resource_id', $serviceApplication->id)
            ->where('resource_type', get_class($serviceApplication))
            ->first();

        expect($vol)->not->toBeNull();
        expect($vol->fs_path)->toBe(service_configuration_dir().'/'.$service->uuid.'/etc/nginx/nginx.conf');
    });

    test('rejects file storage paths with parent segments for a service resource', function () {
        [$service, $serviceApplication] = createTestServiceApplication($this);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
            'Content-Type' => 'application/json',
        ])->postJson("/api/v1/services/{$service->uuid}/storages", [
            'type' => 'file',
            'resource_uuid' => $serviceApplication->uuid,
            'mount_path' => '/../../../../../../root/.ssh/authorized_keys',
            'content' => 'owned',
        ]);

        $response->assertStatus(422);

        expect(LocalFileVolume::where('resource_id', $serviceApplication->id)
            ->where('resource_type', get_class($serviceApplication))
            ->exists())->toBeFalse();
    });
});

describe('PATCH /api/v1/databases/{uuid}/storages', function () {
    test('updates a persistent storage by uuid', function () {
        $db = createTestDatabase($this);

        $vol = LocalPersistentVolume::create([
            'name' => $db->uuid.'-test-vol',
            'mount_path' => '/data',
            'resource_id' => $db->id,
            'resource_type' => $db->getMorphClass(),
        ]);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
            'Content-Type' => 'application/json',
        ])->patchJson("/api/v1/databases/{$db->uuid}/storages", [
            'uuid' => $vol->uuid,
            'type' => 'persistent',
            'mount_path' => '/updated',
        ]);

        $response->assertStatus(200);
        expect($vol->fresh()->mount_path)->toBe('/updated');
    });
});

describe('DELETE /api/v1/databases/{uuid}/storages/{storage_uuid}', function () {
    test('deletes a persistent storage', function () {
        $db = createTestDatabase($this);

        $vol = LocalPersistentVolume::create([
            'name' => $db->uuid.'-test-vol',
            'mount_path' => '/extra',
            'resource_id' => $db->id,
            'resource_type' => $db->getMorphClass(),
        ]);

        $response = $this->withHeaders([
            'Authorization' => 'Bearer '.$this->bearerToken,
        ])->deleteJson("/api/v1/databases/{$db->uuid}/storages/{$vol->uuid}");

        $response->assertStatus(200);
        expect(LocalPersistentVolume::find($vol->id))->toBeNull();
    });
});
