<?php

namespace Tests\Feature\PMCore;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Modules\PMCore\App\Enums\TimesheetStatus;
use Modules\PMCore\App\Models\Timesheet;
use Tests\TestCase;
use Tests\Traits\CreatesPMCoreEntities;
use Tests\Traits\CreatesUsers;

class TimesheetApprovalTest extends TestCase
{
    use CreatesPMCoreEntities;
    use CreatesUsers;
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();

        if (! moduleExists('PMCore')) {
            $this->markTestSkipped('PMCore module is not enabled');
        }
    }

    // ==========================================
    // Submit Tests
    // ==========================================

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

        $project = $this->createProject(['project_manager_id' => $admin->id]);
        $timesheet = $this->createDraftTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
        ]);

        $result = $timesheet->submit();

        $this->assertTrue($result);
        $this->assertEquals(TimesheetStatus::SUBMITTED, $timesheet->fresh()->status);
    }

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

        $project = $this->createProject(['project_manager_id' => $admin->id]);
        $timesheet = $this->createSubmittedTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
        ]);

        $result = $timesheet->submit();

        $this->assertFalse($result);
        $this->assertEquals(TimesheetStatus::SUBMITTED, $timesheet->fresh()->status);
    }

    // ==========================================
    // Approval Tests
    // ==========================================

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

        $project = $this->createProject(['project_manager_id' => $admin->id]);
        $timesheet = $this->createSubmittedTimesheet([
            'user_id' => $employee->id,
            'project_id' => $project->id,
        ]);

        $result = $timesheet->approve($admin);

        $this->assertTrue($result);
        $this->assertEquals(TimesheetStatus::APPROVED, $timesheet->fresh()->status);
        $this->assertEquals($admin->id, $timesheet->fresh()->approved_by_id);
        $this->assertNotNull($timesheet->fresh()->approved_at);
    }

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

        $project = $this->createProject(['project_manager_id' => $manager->id]);
        $timesheet = $this->createSubmittedTimesheet([
            'user_id' => $employee->id,
            'project_id' => $project->id,
        ]);

        $result = $timesheet->approve($admin);

        $this->assertTrue($result);
        $this->assertEquals(TimesheetStatus::APPROVED, $timesheet->fresh()->status);
    }

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

        $project = $this->createProject(['project_manager_id' => $admin->id]);
        $timesheet = $this->createSubmittedTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
        ]);

        $result = $timesheet->approve($admin);

        $this->assertFalse($result);
        $this->assertEquals(TimesheetStatus::SUBMITTED, $timesheet->fresh()->status);
    }

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

        $project = $this->createProject(['project_manager_id' => $admin->id]);
        $timesheet = $this->createDraftTimesheet([
            'user_id' => $employee->id,
            'project_id' => $project->id,
        ]);

        $result = $timesheet->approve($admin);

        $this->assertFalse($result);
        $this->assertEquals(TimesheetStatus::DRAFT, $timesheet->fresh()->status);
    }

    // ==========================================
    // Rejection Tests
    // ==========================================

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

        $project = $this->createProject(['project_manager_id' => $admin->id]);
        $timesheet = $this->createSubmittedTimesheet([
            'user_id' => $employee->id,
            'project_id' => $project->id,
        ]);

        $result = $timesheet->reject($admin);

        $this->assertTrue($result);
        $this->assertEquals(TimesheetStatus::REJECTED, $timesheet->fresh()->status);
        $this->assertEquals($admin->id, $timesheet->fresh()->approved_by_id);
    }

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

        $project = $this->createProject(['project_manager_id' => $admin->id]);
        $timesheet = $this->createSubmittedTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
        ]);

        $result = $timesheet->reject($admin);

        $this->assertFalse($result);
    }

    // ==========================================
    // Permission Check Tests
    // ==========================================

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

        $project = $this->createProject(['project_manager_id' => $manager->id]);
        $timesheet = $this->createSubmittedTimesheet([
            'user_id' => $employee->id,
            'project_id' => $project->id,
        ]);

        // Manager can approve
        $this->assertTrue($timesheet->canBeApprovedBy($manager));

        // Admin can approve
        $this->assertTrue($timesheet->canBeApprovedBy($admin));

        // Owner cannot approve own
        $this->assertFalse($timesheet->canBeApprovedBy($employee));
    }

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

        $project = $this->createProject(['project_manager_id' => $admin->id]);
        $timesheet = $this->createDraftTimesheet([
            'user_id' => $employee->id,
            'project_id' => $project->id,
        ]);

        $this->assertTrue($timesheet->canBeEditedBy($employee));
    }

    public function test_owner_cannot_edit_submitted_timesheet(): void
    {
        $admin = $this->createAdmin();
        $employee = $this->createUser(); // Regular user, not admin
        $this->actingAs($admin);

        $project = $this->createProject(['project_manager_id' => $admin->id]);
        $timesheet = $this->createSubmittedTimesheet([
            'user_id' => $employee->id,
            'project_id' => $project->id,
        ]);

        $this->assertFalse($timesheet->canBeEditedBy($employee));
    }

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

        $project = $this->createProject(['project_manager_id' => $admin->id]);
        $timesheet = $this->createSubmittedTimesheet([
            'user_id' => $employee->id,
            'project_id' => $project->id,
        ]);

        $this->assertTrue($timesheet->canBeEditedBy($admin));
    }

    // ==========================================
    // Financial Calculation Tests
    // ==========================================

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

        $project = $this->createProject();
        $timesheet = $this->createTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
            'hours' => 8.0,
            'cost_rate' => 75.00,
        ]);

        $this->assertEquals(600.00, $timesheet->cost_amount);
    }

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

        $project = $this->createProject();
        $timesheet = $this->createBillableTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
            'hours' => 8.0,
            'billing_rate' => 150.00,
        ]);

        $this->assertEquals(1200.00, $timesheet->billable_amount);
    }

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

        $project = $this->createProject();
        $timesheet = $this->createNonBillableTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
            'hours' => 8.0,
        ]);

        $this->assertEquals(0, $timesheet->billable_amount);
    }

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

        $project = $this->createProject();
        $timesheet = $this->createTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
            'hours' => 4.0,
            'cost_rate' => 75.00,
            'billing_rate' => 100.00,
        ]);

        $originalCost = $timesheet->cost_amount;

        $timesheet->update(['hours' => 8.0]);

        $this->assertNotEquals($originalCost, $timesheet->fresh()->cost_amount);
        $this->assertEquals(600.00, $timesheet->fresh()->cost_amount);
    }

    // ==========================================
    // Project Financial Update Tests
    // ==========================================

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

        $project = $this->createProject([
            'actual_cost' => 0,
        ]);

        $this->createApprovedTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
            'hours' => 10.0,
            'cost_rate' => 100.00,
            'status' => TimesheetStatus::APPROVED,
        ]);

        // Project cost should be updated
        $project->refresh();
        $this->assertGreaterThan(0, $project->actual_cost);
    }

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

        $project = $this->createProject([
            'actual_revenue' => 0,
        ]);

        $this->createApprovedTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
            'hours' => 10.0,
            'billing_rate' => 150.00,
            'is_billable' => true,
            'status' => TimesheetStatus::APPROVED,
        ]);

        $project->refresh();
        $this->assertGreaterThan(0, $project->actual_revenue);
    }

    // ==========================================
    // Scope Tests
    // ==========================================

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

        $project = $this->createProject();
        $this->createTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
        ]);
        $this->createTimesheet([
            'user_id' => $employee->id,
            'project_id' => $project->id,
        ]);

        $userTimesheets = Timesheet::forUser($admin->id)->get();

        $this->assertCount(1, $userTimesheets);
    }

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

        $project1 = $this->createProject();
        $project2 = $this->createProject();

        $this->createTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project1->id,
        ]);
        $this->createTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project1->id,
        ]);
        $this->createTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project2->id,
        ]);

        $projectTimesheets = Timesheet::forProject($project1->id)->get();

        $this->assertCount(2, $projectTimesheets);
    }

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

        $project = $this->createProject();
        $this->createDraftTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
        ]);
        $this->createSubmittedTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
        ]);
        $approved = $this->createApprovedTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
        ]);

        $approvedTimesheets = Timesheet::approved()->get();

        $this->assertCount(1, $approvedTimesheets);
        $this->assertEquals($approved->id, $approvedTimesheets->first()->id);
    }

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

        $project = $this->createProject();
        $billable = $this->createBillableTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
        ]);
        $this->createNonBillableTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
        ]);

        $billableTimesheets = Timesheet::billable()->get();

        $this->assertCount(1, $billableTimesheets);
        $this->assertEquals($billable->id, $billableTimesheets->first()->id);
    }

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

        $project = $this->createProject();

        // Create timesheets for different dates
        $this->createTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
            'date' => now()->subWeek(),
        ]);
        $inRange = $this->createTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
            'date' => now(),
        ]);
        $this->createTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
            'date' => now()->addWeek(),
        ]);

        $rangeTimesheets = Timesheet::forDateRange(now()->subDay(), now()->addDay())->get();

        $this->assertCount(1, $rangeTimesheets);
        $this->assertEquals($inRange->id, $rangeTimesheets->first()->id);
    }

    // ==========================================
    // Relationship Tests
    // ==========================================

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

        $project = $this->createProject();
        $timesheet = $this->createTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
        ]);

        $this->assertNotNull($timesheet->user);
        $this->assertEquals($admin->id, $timesheet->user->id);
    }

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

        $project = $this->createProject();
        $timesheet = $this->createTimesheet([
            'user_id' => $admin->id,
            'project_id' => $project->id,
        ]);

        $this->assertNotNull($timesheet->project);
        $this->assertEquals($project->id, $timesheet->project->id);
    }

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

        $project = $this->createProject(['project_manager_id' => $admin->id]);
        $timesheet = $this->createSubmittedTimesheet([
            'user_id' => $employee->id,
            'project_id' => $project->id,
        ]);

        $timesheet->approve($admin);

        $this->assertNotNull($timesheet->fresh()->approvedBy);
        $this->assertEquals($admin->id, $timesheet->fresh()->approvedBy->id);
    }
}
