<?php

namespace Tests\Feature\PMCore;

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

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

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

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

    // ==========================================
    // Code Generation Tests
    // ==========================================

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

        $project = $this->createProject(['name' => 'Website Redesign']);

        $this->assertNotNull($project->code);
        $this->assertNotEmpty($project->code);
    }

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

        $project = $this->createProject(['name' => 'Website Development']);

        // Code should start with first letters of project name
        $this->assertStringStartsWith('WEB', $project->code);
    }

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

        $project1 = $this->createProject(['name' => 'Alpha Project']);
        $project2 = $this->createProject(['name' => 'Alpha Beta']);

        $this->assertNotEquals($project1->code, $project2->code);
    }

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

        $project = $this->createProject([
            'name' => 'Test Project',
            'code' => 'CUSTOM-001',
        ]);

        $this->assertEquals('CUSTOM-001', $project->code);
    }

    // ==========================================
    // Status Transition Tests
    // ==========================================

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

        $project = $this->createProject();

        $this->assertEquals(ProjectStatus::PLANNING, $project->status);
    }

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

        $project = $this->createProject();
        $project->update(['status' => ProjectStatus::IN_PROGRESS]);

        $this->assertEquals(ProjectStatus::IN_PROGRESS, $project->fresh()->status);
    }

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

        $project = $this->createProjectInProgress();
        $project->update(['status' => ProjectStatus::ON_HOLD]);

        $this->assertEquals(ProjectStatus::ON_HOLD, $project->fresh()->status);
    }

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

        $project = $this->createProjectInProgress();
        $project->markAsCompleted();

        $this->assertEquals(ProjectStatus::COMPLETED, $project->fresh()->status);
        $this->assertEquals(100, $project->fresh()->completion_percentage);
        $this->assertNotNull($project->fresh()->completed_at);
    }

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

        $project = $this->createProjectInProgress();
        $project->update(['status' => ProjectStatus::CANCELLED]);

        $this->assertEquals(ProjectStatus::CANCELLED, $project->fresh()->status);
    }

    // ==========================================
    // Overdue Detection Tests
    // ==========================================

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

        $project = $this->createOverdueProject();

        $this->assertTrue($project->isOverdue());
    }

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

        $project = $this->createProjectInProgress([
            'end_date' => now()->addMonth(),
        ]);

        $this->assertFalse($project->isOverdue());
    }

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

        $project = $this->createCompletedProject([
            'end_date' => now()->subWeek(), // Past end date but completed
        ]);

        $this->assertFalse($project->isOverdue());
    }

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

        $project = $this->createProject([
            'status' => ProjectStatus::CANCELLED,
            'end_date' => now()->subWeek(),
        ]);

        $this->assertFalse($project->isOverdue());
    }

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

        // Use startOfDay() to avoid time-based boundary issues with diffInDays()
        $project = $this->createProject([
            'end_date' => now()->startOfDay()->addDays(10),
        ]);

        $days = $project->getDaysUntilDeadline();
        // Allow for time-of-day differences (9 or 10 is acceptable)
        $this->assertGreaterThanOrEqual(9, $days);
        $this->assertLessThanOrEqual(10, $days);
    }

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

        // Use startOfDay() to avoid time-based boundary issues with diffInDays()
        $project = $this->createProject([
            'end_date' => now()->startOfDay()->subDays(5),
        ]);

        $days = $project->getDaysUntilDeadline();
        // Allow for time-of-day differences (-5 or -6 is acceptable)
        $this->assertGreaterThanOrEqual(-6, $days);
        $this->assertLessThanOrEqual(-5, $days);
    }

    // ==========================================
    // Budget Tracking Tests
    // ==========================================

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

        $project = $this->createOverBudgetProject();

        $this->assertTrue($project->isOverBudget());
    }

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

        $project = $this->createProject([
            'budget' => 50000.00,
            'actual_cost' => 25000.00,
        ]);

        $this->assertFalse($project->isOverBudget());
    }

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

        $project = $this->createProject([
            'budget' => 100000.00,
            'actual_cost' => 75000.00,
        ]);

        $this->assertEquals(25000.00, $project->budget_variance);
    }

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

        $project = $this->createProject([
            'budget' => 100000.00,
            'actual_cost' => 90000.00,
        ]);

        $this->assertEquals(10.00, $project->budget_variance_percentage);
    }

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

        $project = $this->createProject([
            'actual_revenue' => 100000.00,
            'actual_cost' => 60000.00,
        ]);

        $this->assertEquals(40000.00, $project->profit_margin);
    }

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

        $project = $this->createProject([
            'actual_revenue' => 100000.00,
            'actual_cost' => 60000.00,
        ]);

        $this->assertEquals(40.00, $project->profit_margin_percentage);
    }

    // ==========================================
    // Duration Tests
    // ==========================================

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

        $project = $this->createProject([
            'start_date' => now(),
            'end_date' => now()->addDays(30),
        ]);

        $this->assertEquals(30, $project->getDurationInDays());
    }

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

        $project = $this->createProject([
            'start_date' => null,
            'end_date' => null,
        ]);

        $this->assertNull($project->getDurationInDays());
    }

    // ==========================================
    // Archive Tests
    // ==========================================

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

        $project = $this->createCompletedProject();
        $project->archive();

        $this->assertTrue($project->fresh()->is_archived);
    }

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

        $project = $this->createArchivedProject();
        $project->unarchive();

        $this->assertFalse($project->fresh()->is_archived);
    }

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

        $this->createProject();
        $archived = $this->createArchivedProject();

        $archivedProjects = Project::archived()->get();

        $this->assertCount(1, $archivedProjects);
        $this->assertEquals($archived->id, $archivedProjects->first()->id);
    }

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

        $active = $this->createProject();
        $this->createArchivedProject();

        $activeProjects = Project::notArchived()->get();

        $this->assertCount(1, $activeProjects);
        $this->assertEquals($active->id, $activeProjects->first()->id);
    }

    // ==========================================
    // Scopes Tests
    // ==========================================

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

        $planning = $this->createProject(['status' => ProjectStatus::PLANNING]);
        $inProgress = $this->createProjectInProgress();
        $this->createCompletedProject();
        $this->createOnHoldProject();

        $activeProjects = Project::active()->get();

        $this->assertCount(2, $activeProjects);
        $this->assertTrue($activeProjects->contains($planning));
        $this->assertTrue($activeProjects->contains($inProgress));
    }

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

        $this->createProject();
        $completed = $this->createCompletedProject();

        $completedProjects = Project::completed()->get();

        $this->assertCount(1, $completedProjects);
        $this->assertEquals($completed->id, $completedProjects->first()->id);
    }

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

        $managerProject = $this->createProject(['project_manager_id' => $manager->id]);
        $this->createProject(['project_manager_id' => $admin->id]);

        $projects = Project::managedBy($manager->id)->get();

        $this->assertCount(1, $projects);
        $this->assertEquals($managerProject->id, $projects->first()->id);
    }

    // ==========================================
    // Progress Tests
    // ==========================================

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

        $project = $this->createCompletedProject();

        $this->assertEquals(100, $project->getProgressPercentage());
    }

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

        $project = $this->createProjectInProgress();

        // Without TaskSystem integration, should return status-based progress
        $this->assertEquals(50, $project->getProgressPercentage());
    }

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

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

        $project = $this->createProject(['project_manager_id' => $admin->id]);

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

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

        $project = $this->createProject(['project_manager_id' => $manager->id]);

        $this->assertTrue($project->isManageredBy($manager->id));
        $this->assertFalse($project->isManageredBy($admin->id));
    }

    // ==========================================
    // Soft Delete Tests
    // ==========================================

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

        $project = $this->createProject();
        $projectId = $project->id;

        $project->delete();

        $this->assertSoftDeleted('projects', ['id' => $projectId]);
    }
}
