<?php

namespace Tests\Feature\ExpenseManagement;

use App\Models\ExpenseRequest;
use App\Models\ExpenseType;
use App\Models\Settings;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Spatie\Permission\Models\Role;
use Tests\TestCase;
use Tests\Traits\CreatesUsers;

class MyExpenseControllerTest extends TestCase
{
    use CreatesUsers, RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        Role::create(['name' => 'admin', 'guard_name' => 'web', 'is_web_access_enabled' => true]);
        Role::create(['name' => 'hr', 'guard_name' => 'web', 'is_web_access_enabled' => true]);
        Role::create(['name' => 'field_employee', 'guard_name' => 'web', 'is_web_access_enabled' => true]);

        // Create settings record required for views
        Settings::create([
            'currency_symbol' => '$',
            'app_name' => 'Test App',
        ]);
    }

    public function test_guest_cannot_access_my_expenses(): void
    {
        $response = $this->get(route('hrcore.my.expenses'));

        $response->assertRedirect('/auth/login');
    }

    public function test_employee_can_view_their_expenses(): void
    {
        $employee = $this->createFieldEmployee();
        $this->actingAs($employee);

        // Create an expense type for the view
        ExpenseType::factory()->create();

        $response = $this->get(route('hrcore.my.expenses'));

        $response->assertOk();
        $response->assertViewIs('expenses.my-expenses');
    }

    public function test_my_expenses_ajax_returns_only_user_expenses(): void
    {
        $employee = $this->createFieldEmployee();
        $otherEmployee = User::factory()->create();
        $expenseType = ExpenseType::factory()->create();

        // Create expenses for the logged-in user
        ExpenseRequest::factory()
            ->count(3)
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->create();

        // Create expenses for another user
        ExpenseRequest::factory()
            ->count(2)
            ->forUser($otherEmployee)
            ->forExpenseType($expenseType)
            ->create();

        $this->actingAs($employee);

        $response = $this->getJson(route('hrcore.my.expenses.datatable', [
            'draw' => 1,
            'start' => 0,
            'length' => 10,
        ]));

        $response->assertOk();
        $this->assertEquals(3, $response->json('recordsTotal'));
    }

    public function test_employee_can_create_expense_request(): void
    {
        $employee = $this->createFieldEmployee();
        $expenseType = ExpenseType::factory()->create();

        $this->actingAs($employee);

        $data = [
            'expense_type_id' => $expenseType->id,
            'for_date' => now()->format('Y-m-d'),
            'amount' => 150.50,
            'remarks' => 'Test expense',
        ];

        $response = $this->postJson(route('hrcore.my.expenses.store'), $data);

        $response->assertOk();
        $response->assertJson(['status' => 'success']);

        $this->assertDatabaseHas('expense_requests', [
            'user_id' => $employee->id,
            'expense_type_id' => $expenseType->id,
            'amount' => 150.50,
            'status' => 'pending',
        ]);
    }

    public function test_cannot_create_expense_with_invalid_amount(): void
    {
        $employee = $this->createFieldEmployee();
        $expenseType = ExpenseType::factory()->create();

        $this->actingAs($employee);

        $data = [
            'expense_type_id' => $expenseType->id,
            'for_date' => now()->format('Y-m-d'),
            'amount' => -50,
            'remarks' => 'Test expense',
        ];

        $response = $this->postJson(route('hrcore.my.expenses.store'), $data);

        $response->assertStatus(422);
        $response->assertJsonValidationErrors(['amount']);
    }

    public function test_cannot_create_expense_with_invalid_date(): void
    {
        $employee = $this->createFieldEmployee();
        $expenseType = ExpenseType::factory()->create();

        $this->actingAs($employee);

        $data = [
            'expense_type_id' => $expenseType->id,
            'for_date' => 'invalid-date',
            'amount' => 150.50,
            'remarks' => 'Test expense',
        ];

        $response = $this->postJson(route('hrcore.my.expenses.store'), $data);

        $response->assertStatus(422);
        $response->assertJsonValidationErrors(['for_date']);
    }

    public function test_cannot_create_expense_without_expense_type(): void
    {
        $employee = $this->createFieldEmployee();

        $this->actingAs($employee);

        $data = [
            'for_date' => now()->format('Y-m-d'),
            'amount' => 150.50,
            'remarks' => 'Test expense',
        ];

        $response = $this->postJson(route('hrcore.my.expenses.store'), $data);

        $response->assertStatus(422);
        $response->assertJsonValidationErrors(['expense_type_id']);
    }

    public function test_can_upload_expense_document(): void
    {
        Storage::fake('public');

        $employee = $this->createFieldEmployee();
        $expenseType = ExpenseType::factory()->create();

        $this->actingAs($employee);

        $file = UploadedFile::fake()->create('receipt.pdf', 100, 'application/pdf');

        $data = [
            'expense_type_id' => $expenseType->id,
            'for_date' => now()->format('Y-m-d'),
            'amount' => 150.50,
            'remarks' => 'Test expense',
            'document' => $file,
        ];

        $response = $this->postJson(route('hrcore.my.expenses.store'), $data);

        $response->assertOk();

        $expense = ExpenseRequest::where('user_id', $employee->id)->first();
        $this->assertNotNull($expense->document_url);
    }

    public function test_cannot_upload_invalid_file_type(): void
    {
        $employee = $this->createFieldEmployee();
        $expenseType = ExpenseType::factory()->create();

        $this->actingAs($employee);

        $file = UploadedFile::fake()->create('malicious.exe', 100);

        $data = [
            'expense_type_id' => $expenseType->id,
            'for_date' => now()->format('Y-m-d'),
            'amount' => 150.50,
            'remarks' => 'Test expense',
            'document' => $file,
        ];

        $response = $this->postJson(route('hrcore.my.expenses.store'), $data);

        $response->assertStatus(422);
        $response->assertJsonValidationErrors(['document']);
    }

    public function test_employee_can_view_own_expense_details(): void
    {
        $employee = $this->createFieldEmployee();
        $expenseType = ExpenseType::factory()->create();
        $expense = ExpenseRequest::factory()
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->create();

        $this->actingAs($employee);

        $response = $this->getJson(route('hrcore.my.expenses.show', $expense->id));

        $response->assertOk();
        $response->assertJson(['status' => 'success']);
        $response->assertJsonFragment(['id' => $expense->id]);
    }

    public function test_employee_cannot_view_other_user_expense(): void
    {
        $employee = $this->createFieldEmployee();
        $otherEmployee = User::factory()->create();
        $expenseType = ExpenseType::factory()->create();
        $expense = ExpenseRequest::factory()
            ->forUser($otherEmployee)
            ->forExpenseType($expenseType)
            ->create();

        $this->actingAs($employee);

        $response = $this->getJson(route('hrcore.my.expenses.show', $expense->id));

        $response->assertStatus(404);
    }

    public function test_employee_can_edit_pending_expense(): void
    {
        $employee = $this->createFieldEmployee();
        $expenseType = ExpenseType::factory()->create();
        $expense = ExpenseRequest::factory()
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->pending()
            ->create();

        $this->actingAs($employee);

        $response = $this->getJson(route('hrcore.my.expenses.edit', $expense->id));

        $response->assertOk();
        $response->assertJson(['status' => 'success']);
    }

    public function test_employee_cannot_edit_approved_expense(): void
    {
        $employee = $this->createFieldEmployee();
        $expenseType = ExpenseType::factory()->create();
        $expense = ExpenseRequest::factory()
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->approved()
            ->create();

        $this->actingAs($employee);

        $response = $this->getJson(route('hrcore.my.expenses.edit', $expense->id));

        $response->assertStatus(404);
    }

    public function test_employee_can_update_pending_expense(): void
    {
        $employee = $this->createFieldEmployee();
        $expenseType = ExpenseType::factory()->create();
        $expense = ExpenseRequest::factory()
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->pending()
            ->withAmount(100)
            ->create();

        $this->actingAs($employee);

        $data = [
            'expense_type_id' => $expenseType->id,
            'for_date' => now()->format('Y-m-d'),
            'amount' => 200,
            'remarks' => 'Updated expense',
        ];

        $response = $this->putJson(route('hrcore.my.expenses.update', $expense->id), $data);

        $response->assertOk();

        $expense->refresh();
        $this->assertEquals(200, $expense->amount);
        $this->assertEquals('Updated expense', $expense->remarks);
    }

    public function test_employee_cannot_update_approved_expense(): void
    {
        $employee = $this->createFieldEmployee();
        $expenseType = ExpenseType::factory()->create();
        $expense = ExpenseRequest::factory()
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->approved()
            ->create();

        $this->actingAs($employee);

        $data = [
            'expense_type_id' => $expenseType->id,
            'for_date' => now()->format('Y-m-d'),
            'amount' => 200,
            'remarks' => 'Updated expense',
        ];

        $response = $this->putJson(route('hrcore.my.expenses.update', $expense->id), $data);

        // Controller returns 400 when expense is not found in pending status
        $response->assertStatus(400);
    }

    public function test_employee_can_delete_pending_expense(): void
    {
        $employee = $this->createFieldEmployee();
        $expenseType = ExpenseType::factory()->create();
        $expense = ExpenseRequest::factory()
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->pending()
            ->create();

        $this->actingAs($employee);

        $response = $this->deleteJson(route('hrcore.my.expenses.delete', $expense->id));

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

    public function test_employee_cannot_delete_approved_expense(): void
    {
        $employee = $this->createFieldEmployee();
        $expenseType = ExpenseType::factory()->create();
        $expense = ExpenseRequest::factory()
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->approved()
            ->create();

        $this->actingAs($employee);

        $response = $this->deleteJson(route('hrcore.my.expenses.delete', $expense->id));

        // Controller returns 400 when expense is not found in pending status
        $response->assertStatus(400);
    }

    public function test_my_expenses_can_be_filtered_by_status(): void
    {
        $employee = $this->createFieldEmployee();
        $expenseType = ExpenseType::factory()->create();

        // Create expenses with different statuses
        ExpenseRequest::factory()
            ->count(2)
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->pending()
            ->create();

        ExpenseRequest::factory()
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->approved()
            ->create();

        $this->actingAs($employee);

        $response = $this->getJson(route('hrcore.my.expenses.datatable', [
            'draw' => 1,
            'start' => 0,
            'length' => 10,
            'statusFilter' => 'pending',
        ]));

        $response->assertOk();
        // The datatable returns all matching records, but filter should work
        $this->assertCount(2, collect($response->json('data'))->filter(fn ($item) => $item['status'] === 'pending'));
    }

    public function test_my_expenses_can_be_filtered_by_expense_type(): void
    {
        $employee = $this->createFieldEmployee();
        $expenseType1 = ExpenseType::factory()->create();
        $expenseType2 = ExpenseType::factory()->create();

        // Create expenses with different types
        ExpenseRequest::factory()
            ->count(2)
            ->forUser($employee)
            ->forExpenseType($expenseType1)
            ->create();

        ExpenseRequest::factory()
            ->forUser($employee)
            ->forExpenseType($expenseType2)
            ->create();

        $this->actingAs($employee);

        $response = $this->getJson(route('hrcore.my.expenses.datatable', [
            'draw' => 1,
            'start' => 0,
            'length' => 10,
            'expenseTypeFilter' => $expenseType1->id,
        ]));

        $response->assertOk();
        $this->assertEquals(2, $response->json('recordsTotal'));
    }
}
