<?php

declare(strict_types=1);

namespace Tests\Feature\Reports;

use App\Enums\AssetCondition;
use App\Enums\AssetStatus;
use App\Enums\AuditEvent;
use App\Enums\ImportStatus;
use App\Exports\ReportExport;
use App\Models\Asset;
use App\Models\AssetAssignment;
use App\Models\AssetCategory;
use App\Models\AssetWarranty;
use App\Models\AuditLog;
use App\Models\Employee;
use App\Models\ImportBatch;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Maatwebsite\Excel\Facades\Excel;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class ReportsTest extends TestCase
{
    use RefreshDatabase;

    /**
     * @return array<string, array{0: string}>
     */
    public static function reportKeys(): array
    {
        $keys = [
            'asset-register', 'assets-by-category', 'assets-by-team', 'assets-by-location',
            'assets-by-status', 'asset-ageing',
            'employee-assets', 'unassigned-employees', 'employees-multiple-assets', 'employee-assignment-history',
            'current-assignments', 'returned-assets', 'overdue-returns', 'asset-movement-history',
            'active-warranties', 'expiring-warranties', 'expired-warranties', 'warranty-renewal-history',
            'user-activity', 'asset-activity', 'assignment-activity', 'import-activity',
        ];

        return array_combine($keys, array_map(fn (string $key): array => [$key], $keys));
    }

    // ------------------------------------------------------------- access

    #[Test]
    public function a_guest_is_sent_to_the_sign_in_screen(): void
    {
        $this->get(route('reports.index'))->assertRedirect(route('login'));
        $this->get(route('reports.show', 'asset-register'))->assertRedirect(route('login'));
    }

    #[Test]
    public function any_signed_in_user_can_reach_reports_no_master_flag_required(): void
    {
        $this->actingAs(User::factory()->create())
            ->get(route('reports.index'))
            ->assertOk();

        $this->actingAs(User::factory()->create())
            ->get(route('reports.show', 'asset-register'))
            ->assertOk();
    }

    #[Test]
    public function the_landing_page_lists_every_category_with_its_reports(): void
    {
        $this->actingAs(User::factory()->create())
            ->get(route('reports.index'))
            ->assertOk()
            ->assertSee('Inventory Reports')
            ->assertSee('Employee Reports')
            ->assertSee('Assignment Reports')
            ->assertSee('Warranty Reports')
            ->assertSee('Audit Reports')
            ->assertSee('Complete Asset Register')
            ->assertSee('Overdue Returns')
            ->assertSee('Warranty Renewal History')
            ->assertSee('Import Activity');
    }

    #[Test]
    public function an_unknown_report_key_404s(): void
    {
        $this->actingAs(User::factory()->create())
            ->get(route('reports.show', 'not-a-real-report'))
            ->assertNotFound();
    }

    // -------------------------------------------------------- every report

    #[Test]
    #[DataProvider('reportKeys')]
    public function every_report_renders(string $key): void
    {
        Asset::factory()->create();
        Employee::factory()->create();

        $this->actingAs(User::factory()->create())
            ->get(route('reports.show', $key))
            ->assertOk();
    }

    #[Test]
    #[DataProvider('reportKeys')]
    public function every_report_exports_to_excel(string $key): void
    {
        Excel::fake();
        $this->travelTo($frozen = now());

        Asset::factory()->create();
        Employee::factory()->create();

        $this->actingAs(User::factory()->create())
            ->get(route('reports.export.excel', $key))
            ->assertOk();

        Excel::assertDownloaded(
            'aumni-ams-'.$key.'-'.$frozen->format('Y-m-d-His').'.xlsx',
        );
    }

    #[Test]
    #[DataProvider('reportKeys')]
    public function every_report_exports_to_pdf(string $key): void
    {
        Asset::factory()->create();
        Employee::factory()->create();

        $response = $this->actingAs(User::factory()->create())
            ->get(route('reports.export.pdf', $key));

        $response->assertOk();
        $this->assertSame('application/pdf', $response->headers->get('Content-Type'));
    }

    // ------------------------------------------------------------- filtering

    #[Test]
    public function the_asset_register_filters_by_search_and_status(): void
    {
        Asset::factory()->create(['asset_tag' => 'AUM-FIND-0001', 'status' => AssetStatus::InStock]);
        Asset::factory()->create(['asset_tag' => 'AUM-OTHER-0002', 'status' => AssetStatus::Lost]);

        $this->actingAs(User::factory()->create())
            ->get(route('reports.show', ['report' => 'asset-register', 'search' => 'FIND']))
            ->assertOk()
            ->assertSee('AUM-FIND-0001')
            ->assertDontSee('AUM-OTHER-0002');

        $this->actingAs(User::factory()->create())
            ->get(route('reports.show', ['report' => 'asset-register', 'status' => AssetStatus::Lost->value]))
            ->assertOk()
            ->assertDontSee('AUM-FIND-0001')
            ->assertSee('AUM-OTHER-0002');
    }

    #[Test]
    public function the_asset_register_sorts_by_purchase_cost(): void
    {
        Asset::factory()->create(['asset_tag' => 'AUM-CHEAP-01', 'purchase_cost' => 1000]);
        Asset::factory()->create(['asset_tag' => 'AUM-COSTLY-1', 'purchase_cost' => 90000]);

        $response = $this->actingAs(User::factory()->create())->get(route('reports.show', [
            'report' => 'asset-register', 'sort' => 'purchase_cost', 'direction' => 'desc',
        ]));

        $response->assertOk()->assertSeeInOrder(['AUM-COSTLY-1', 'AUM-CHEAP-01']);
    }

    #[Test]
    public function assets_by_status_groups_and_counts_correctly(): void
    {
        Asset::factory()->count(2)->create(['status' => AssetStatus::InStock]);
        Asset::factory()->create(['status' => AssetStatus::Lost]);

        $response = $this->actingAs(User::factory()->create())->get(route('reports.show', 'assets-by-status'));

        $response->assertOk()->assertSee('In Stock')->assertSee('Lost');
    }

    #[Test]
    public function the_ageing_report_buckets_assets_by_purchase_date(): void
    {
        Asset::factory()->create(['asset_tag' => 'AUM-NEW-0001', 'purchase_date' => now()->subMonths(2)]);
        Asset::factory()->create(['asset_tag' => 'AUM-OLD-0001', 'purchase_date' => now()->subYears(4)]);

        $this->actingAs(User::factory()->create())
            ->get(route('reports.show', ['report' => 'asset-ageing', 'age_bucket' => 'under_1']))
            ->assertOk()
            ->assertSee('AUM-NEW-0001')
            ->assertDontSee('AUM-OLD-0001');

        $this->actingAs(User::factory()->create())
            ->get(route('reports.show', ['report' => 'asset-ageing', 'age_bucket' => 'over_3']))
            ->assertOk()
            ->assertSee('AUM-OLD-0001')
            ->assertDontSee('AUM-NEW-0001');
    }

    #[Test]
    public function unassigned_employees_excludes_anyone_currently_holding_an_asset(): void
    {
        $holder = Employee::factory()->create(['first_name' => 'Holds', 'last_name' => 'Assets']);
        AssetAssignment::factory()->for($holder)->create();

        Employee::factory()->create(['first_name' => 'Has', 'last_name' => 'Nothing']);

        $this->actingAs(User::factory()->create())
            ->get(route('reports.show', 'unassigned-employees'))
            ->assertOk()
            ->assertSee('Has Nothing')
            ->assertDontSee('Holds Assets');
    }

    #[Test]
    public function employees_holding_multiple_assets_requires_at_least_two(): void
    {
        $multi = Employee::factory()->create(['first_name' => 'Multi', 'last_name' => 'Holder']);
        AssetAssignment::factory()->for($multi)->count(2)->create();

        $single = Employee::factory()->create(['first_name' => 'Single', 'last_name' => 'Holder']);
        AssetAssignment::factory()->for($single)->count(1)->create();

        $this->actingAs(User::factory()->create())
            ->get(route('reports.show', 'employees-multiple-assets'))
            ->assertOk()
            ->assertSee('Multi Holder')
            ->assertDontSee('Single Holder');
    }

    #[Test]
    public function overdue_returns_only_lists_active_assignments_past_their_expected_return(): void
    {
        $overdueAsset = Asset::factory()->create(['asset_tag' => 'AUM-OVERDUE-1', 'status' => AssetStatus::Assigned]);
        AssetAssignment::factory()->for($overdueAsset)->create([
            'expected_return_at' => now()->subDays(3),
        ]);

        $onTimeAsset = Asset::factory()->create(['asset_tag' => 'AUM-ONTIME-01', 'status' => AssetStatus::Assigned]);
        AssetAssignment::factory()->for($onTimeAsset)->create([
            'expected_return_at' => now()->addDays(10),
        ]);

        $this->actingAs(User::factory()->create())
            ->get(route('reports.show', 'overdue-returns'))
            ->assertOk()
            ->assertSee('AUM-OVERDUE-1')
            ->assertDontSee('AUM-ONTIME-01');
    }

    #[Test]
    public function current_assignments_excludes_returned_ones(): void
    {
        $active = Asset::factory()->create(['asset_tag' => 'AUM-ACTIVE-01']);
        AssetAssignment::factory()->for($active)->create();

        $returned = Asset::factory()->create(['asset_tag' => 'AUM-RETURN-01']);
        AssetAssignment::factory()->for($returned)->returned()->create();

        $this->actingAs(User::factory()->create())
            ->get(route('reports.show', 'current-assignments'))
            ->assertOk()
            ->assertSee('AUM-ACTIVE-01')
            ->assertDontSee('AUM-RETURN-01');
    }

    #[Test]
    public function expiring_warranties_respects_the_within_days_filter(): void
    {
        $soon = Asset::factory()->create(['asset_tag' => 'AUM-SOON-0001']);
        AssetWarranty::factory()->forAsset($soon)->primary()->expiringInDays(20)->create();

        $later = Asset::factory()->create(['asset_tag' => 'AUM-LATER-001']);
        AssetWarranty::factory()->forAsset($later)->primary()->expiringInDays(150)->create();

        $this->actingAs(User::factory()->create())
            ->get(route('reports.show', ['report' => 'expiring-warranties', 'within' => '30']))
            ->assertOk()
            ->assertSee('AUM-SOON-0001')
            ->assertDontSee('AUM-LATER-001');
    }

    #[Test]
    public function warranty_renewal_history_includes_superseded_coverage(): void
    {
        $asset = Asset::factory()->create(['asset_tag' => 'AUM-RENEW-001']);
        AssetWarranty::factory()->forAsset($asset)->create(['is_primary' => false, 'provider' => 'Old Provider Co']);
        AssetWarranty::factory()->forAsset($asset)->primary()->create(['provider' => 'Current Provider Co']);

        $this->actingAs(User::factory()->create())
            ->get(route('reports.show', 'warranty-renewal-history'))
            ->assertOk()
            ->assertSee('Old Provider Co')
            ->assertSee('Current Provider Co');
    }

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

        AuditLog::create([
            'user_id' => $user->id, 'event' => AuditEvent::Login, 'description' => 'Signed in test marker',
        ]);
        AuditLog::create([
            'user_id' => $user->id, 'event' => AuditEvent::Logout, 'description' => 'Signed out test marker',
        ]);

        $this->actingAs($user)
            ->get(route('reports.show', ['report' => 'user-activity', 'event' => AuditEvent::Login->value]))
            ->assertOk()
            ->assertSee('Signed in test marker')
            ->assertDontSee('Signed out test marker');
    }

    #[Test]
    public function import_activity_filters_by_status(): void
    {
        ImportBatch::factory()->completed()->create(['original_filename' => 'good-import.xlsx']);
        ImportBatch::factory()->failed()->create(['original_filename' => 'bad-import.xlsx']);

        $this->actingAs(User::factory()->create())
            ->get(route('reports.show', ['report' => 'import-activity', 'status' => ImportStatus::Failed->value]))
            ->assertOk()
            ->assertSee('bad-import.xlsx')
            ->assertDontSee('good-import.xlsx');
    }

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

        AuditLog::create([
            'auditable_type' => Asset::class, 'auditable_id' => $asset->id,
            'event' => AuditEvent::Updated, 'description' => 'Asset marker entry',
        ]);
        AuditLog::create([
            'auditable_type' => Employee::class, 'auditable_id' => 1,
            'event' => AuditEvent::Updated, 'description' => 'Employee marker entry',
        ]);

        $this->actingAs(User::factory()->create())
            ->get(route('reports.show', 'asset-activity'))
            ->assertOk()
            ->assertSee('Asset marker entry')
            ->assertDontSee('Employee marker entry');
    }

    // ------------------------------------------------------------- output

    #[Test]
    public function an_empty_report_shows_a_helpful_empty_state_not_a_blank_table(): void
    {
        $this->actingAs(User::factory()->create())
            ->get(route('reports.show', 'asset-register'))
            ->assertOk()
            ->assertSee('No data yet');
    }

    #[Test]
    public function a_filtered_report_with_no_matches_explains_that_rather_than_looking_broken(): void
    {
        Asset::factory()->create(['asset_tag' => 'AUM-REAL-0001']);

        $this->actingAs(User::factory()->create())
            ->get(route('reports.show', ['report' => 'asset-register', 'search' => 'nothing-will-match-this']))
            ->assertOk()
            ->assertSee('No records match these filters');
    }

    #[Test]
    public function the_pdf_export_refuses_a_report_larger_than_the_row_cap(): void
    {
        $categoryId = AssetCategory::factory()->create()->id;
        $now = now();

        $rows = [];

        for ($i = 0; $i < 2001; $i++) {
            $rows[] = [
                'uuid' => (string) Str::uuid(),
                'asset_tag' => 'AUM-BULK-'.str_pad((string) $i, 6, '0', STR_PAD_LEFT),
                'name' => 'Bulk Asset '.$i,
                'category_id' => $categoryId,
                'status' => AssetStatus::InStock->value,
                'condition' => AssetCondition::Good->value,
                'currency' => 'INR',
                'is_active' => true,
                'created_at' => $now,
                'updated_at' => $now,
            ];
        }

        foreach (array_chunk($rows, 500) as $chunk) {
            DB::table('assets')->insert($chunk);
        }

        $this->actingAs(User::factory()->create())
            ->get(route('reports.export.pdf', 'asset-register'))
            ->assertRedirect()
            ->assertSessionHas('error');
    }

    #[Test]
    public function exporting_excel_honours_the_current_search_filter(): void
    {
        Excel::fake();
        $this->travelTo($frozen = now());

        Asset::factory()->create(['asset_tag' => 'AUM-KEEP-0001']);
        Asset::factory()->create(['asset_tag' => 'AUM-DROP-0001']);

        $this->actingAs(User::factory()->create())
            ->get(route('reports.export.excel', ['report' => 'asset-register', 'search' => 'KEEP']))
            ->assertOk();

        Excel::assertDownloaded(
            'aumni-ams-asset-register-'.$frozen->format('Y-m-d-His').'.xlsx',
            function (ReportExport $export): bool {
                $rows = $export->query()->get();

                return $rows->count() === 1 && $rows->first()->asset_tag === 'AUM-KEEP-0001';
            },
        );
    }
}
