<?php

namespace Tests\Feature\ExpenseManagement\Api;

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 ExpenseApiTest extends TestCase
{
    use CreatesUsers, RefreshDatabase;

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

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

    public function test_unauthenticated_cannot_access_expense_api(): void
    {
        $response = $this->getJson('/api/V1/expense/getExpenseTypes');

        $response->assertStatus(401);
    }

    public function test_can_get_expense_types(): void
    {
        $user = $this->createFieldEmployee();

        // Create some expense types
        ExpenseType::factory()->count(3)->active()->create();
        ExpenseType::factory()->inactive()->create();

        $response = $this->actingAs($user, 'api')
            ->getJson('/api/V1/expense/getExpenseTypes');

        $response->assertOk();
        $response->assertJson(['status' => 'success']);
        $this->assertCount(3, $response->json('data'));
    }

    public function test_expense_types_only_returns_active_types(): void
    {
        $user = $this->createFieldEmployee();

        // Create active and inactive types
        $activeTypes = ExpenseType::factory()->count(2)->active()->create();
        ExpenseType::factory()->count(3)->inactive()->create();

        $response = $this->actingAs($user, 'api')
            ->getJson('/api/V1/expense/getExpenseTypes');

        $response->assertOk();
        $this->assertCount(2, $response->json('data'));
    }

    public function test_expense_type_response_has_correct_structure(): void
    {
        $user = $this->createFieldEmployee();
        ExpenseType::factory()->create([
            'name' => 'Travel',
            'is_proof_required' => true,
        ]);

        $response = $this->actingAs($user, 'api')
            ->getJson('/api/V1/expense/getExpenseTypes');

        $response->assertOk();
        $response->assertJsonStructure([
            'status',
            'data' => [
                '*' => ['id', 'name', 'isImgRequired'],
            ],
        ]);
    }

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

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

        $response = $this->actingAs($user, 'api')
            ->getJson('/api/V1/expense/getExpenseRequests?skip=0&take=10');

        $response->assertOk();
        $response->assertJson(['status' => 'success']);
        $this->assertEquals(5, $response->json('data.totalCount'));
    }

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

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

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

        $response = $this->actingAs($user, 'api')
            ->getJson('/api/V1/expense/getExpenseRequests?skip=0&take=10&status=pending');

        $response->assertOk();
        $this->assertEquals(3, $response->json('data.totalCount'));
    }

    public function test_expense_requests_only_returns_user_own_requests(): void
    {
        $user = $this->createFieldEmployee();
        $otherUser = User::factory()->create();
        $expenseType = ExpenseType::factory()->create();

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

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

        $response = $this->actingAs($user, 'api')
            ->getJson('/api/V1/expense/getExpenseRequests?skip=0&take=10');

        $response->assertOk();
        $this->assertEquals(3, $response->json('data.totalCount'));
    }

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

        $data = [
            'date' => now()->format('Y-m-d'),
            'amount' => 150.50,
            'typeId' => $expenseType->id,
            'comments' => 'Test expense',
        ];

        $response = $this->actingAs($user, 'api')
            ->postJson('/api/V1/expense/createExpenseRequest', $data);

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

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

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

        $data = [
            'amount' => 150.50,
            'typeId' => $expenseType->id,
            'comments' => 'Test expense',
        ];

        $response = $this->actingAs($user, 'api')
            ->postJson('/api/V1/expense/createExpenseRequest', $data);

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

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

        $data = [
            'date' => now()->format('Y-m-d'),
            'typeId' => $expenseType->id,
            'comments' => 'Test expense',
        ];

        $response = $this->actingAs($user, 'api')
            ->postJson('/api/V1/expense/createExpenseRequest', $data);

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

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

        $data = [
            'date' => now()->format('Y-m-d'),
            'amount' => 0,
            'typeId' => $expenseType->id,
            'comments' => 'Test expense',
        ];

        $response = $this->actingAs($user, 'api')
            ->postJson('/api/V1/expense/createExpenseRequest', $data);

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

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

        $data = [
            'date' => now()->format('Y-m-d'),
            'amount' => -50,
            'typeId' => $expenseType->id,
            'comments' => 'Test expense',
        ];

        $response = $this->actingAs($user, 'api')
            ->postJson('/api/V1/expense/createExpenseRequest', $data);

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

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

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

        $response = $this->actingAs($user, 'api')
            ->postJson('/api/V1/expense/createExpenseRequest', $data);

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

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

        $data = [
            'date' => now()->format('Y-m-d'),
            'amount' => 150.50,
            'typeId' => $expenseType->id,
        ];

        $response = $this->actingAs($user, 'api')
            ->postJson('/api/V1/expense/createExpenseRequest', $data);

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

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

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

        // First create an expense request
        $expense = ExpenseRequest::factory()
            ->forUser($user)
            ->forExpenseType($expenseType)
            ->pending()
            ->create();

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

        $response = $this->actingAs($user, 'api')
            ->postJson('/api/V1/expense/uploadExpenseDocument', [
                'file' => $file,
            ]);

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

        $expense->refresh();
        $this->assertNotNull($expense->document_url);
    }

    public function test_upload_requires_file(): void
    {
        $user = $this->createFieldEmployee();

        $response = $this->actingAs($user, 'api')
            ->postJson('/api/V1/expense/uploadExpenseDocument', []);

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

    public function test_upload_fails_without_expense_request(): void
    {
        $user = $this->createFieldEmployee();
        // No expense request exists for this user

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

        $response = $this->actingAs($user, 'api')
            ->postJson('/api/V1/expense/uploadExpenseDocument', [
                'file' => $file,
            ]);

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

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

        $response = $this->actingAs($user, 'api')
            ->postJson('/api/V1/expense/cancel', [
                'id' => $expense->id,
            ]);

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

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

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

        $response = $this->actingAs($user, 'api')
            ->postJson('/api/V1/expense/cancel', [
                'id' => $expense->id,
            ]);

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

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

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

        $response = $this->actingAs($user, 'api')
            ->postJson('/api/V1/expense/cancel', [
                'id' => $expense->id,
            ]);

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

    public function test_cannot_cancel_nonexistent_expense(): void
    {
        $user = $this->createFieldEmployee();

        $response = $this->actingAs($user, 'api')
            ->postJson('/api/V1/expense/cancel', [
                'id' => 99999,
            ]);

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

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

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

        $response = $this->actingAs($user, 'api')
            ->getJson('/api/V1/expense/getExpenseRequests?skip=0&take=5');

        $response->assertOk();
        $this->assertEquals(15, $response->json('data.totalCount'));
        $this->assertCount(5, $response->json('data.values'));
    }

    public function test_expense_requests_can_be_filtered_by_date(): void
    {
        $user = $this->createFieldEmployee();
        $expenseType = ExpenseType::factory()->create();
        $targetDate = now()->subDays(5);

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

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

        $response = $this->actingAs($user, 'api')
            ->getJson('/api/V1/expense/getExpenseRequests?skip=0&take=10&date='.$targetDate->format('Y-m-d'));

        $response->assertOk();
        $this->assertEquals(2, $response->json('data.totalCount'));
    }
}
