<?php

namespace Tests\Unit\Models;

use App\Models\ExpenseRequest;
use App\Models\ExpenseType;
use App\Models\User;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Testing\RefreshDatabase;
use OwenIt\Auditing\Auditable;
use Tests\TestCase;

class ExpenseRequestTest extends TestCase
{
    use RefreshDatabase;

    public function test_expense_request_has_correct_fillable_attributes(): void
    {
        $expenseRequest = new ExpenseRequest;
        $expectedFillable = [
            'user_id',
            'for_date',
            'expense_type_id',
            'document_url',
            'remarks',
            'amount',
            'approved_amount',
            'status',
            'approved_at',
            'approved_by_id',
            'rejected_at',
            'rejected_by_id',
            'admin_remarks',
            'processed_in_payroll',
            'payroll_record_id',
            'payroll_processed_at',
            'created_by_id',
            'updated_by_id',
        ];

        $this->assertEquals($expectedFillable, $expenseRequest->getFillable());
    }

    public function test_expense_request_has_correct_casts(): void
    {
        $expenseRequest = new ExpenseRequest;
        $casts = $expenseRequest->getCasts();

        $this->assertArrayHasKey('for_date', $casts);
        $this->assertArrayHasKey('approved_at', $casts);
        $this->assertArrayHasKey('rejected_at', $casts);
        $this->assertArrayHasKey('created_at', $casts);
        $this->assertArrayHasKey('payroll_processed_at', $casts);
        $this->assertArrayHasKey('amount', $casts);
        $this->assertArrayHasKey('approved_amount', $casts);
        $this->assertArrayHasKey('processed_in_payroll', $casts);

        $this->assertEquals('float', $casts['amount']);
        $this->assertEquals('float', $casts['approved_amount']);
        $this->assertEquals('boolean', $casts['processed_in_payroll']);
    }

    public function test_expense_request_casts_dates_correctly(): void
    {
        $expenseRequest = ExpenseRequest::factory()->create([
            'for_date' => '2024-01-15',
        ]);

        $this->assertInstanceOf(\Illuminate\Support\Carbon::class, $expenseRequest->for_date);
        $this->assertEquals('2024-01-15', $expenseRequest->for_date->format('Y-m-d'));
    }

    public function test_expense_request_uses_soft_deletes(): void
    {
        $expenseRequest = new ExpenseRequest;
        $traits = class_uses_recursive($expenseRequest);

        $this->assertContains(SoftDeletes::class, $traits);
    }

    public function test_expense_request_uses_auditable_trait(): void
    {
        $expenseRequest = new ExpenseRequest;
        $traits = class_uses_recursive($expenseRequest);

        $this->assertContains(Auditable::class, $traits);
    }

    public function test_expense_request_belongs_to_user(): void
    {
        $user = User::factory()->create();
        $expenseRequest = ExpenseRequest::factory()->forUser($user)->create();

        $this->assertInstanceOf(\Illuminate\Database\Eloquent\Relations\BelongsTo::class, $expenseRequest->user());
        $this->assertEquals($user->id, $expenseRequest->user->id);
    }

    public function test_expense_request_belongs_to_expense_type(): void
    {
        $expenseType = ExpenseType::factory()->create();
        $expenseRequest = ExpenseRequest::factory()->forExpenseType($expenseType)->create();

        $this->assertInstanceOf(\Illuminate\Database\Eloquent\Relations\BelongsTo::class, $expenseRequest->expenseType());
        $this->assertEquals($expenseType->id, $expenseRequest->expenseType->id);
    }

    public function test_expense_request_belongs_to_approved_by(): void
    {
        $approver = User::factory()->create();
        $expenseRequest = ExpenseRequest::factory()->approved()->create([
            'approved_by_id' => $approver->id,
        ]);

        $this->assertInstanceOf(\Illuminate\Database\Eloquent\Relations\BelongsTo::class, $expenseRequest->approvedBy());
        $this->assertEquals($approver->id, $expenseRequest->approvedBy->id);
    }

    public function test_expense_request_belongs_to_rejected_by(): void
    {
        $rejecter = User::factory()->create();
        $expenseRequest = ExpenseRequest::factory()->rejected()->create([
            'rejected_by_id' => $rejecter->id,
        ]);

        $this->assertInstanceOf(\Illuminate\Database\Eloquent\Relations\BelongsTo::class, $expenseRequest->rejectedBy());
        $this->assertEquals($rejecter->id, $expenseRequest->rejectedBy->id);
    }

    public function test_expense_request_has_many_items(): void
    {
        $expenseRequest = ExpenseRequest::factory()->create();

        $this->assertInstanceOf(\Illuminate\Database\Eloquent\Relations\HasMany::class, $expenseRequest->items());
    }

    public function test_expense_request_can_be_created_with_factory(): void
    {
        $expenseRequest = ExpenseRequest::factory()->create();

        $this->assertDatabaseHas('expense_requests', [
            'id' => $expenseRequest->id,
        ]);
    }

    public function test_expense_request_factory_pending_state(): void
    {
        $expenseRequest = ExpenseRequest::factory()->pending()->create();

        $this->assertEquals('pending', $expenseRequest->status);
        $this->assertNull($expenseRequest->approved_at);
        $this->assertNull($expenseRequest->approved_by_id);
    }

    public function test_expense_request_factory_approved_state(): void
    {
        $expenseRequest = ExpenseRequest::factory()->approved()->create();

        $this->assertEquals('approved', $expenseRequest->status);
        $this->assertNotNull($expenseRequest->approved_at);
        $this->assertNotNull($expenseRequest->approved_by_id);
        $this->assertNotNull($expenseRequest->approved_amount);
    }

    public function test_expense_request_factory_rejected_state(): void
    {
        $expenseRequest = ExpenseRequest::factory()->rejected()->create();

        $this->assertEquals('rejected', $expenseRequest->status);
        $this->assertNotNull($expenseRequest->rejected_at);
        $this->assertNotNull($expenseRequest->rejected_by_id);
        $this->assertNotNull($expenseRequest->admin_remarks);
    }

    public function test_expense_request_factory_cancelled_state(): void
    {
        $expenseRequest = ExpenseRequest::factory()->cancelled()->create();

        $this->assertEquals('cancelled', $expenseRequest->status);
    }

    public function test_expense_request_factory_with_document_state(): void
    {
        $expenseRequest = ExpenseRequest::factory()->withDocument()->create();

        $this->assertNotNull($expenseRequest->document_url);
    }

    public function test_expense_request_can_be_soft_deleted(): void
    {
        $expenseRequest = ExpenseRequest::factory()->create();
        $expenseRequest->delete();

        $this->assertSoftDeleted('expense_requests', [
            'id' => $expenseRequest->id,
        ]);
    }

    public function test_expense_request_casts_amount_to_float(): void
    {
        $expenseRequest = ExpenseRequest::factory()->withAmount(150.50)->create();

        $this->assertIsFloat($expenseRequest->amount);
        $this->assertEquals(150.50, $expenseRequest->amount);
    }

    public function test_expense_request_casts_processed_in_payroll_to_boolean(): void
    {
        $expenseRequest = ExpenseRequest::factory()->create(['processed_in_payroll' => 1]);

        $this->assertIsBool($expenseRequest->processed_in_payroll);
        $this->assertTrue($expenseRequest->processed_in_payroll);
    }
}
