<?php

declare(strict_types=1);

namespace Tests\Feature\Assets;

use App\Enums\AuditEvent;
use App\Models\Asset;
use App\Models\AssetCategory;
use App\Models\AssetDocument;
use App\Models\AuditLog;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class AssetDocumentManagementTest extends TestCase
{
    use RefreshDatabase;

    // ------------------------------------------------------- manual create

    #[Test]
    public function a_document_can_be_attached_while_creating_an_asset(): void
    {
        $user = User::factory()->create();
        $category = AssetCategory::factory()->create();

        $response = $this->actingAs($user)->post(route('assets.store'), [
            'name' => 'Dell Latitude 5440',
            'category_id' => $category->getKey(),
            'status' => 'in_stock',
            'condition' => 'new',
            'document_type' => 'purchase_order',
            'document' => UploadedFile::fake()->create('po.pdf', 200, 'application/pdf'),
        ]);

        $response->assertRedirect();

        $asset = Asset::query()->firstWhere('name', 'Dell Latitude 5440');
        $this->assertNotNull($asset);

        $document = AssetDocument::query()->where('asset_id', $asset->id)->firstOrFail();
        $this->assertSame('purchase_order', $document->document_type->value);
        $this->assertSame('po.pdf', $document->original_filename);
        $this->assertTrue(Storage::disk('local')->exists($document->stored_path));
    }

    #[Test]
    public function the_document_type_defaults_to_invoice_when_not_chosen(): void
    {
        $user = User::factory()->create();
        $category = AssetCategory::factory()->create();

        $this->actingAs($user)->post(route('assets.store'), [
            'name' => 'No Type Chosen',
            'category_id' => $category->getKey(),
            'status' => 'in_stock',
            'condition' => 'new',
            'document' => UploadedFile::fake()->create('invoice.pdf', 200, 'application/pdf'),
        ]);

        $asset = Asset::query()->firstWhere('name', 'No Type Chosen');
        $document = AssetDocument::query()->where('asset_id', $asset->id)->firstOrFail();

        $this->assertSame('invoice', $document->document_type->value);
    }

    #[Test]
    public function no_document_row_or_file_is_created_when_asset_creation_fails_validation(): void
    {
        $user = User::factory()->create();

        // Missing `name` and `category_id` — StoreAssetRequest rejects this
        // before AssetController::store() ever runs.
        $this->actingAs($user)->post(route('assets.store'), [
            'status' => 'in_stock',
            'condition' => 'new',
            'document' => UploadedFile::fake()->create('orphan.pdf', 200, 'application/pdf'),
        ]);

        $this->assertSame(0, AssetDocument::query()->count());
        $this->assertSame(0, Asset::query()->count());
    }

    #[Test]
    public function an_asset_can_be_created_with_no_document_at_all(): void
    {
        $user = User::factory()->create();
        $category = AssetCategory::factory()->create();

        $this->actingAs($user)->post(route('assets.store'), [
            'name' => 'No Document Asset',
            'category_id' => $category->getKey(),
            'status' => 'in_stock',
            'condition' => 'new',
        ])->assertRedirect();

        $this->assertDatabaseHas('assets', ['name' => 'No Document Asset']);
        $this->assertSame(0, AssetDocument::query()->count());
    }

    // -------------------------------------------------------- asset detail

    #[Test]
    public function a_document_can_be_uploaded_from_the_asset_detail_page(): void
    {
        $user = User::factory()->create();
        $asset = Asset::factory()->create();

        $response = $this->actingAs($user)->post(route('assets.documents.store', $asset), [
            'document_type' => 'delivery_challan',
            'document' => UploadedFile::fake()->create('dc.pdf', 200, 'application/pdf'),
        ]);

        $response->assertRedirect(route('assets.show', $asset));

        $document = AssetDocument::query()->where('asset_id', $asset->id)->firstOrFail();
        $this->assertSame('delivery_challan', $document->document_type->value);
        $this->assertSame($user->id, $document->uploaded_by);
    }

    #[Test]
    public function uploading_a_document_is_audited(): void
    {
        $user = User::factory()->create();
        $asset = Asset::factory()->create();

        $this->actingAs($user)->post(route('assets.documents.store', $asset), [
            'document_type' => 'invoice',
            'document' => UploadedFile::fake()->create('invoice.pdf', 200, 'application/pdf'),
        ]);

        $document = AssetDocument::query()->where('asset_id', $asset->id)->firstOrFail();

        $log = AuditLog::query()
            ->where('event', AuditEvent::Created->value)
            ->where('auditable_type', $document->getMorphClass())
            ->where('auditable_id', $document->id)
            ->first();

        $this->assertNotNull($log);
    }

    #[Test]
    public function an_invalid_file_type_is_rejected(): void
    {
        $user = User::factory()->create();
        $asset = Asset::factory()->create();

        $response = $this->actingAs($user)->post(route('assets.documents.store', $asset), [
            'document_type' => 'invoice',
            'document' => UploadedFile::fake()->create('malware.exe', 200, 'application/x-msdownload'),
        ]);

        $response->assertSessionHasErrors('document');
        $this->assertSame(0, AssetDocument::query()->count());
    }

    #[Test]
    public function an_oversized_file_is_rejected(): void
    {
        $user = User::factory()->create();
        $asset = Asset::factory()->create();

        $response = $this->actingAs($user)->post(route('assets.documents.store', $asset), [
            'document_type' => 'invoice',
            'document' => UploadedFile::fake()->create('huge.pdf', 10241, 'application/pdf'),
        ]);

        $response->assertSessionHasErrors('document');
        $this->assertSame(0, AssetDocument::query()->count());
    }

    #[Test]
    public function view_and_download_both_serve_the_file(): void
    {
        $user = User::factory()->create();
        $asset = Asset::factory()->create();

        $this->actingAs($user)->post(route('assets.documents.store', $asset), [
            'document_type' => 'invoice',
            'document' => UploadedFile::fake()->create('invoice.pdf', 200, 'application/pdf'),
        ]);

        $document = AssetDocument::query()->where('asset_id', $asset->id)->firstOrFail();

        $this->actingAs($user)->get(route('assets.documents.show', [$asset, $document]))->assertOk();
        $this->actingAs($user)->get(route('assets.documents.download', [$asset, $document]))->assertOk();
    }

    #[Test]
    public function the_response_never_exposes_the_internal_storage_path(): void
    {
        $user = User::factory()->create();
        $asset = Asset::factory()->create();

        $this->actingAs($user)->post(route('assets.documents.store', $asset), [
            'document_type' => 'invoice',
            'document' => UploadedFile::fake()->create('invoice.pdf', 200, 'application/pdf'),
        ]);

        $document = AssetDocument::query()->where('asset_id', $asset->id)->firstOrFail();

        $response = $this->actingAs($user)->get(route('assets.show', $asset));
        $response->assertOk();
        $response->assertDontSee($document->stored_path, escape: false);
    }

    #[Test]
    public function a_document_belonging_to_a_different_asset_404s(): void
    {
        $user = User::factory()->create();
        $assetA = Asset::factory()->create();
        $assetB = Asset::factory()->create();
        $document = AssetDocument::factory()->forAsset($assetB)->create();

        $this->actingAs($user)->get(route('assets.documents.show', [$assetA, $document]))->assertNotFound();
    }

    // -------------------------------------------------------------- delete

    #[Test]
    public function a_regular_user_cannot_delete_a_document(): void
    {
        $user = User::factory()->create();
        $asset = Asset::factory()->create();
        $document = AssetDocument::factory()->forAsset($asset)->create();

        $this->actingAs($user)->delete(route('assets.documents.destroy', [$asset, $document]))->assertForbidden();
        $this->assertDatabaseHas('asset_documents', ['id' => $document->id]);
    }

    #[Test]
    public function a_guest_is_redirected_to_login(): void
    {
        $asset = Asset::factory()->create();
        $document = AssetDocument::factory()->forAsset($asset)->create();

        $this->get(route('assets.documents.show', [$asset, $document]))->assertRedirect(route('login'));
        $this->delete(route('assets.documents.destroy', [$asset, $document]))->assertRedirect(route('login'));
    }

    #[Test]
    public function a_master_can_delete_a_document_and_the_file_is_removed(): void
    {
        $master = User::factory()->master()->create();
        $asset = Asset::factory()->create();

        $this->actingAs($master)->post(route('assets.documents.store', $asset), [
            'document_type' => 'invoice',
            'document' => UploadedFile::fake()->create('invoice.pdf', 200, 'application/pdf'),
        ]);

        $document = AssetDocument::query()->where('asset_id', $asset->id)->firstOrFail();
        $path = $document->stored_path;

        $response = $this->actingAs($master)->delete(route('assets.documents.destroy', [$asset, $document]));
        $response->assertRedirect(route('assets.show', $asset));

        $this->assertDatabaseMissing('asset_documents', ['id' => $document->id]);
        $this->assertFalse(Storage::disk('local')->exists($path));
    }

    #[Test]
    public function deleting_a_document_is_audited(): void
    {
        $master = User::factory()->master()->create();
        $asset = Asset::factory()->create();
        $document = AssetDocument::factory()->forAsset($asset)->create();

        $this->actingAs($master)->delete(route('assets.documents.destroy', [$asset, $document]));

        $log = AuditLog::query()
            ->where('event', AuditEvent::Deleted->value)
            ->where('auditable_type', $document->getMorphClass())
            ->where('auditable_id', $document->id)
            ->first();

        $this->assertNotNull($log);
    }
}
