<?php

namespace Tests\Feature\ExpenseManagement;

use App\Models\ExpenseRequest;
use App\Models\ExpenseType;
use App\Models\Settings;
use App\Models\User;
use App\Notifications\Expense\ExpenseRequestApproval;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Notification;
use Spatie\Permission\Models\Role;
use Tests\TestCase;
use Tests\Traits\CreatesUsers;

class ExpenseAdminControllerTest 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']);

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

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

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

    public function test_admin_can_view_all_expenses(): void
    {
        $this->actingAsAdmin();

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

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

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

    public function test_expenses_ajax_returns_paginated_data(): void
    {
        $this->actingAsAdmin();

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

        // Create 15 expenses
        ExpenseRequest::factory()
            ->count(15)
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->create();

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

        $response->assertOk();
        $response->assertJsonStructure([
            'draw',
            'recordsTotal',
            'recordsFiltered',
            'data',
        ]);
        $this->assertEquals(15, $response->json('recordsTotal'));
        $this->assertCount(10, $response->json('data'));
    }

    public function test_expenses_can_be_filtered_by_employee(): void
    {
        $this->actingAsAdmin();

        $expenseType = ExpenseType::factory()->create();
        $employee1 = User::factory()->create();
        $employee2 = User::factory()->create();

        // Create expenses for different employees
        ExpenseRequest::factory()
            ->count(3)
            ->forUser($employee1)
            ->forExpenseType($expenseType)
            ->create();

        ExpenseRequest::factory()
            ->count(2)
            ->forUser($employee2)
            ->forExpenseType($expenseType)
            ->create();

        $response = $this->getJson(route('hrcore.expenses.datatable', [
            'draw' => 1,
            'start' => 0,
            'length' => 10,
            'order' => [['column' => 1, 'dir' => 'desc']],
            'employeeFilter' => $employee1->id,
        ]));

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

    public function test_expenses_can_be_filtered_by_status(): void
    {
        $this->actingAsAdmin();

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

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

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

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

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

    public function test_expenses_can_be_filtered_by_date(): void
    {
        $this->actingAsAdmin();

        $expenseType = ExpenseType::factory()->create();
        $employee = User::factory()->create();
        $targetDate = now()->subDays(5);

        // Create expenses for the target date
        ExpenseRequest::factory()
            ->count(2)
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->create(['for_date' => $targetDate]);

        // Create expenses for other dates
        ExpenseRequest::factory()
            ->count(3)
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->create(['for_date' => now()->subDays(10)]);

        $response = $this->getJson(route('hrcore.expenses.datatable', [
            'draw' => 1,
            'start' => 0,
            'length' => 10,
            'order' => [['column' => 1, 'dir' => 'desc']],
            'dateFilter' => $targetDate->format('Y-m-d'),
        ]));

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

    public function test_expenses_can_be_filtered_by_expense_type(): void
    {
        $this->actingAsAdmin();

        $expenseType1 = ExpenseType::factory()->create();
        $expenseType2 = ExpenseType::factory()->create();
        $employee = User::factory()->create();

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

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

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

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

    public function test_admin_can_approve_expense(): void
    {
        Notification::fake();

        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $expenseType = ExpenseType::factory()->create();
        $employee = User::factory()->create();
        $expense = ExpenseRequest::factory()
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->pending()
            ->withAmount(150)
            ->create();

        $response = $this->postJson(route('hrcore.expenses.action', $expense->id), [
            'id' => $expense->id,
            'status' => 'approved',
            'approvedAmount' => 150,
            'adminRemarks' => 'Approved for full amount',
        ]);

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

        $expense->refresh();
        $this->assertEquals('approved', $expense->status);
        $this->assertEquals(150, $expense->approved_amount);
        $this->assertEquals($admin->id, $expense->approved_by_id);
        $this->assertNotNull($expense->approved_at);

        Notification::assertSentTo($employee, ExpenseRequestApproval::class);
    }

    public function test_admin_can_approve_with_different_amount(): void
    {
        Notification::fake();

        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $expenseType = ExpenseType::factory()->create();
        $employee = User::factory()->create();
        $expense = ExpenseRequest::factory()
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->pending()
            ->withAmount(200)
            ->create();

        $response = $this->postJson(route('hrcore.expenses.action', $expense->id), [
            'id' => $expense->id,
            'status' => 'approved',
            'approvedAmount' => 150,
            'adminRemarks' => 'Partial approval',
        ]);

        $response->assertOk();

        $expense->refresh();
        $this->assertEquals('approved', $expense->status);
        $this->assertEquals(150, $expense->approved_amount);
    }

    public function test_admin_cannot_approve_with_zero_amount(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $expenseType = ExpenseType::factory()->create();
        $employee = User::factory()->create();
        $expense = ExpenseRequest::factory()
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->pending()
            ->withAmount(200)
            ->create();

        $response = $this->postJson(route('hrcore.expenses.action', $expense->id), [
            'id' => $expense->id,
            'status' => 'approved',
            'approvedAmount' => 0,
        ]);

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

        $expense->refresh();
        $this->assertEquals('pending', $expense->status);
    }

    public function test_admin_can_reject_expense(): void
    {
        Notification::fake();

        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $expenseType = ExpenseType::factory()->create();
        $employee = User::factory()->create();
        $expense = ExpenseRequest::factory()
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->pending()
            ->create();

        $response = $this->postJson(route('hrcore.expenses.action', $expense->id), [
            'id' => $expense->id,
            'status' => 'rejected',
            'adminRemarks' => 'Missing receipt',
        ]);

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

        $expense->refresh();
        $this->assertEquals('rejected', $expense->status);
        $this->assertEquals($admin->id, $expense->rejected_by_id);
        $this->assertNotNull($expense->rejected_at);
        $this->assertEquals('Missing receipt', $expense->admin_remarks);
        $this->assertNull($expense->approved_amount);

        Notification::assertSentTo($employee, ExpenseRequestApproval::class);
    }

    public function test_cannot_approve_nonexistent_expense(): void
    {
        $this->actingAsAdmin();

        $response = $this->postJson(route('hrcore.expenses.action', 99999), [
            'id' => 99999,
            'status' => 'approved',
        ]);

        $response->assertStatus(422);
    }

    public function test_get_by_id_returns_expense_details(): void
    {
        $this->actingAsAdmin();

        $expenseType = ExpenseType::factory()->create();
        $employee = User::factory()->create();
        $expense = ExpenseRequest::factory()
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->create();

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

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

    public function test_get_by_id_returns_404_for_nonexistent_expense(): void
    {
        $this->actingAsAdmin();

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

        $response->assertStatus(404);
    }

    public function test_approval_defaults_to_requested_amount(): void
    {
        Notification::fake();

        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $expenseType = ExpenseType::factory()->create();
        $employee = User::factory()->create();
        $expense = ExpenseRequest::factory()
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->pending()
            ->withAmount(175.50)
            ->create();

        $response = $this->postJson(route('hrcore.expenses.action', $expense->id), [
            'id' => $expense->id,
            'status' => 'approved',
            // No approvedAmount provided - should default to requested amount
        ]);

        $response->assertOk();

        $expense->refresh();
        $this->assertEquals(175.50, $expense->approved_amount);
    }

    public function test_rejection_clears_approved_amount(): void
    {
        Notification::fake();

        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $expenseType = ExpenseType::factory()->create();
        $employee = User::factory()->create();
        $expense = ExpenseRequest::factory()
            ->forUser($employee)
            ->forExpenseType($expenseType)
            ->pending()
            ->create();

        $response = $this->postJson(route('hrcore.expenses.action', $expense->id), [
            'id' => $expense->id,
            'status' => 'rejected',
            'adminRemarks' => 'Rejected',
        ]);

        $response->assertOk();

        $expense->refresh();
        $this->assertNull($expense->approved_amount);
    }

    public function test_datatable_supports_search(): void
    {
        $this->actingAsAdmin();

        $expenseType = ExpenseType::factory()->create(['name' => 'Travel']);
        // Use a unique name that won't appear randomly in faker
        $employee = User::factory()->create(['first_name' => 'UniqueTestName', 'last_name' => 'ForSearchTest']);

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

        // Create expenses for other employees
        $otherEmployee = User::factory()->create(['first_name' => 'Other', 'last_name' => 'Employee']);
        ExpenseRequest::factory()
            ->count(5)
            ->forUser($otherEmployee)
            ->forExpenseType($expenseType)
            ->create();

        $response = $this->getJson(route('hrcore.expenses.datatable', [
            'draw' => 1,
            'start' => 0,
            'length' => 10,
            'order' => [['column' => 1, 'dir' => 'desc']],
            'search' => ['value' => 'UniqueTestName'],
        ]));

        $response->assertOk();
        $this->assertEquals(1, $response->json('recordsFiltered'));
    }
}
