<?php

declare(strict_types=1);

namespace Tests\Feature;

use App\Enums\AssetStatus;
use App\Enums\EmployeeStatus;
use App\Models\Asset;
use App\Models\AssetAssignment;
use App\Models\AssetWarranty;
use App\Models\Employee;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class DashboardTest extends TestCase
{
    use RefreshDatabase;

    #[Test]
    public function a_guest_is_sent_to_the_sign_in_screen(): void
    {
        $this->get(route('dashboard'))->assertRedirect(route('login'));
    }

    #[Test]
    public function a_signed_in_user_sees_the_dashboard(): void
    {
        $user = User::factory()->create(['name' => 'Priya Sharma']);

        // The greeting is time-aware ("Good morning" / "Good afternoon" /
        // "Good evening") and uses the first name only, so the assertion is on
        // the part that is actually stable: that the user is greeted by name.
        $this->actingAs($user)
            ->get(route('dashboard'))
            ->assertOk()
            ->assertSee(', Priya');
    }

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

        $this->actingAs($user)->get(route('dashboard'))->assertOk();

        $user->update(['is_active' => false]);

        $this->get(route('dashboard'))->assertRedirect(config('ams.auth.login_path'));
        $this->assertGuest();
    }

    #[Test]
    public function a_master_sees_the_user_account_summary(): void
    {
        User::factory()->count(3)->create();
        User::factory()->disabled()->create();

        $this->actingAs(User::factory()->master()->create())
            ->get(route('dashboard'))
            ->assertOk()
            ->assertSee('User Accounts')
            ->assertSee('Master Users');
    }

    #[Test]
    public function a_regular_user_does_not_see_the_user_account_summary(): void
    {
        $this->actingAs(User::factory()->create())
            ->get(route('dashboard'))
            ->assertOk()
            ->assertDontSee('User Accounts');
    }

    #[Test]
    public function the_counts_are_accurate(): void
    {
        $master = User::factory()->master()->create();
        User::factory()->count(2)->create();
        User::factory()->disabled()->create();
        User::factory()->mustChangePassword()->create();

        $response = $this->actingAs($master)->get(route('dashboard'));

        $stats = $response->viewData('userStats');

        // 1 master + 2 regular + 1 disabled + 1 with a temporary password.
        $this->assertSame(5, $stats['total']);
        $this->assertSame(4, $stats['active']);
        $this->assertSame(1, $stats['disabled']);
        $this->assertSame(1, $stats['masters']);
        $this->assertSame(1, $stats['pendingPasswordChange']);
    }

    #[Test]
    public function masters_are_warned_about_unchanged_temporary_passwords(): void
    {
        User::factory()->mustChangePassword()->create();

        $this->actingAs(User::factory()->master()->create())
            ->get(route('dashboard'))
            ->assertOk()
            ->assertSee('temporary password that has not been changed');
    }

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

        Employee::factory()->count(2)->create(['status' => EmployeeStatus::Active]);
        Employee::factory()->create(['status' => EmployeeStatus::OnNotice]);
        Employee::factory()->create(['status' => EmployeeStatus::Exited]);

        // A status that no KPI besides the total-assets count tracks, so
        // these two extra assets (created only to carry a warranty) can't be
        // mistaken for contributing to available/assigned/under-repair/etc.
        $expiringAsset = Asset::factory()->create(['status' => AssetStatus::Damaged]);
        AssetWarranty::factory()->forAsset($expiringAsset)->primary()->expiringInDays(10)->create();

        $expiredAsset = Asset::factory()->create(['status' => AssetStatus::Damaged]);
        AssetWarranty::factory()->forAsset($expiredAsset)->primary()->expired()->create();

        $response = $this->actingAs(User::factory()->create())->get(route('dashboard'));

        $kpis = $response->viewData('kpis');

        $this->assertSame(9, $kpis['total_assets']['value']);
        $this->assertSame(1, $kpis['assigned_assets']['value']);
        $this->assertSame(2, $kpis['available_assets']['value']);
        $this->assertSame(1, $kpis['under_repair']['value']);
        $this->assertSame(1, $kpis['lost']['value']);
        $this->assertSame(1, $kpis['retired']['value']);
        $this->assertSame(1, $kpis['warranty_expiring']['value']);
        $this->assertSame(1, $kpis['warranty_expired']['value']);
        $this->assertSame(4, $kpis['total_employees']['value']);
        $this->assertSame(2, $kpis['active_employees']['value']);
    }

    #[Test]
    public function an_empty_system_shows_empty_states_instead_of_broken_widgets(): void
    {
        // Recent Activity is deliberately not asserted here: signing in at
        // all requires a User row, and User is itself Auditable, so its own
        // "Created User ..." entry means that one widget is never truly
        // empty once a session exists to view the dashboard with.
        $this->actingAs(User::factory()->create())
            ->get(route('dashboard'))
            ->assertOk()
            ->assertSee('No assets recorded yet')
            ->assertSee('No warranty coverage recorded yet')
            ->assertSee('No purchase dates recorded yet')
            ->assertSee('No assignments yet')
            ->assertSee('No returns yet')
            ->assertSee('Nothing overdue')
            ->assertSee('Nothing expiring soon')
            ->assertSee('No assets currently issued');
    }

    #[Test]
    public function recently_assigned_and_returned_assets_are_listed(): void
    {
        $assigned = Asset::factory()->create(['asset_tag' => 'AUM-DSH-0001']);
        AssetAssignment::factory()->for($assigned)->create();

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

        $this->actingAs(User::factory()->create())
            ->get(route('dashboard'))
            ->assertOk()
            ->assertSee('AUM-DSH-0001')
            ->assertSee('AUM-DSH-0002');
    }

    #[Test]
    public function assets_overdue_for_return_are_flagged(): void
    {
        $asset = Asset::factory()->create(['asset_tag' => 'AUM-DSH-0003', 'status' => AssetStatus::Assigned]);

        AssetAssignment::factory()->for($asset)->create([
            'assigned_at' => now()->subDays(20),
            'expected_return_at' => now()->subDays(5),
        ]);

        $this->actingAs(User::factory()->create())
            ->get(route('dashboard'))
            ->assertOk()
            ->assertSee('AUM-DSH-0003')
            // Whole days only — a fractional "5.6931…d overdue" is what
            // shipped before diffInDays()'s float result was cast to int.
            ->assertSee('5d overdue');
    }

    #[Test]
    public function upcoming_warranty_expiry_is_listed(): void
    {
        $asset = Asset::factory()->create(['asset_tag' => 'AUM-DSH-0004']);
        AssetWarranty::factory()->forAsset($asset)->primary()->expiringInDays(15)->create();

        $this->actingAs(User::factory()->create())
            ->get(route('dashboard'))
            ->assertOk()
            ->assertSee('AUM-DSH-0004');
    }

    #[Test]
    public function the_employee_holding_the_most_assets_is_ranked_first(): void
    {
        $topHolder = Employee::factory()->create(['first_name' => 'Ananya', 'last_name' => 'Rao']);
        AssetAssignment::factory()->for($topHolder)->count(3)->create();

        $otherHolder = Employee::factory()->create(['first_name' => 'Vikram', 'last_name' => 'Iyer']);
        AssetAssignment::factory()->for($otherHolder)->count(1)->create();

        $this->actingAs(User::factory()->create())
            ->get(route('dashboard'))
            ->assertOk()
            ->assertSeeInOrder(['Ananya Rao', 'Vikram Iyer']);
    }

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

        $user = User::factory()->create();

        $first = $this->actingAs($user)->get(route('dashboard'));
        $this->assertSame(1, $first->viewData('kpis')['total_assets']['value']);

        // A second asset created after the first request should not change
        // the count until the cache entry expires — this is what makes the
        // dashboard fast under real load instead of running ten aggregate
        // queries on every page view.
        Asset::factory()->create(['status' => AssetStatus::InStock]);

        $second = $this->actingAs($user)->get(route('dashboard'));
        $this->assertSame(1, $second->viewData('kpis')['total_assets']['value']);
    }

    #[Test]
    public function quick_actions_link_to_the_right_screens(): void
    {
        $this->actingAs(User::factory()->create())
            ->get(route('dashboard'))
            ->assertOk()
            ->assertSee(route('assets.create'), false)
            ->assertSee(route('imports.create'), false)
            ->assertSee(route('assignments.create'), false)
            ->assertSee(route('employees.create'), false)
            ->assertSee(route('warranty.expiring'), false);
    }
}
