<?php

use App\Models\Actual;
use App\Models\Allocation;
use App\Models\Project;
use App\Models\ProjectStatus;
use App\Models\Role;
use App\Models\TeamMember;
use App\Services\ActualsService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

/**
 * Unit tests for ActualsService.
 *
 * Tests variance calculation logic and business rules for actuals tracking.
 *
 * @mixin \Tests\TestCase
 */
uses(TestCase::class, RefreshDatabase::class);

// ============================================================================
// VARIANCE CALCULATION TESTS
// ============================================================================

test('calculate_variance returns correct percentage', function () {
    $role = Role::factory()->create();
    $teamMember = TeamMember::factory()->create(['role_id' => $role->id]);
    $projectStatus = ProjectStatus::factory()->create(['name' => 'Active']);
    $project = Project::factory()->create(['status_id' => $projectStatus->id]);

    // Create allocation: 100 hours allocated
    Allocation::factory()->create([
        'project_id' => $project->id,
        'team_member_id' => $teamMember->id,
        'month' => '2026-02-01',
        'allocated_hours' => 100,
    ]);

    // Create actual: 80 hours logged
    Actual::factory()->create([
        'project_id' => $project->id,
        'team_member_id' => $teamMember->id,
        'month' => '2026-02-01',
        'hours_logged' => 80,
    ]);

    $service = app(ActualsService::class);
    $result = $service->calculateVariance($project->id, $teamMember->id, '2026-02');

    // Variance = ((80 - 100) / 100) * 100 = -20%
    expect($result['allocated'])->toBe(100.0)
        ->and($result['actual'])->toBe(80.0)
        ->and($result['variance_percentage'])->toBe(-20.0)
        ->and($result['indicator'])->toBe('yellow');
});

test('calculate_variance handles zero allocation', function () {
    $role = Role::factory()->create();
    $teamMember = TeamMember::factory()->create(['role_id' => $role->id]);
    $projectStatus = ProjectStatus::factory()->create(['name' => 'Active']);
    $project = Project::factory()->create(['status_id' => $projectStatus->id]);

    // No allocation, but actual hours logged
    Actual::factory()->create([
        'project_id' => $project->id,
        'team_member_id' => $teamMember->id,
        'month' => '2026-02-01',
        'hours_logged' => 40,
    ]);

    $service = app(ActualsService::class);
    $result = $service->calculateVariance($project->id, $teamMember->id, '2026-02');

    // When allocation is 0 but actual is > 0, variance is 100%
    expect($result['allocated'])->toBe(0.0)
        ->and($result['actual'])->toBe(40.0)
        ->and($result['variance_percentage'])->toBe(100.0)
        ->and($result['indicator'])->toBe('red');
});

test('calculate_variance handles both zero', function () {
    $role = Role::factory()->create();
    $teamMember = TeamMember::factory()->create(['role_id' => $role->id]);
    $projectStatus = ProjectStatus::factory()->create(['name' => 'Active']);
    $project = Project::factory()->create(['status_id' => $projectStatus->id]);

    // No allocation, no actual hours
    $service = app(ActualsService::class);
    $result = $service->calculateVariance($project->id, $teamMember->id, '2026-02');

    // When both are 0, variance is 0%
    expect($result['allocated'])->toBe(0.0)
        ->and($result['actual'])->toBe(0.0)
        ->and($result['variance_percentage'])->toBe(0.0)
        ->and($result['indicator'])->toBe('green');
});

test('calculate_variance handles positive_variance', function () {
    $role = Role::factory()->create();
    $teamMember = TeamMember::factory()->create(['role_id' => $role->id]);
    $projectStatus = ProjectStatus::factory()->create(['name' => 'Active']);
    $project = Project::factory()->create(['status_id' => $projectStatus->id]);

    Allocation::factory()->create([
        'project_id' => $project->id,
        'team_member_id' => $teamMember->id,
        'month' => '2026-02-01',
        'allocated_hours' => 80,
    ]);

    Actual::factory()->create([
        'project_id' => $project->id,
        'team_member_id' => $teamMember->id,
        'month' => '2026-02-01',
        'hours_logged' => 100,
    ]);

    $service = app(ActualsService::class);
    $result = $service->calculateVariance($project->id, $teamMember->id, '2026-02');

    // Variance = ((100 - 80) / 80) * 100 = 25%
    expect($result['allocated'])->toBe(80.0)
        ->and($result['actual'])->toBe(100.0)
        ->and($result['variance_percentage'])->toBe(25.0)
        ->and($result['indicator'])->toBe('red');
});

// ============================================================================
// INDICATOR THRESHOLD TESTS
// ============================================================================

test('get_indicator returns green for small variance', function () {
    $service = app(ActualsService::class);

    // Green: |variance| <= 5%
    $reflection = new ReflectionClass($service);
    $method = $reflection->getMethod('getIndicator');
    $method->setAccessible(true);

    expect($method->invoke($service, 0.0))->toBe('green')
        ->and($method->invoke($service, 5.0))->toBe('green')
        ->and($method->invoke($service, -5.0))->toBe('green')
        ->and($method->invoke($service, 3.5))->toBe('green')
        ->and($method->invoke($service, -2.1))->toBe('green');
});

test('get_indicator returns yellow for medium variance', function () {
    $service = app(ActualsService::class);

    // Yellow: 5% < |variance| <= 20%
    $reflection = new ReflectionClass($service);
    $method = $reflection->getMethod('getIndicator');
    $method->setAccessible(true);

    expect($method->invoke($service, 6.0))->toBe('yellow')
        ->and($method->invoke($service, 20.0))->toBe('yellow')
        ->and($method->invoke($service, -10.0))->toBe('yellow')
        ->and($method->invoke($service, -15.5))->toBe('yellow')
        ->and($method->invoke($service, 19.9))->toBe('yellow');
});

test('get_indicator returns red for large variance', function () {
    $service = app(ActualsService::class);

    // Red: |variance| > 20%
    $reflection = new ReflectionClass($service);
    $method = $reflection->getMethod('getIndicator');
    $method->setAccessible(true);

    expect($method->invoke($service, 21.0))->toBe('red')
        ->and($method->invoke($service, -21.0))->toBe('red')
        ->and($method->invoke($service, 50.0))->toBe('red')
        ->and($method->invoke($service, -100.0))->toBe('red')
        ->and($method->invoke($service, 1000.0))->toBe('red');
});

// ============================================================================
// PROJECT STATUS TESTS
// ============================================================================

test('get_inactive_project_statuses returns expected values', function () {
    $service = app(ActualsService::class);
    $statuses = $service->getInactiveProjectStatuses();

    expect($statuses)->toBe(['Done', 'Cancelled', 'Closed'])
        ->and(count($statuses))->toBe(3);
});

test('can_log_to_inactive_projects respects config', function () {
    $service = app(ActualsService::class);

    // Default config value should be false
    config(['actuals.allow_actuals_on_inactive_projects' => false]);
    expect($service->canLogToInactiveProjects())->toBeFalse();

    // When enabled, should return true
    config(['actuals.allow_actuals_on_inactive_projects' => true]);
    expect($service->canLogToInactiveProjects())->toBeTrue();
});

// ============================================================================
// EDGE CASE TESTS
// ============================================================================

test('calculate_variance sums multiple allocations', function () {
    $role = Role::factory()->create();
    $teamMember = TeamMember::factory()->create(['role_id' => $role->id]);
    $projectStatus = ProjectStatus::factory()->create(['name' => 'Active']);
    $project = Project::factory()->create(['status_id' => $projectStatus->id]);

    // Multiple allocations for the same project/member/month
    Allocation::factory()->create([
        'project_id' => $project->id,
        'team_member_id' => $teamMember->id,
        'month' => '2026-02-01',
        'allocated_hours' => 40,
    ]);

    Allocation::factory()->create([
        'project_id' => $project->id,
        'team_member_id' => $teamMember->id,
        'month' => '2026-02-01',
        'allocated_hours' => 60,
    ]);

    Actual::factory()->create([
        'project_id' => $project->id,
        'team_member_id' => $teamMember->id,
        'month' => '2026-02-01',
        'hours_logged' => 100,
    ]);

    $service = app(ActualsService::class);
    $result = $service->calculateVariance($project->id, $teamMember->id, '2026-02');

    // Total allocated: 100, Actual: 100, Variance: 0%
    expect($result['allocated'])->toBe(100.0)
        ->and($result['actual'])->toBe(100.0)
        ->and($result['variance_percentage'])->toBe(0.0)
        ->and($result['indicator'])->toBe('green');
});

test('calculate_variance sums multiple actuals', function () {
    $role = Role::factory()->create();
    $teamMember = TeamMember::factory()->create(['role_id' => $role->id]);
    $projectStatus = ProjectStatus::factory()->create(['name' => 'Active']);
    $project = Project::factory()->create(['status_id' => $projectStatus->id]);

    Allocation::factory()->create([
        'project_id' => $project->id,
        'team_member_id' => $teamMember->id,
        'month' => '2026-02-01',
        'allocated_hours' => 100,
    ]);

    // Multiple actual entries (simulating multiple time logs)
    Actual::factory()->create([
        'project_id' => $project->id,
        'team_member_id' => $teamMember->id,
        'month' => '2026-02-01',
        'hours_logged' => 40,
    ]);

    Actual::factory()->create([
        'project_id' => $project->id,
        'team_member_id' => $teamMember->id,
        'month' => '2026-02-01',
        'hours_logged' => 60,
    ]);

    $service = app(ActualsService::class);
    $result = $service->calculateVariance($project->id, $teamMember->id, '2026-02');

    // Total allocated: 100, Total actual: 100, Variance: 0%
    expect($result['allocated'])->toBe(100.0)
        ->and($result['actual'])->toBe(100.0)
        ->and($result['variance_percentage'])->toBe(0.0)
        ->and($result['indicator'])->toBe('green');
});

test('calculate_variance handles decimal precision', function () {
    $role = Role::factory()->create();
    $teamMember = TeamMember::factory()->create(['role_id' => $role->id]);
    $projectStatus = ProjectStatus::factory()->create(['name' => 'Active']);
    $project = Project::factory()->create(['status_id' => $projectStatus->id]);

    Allocation::factory()->create([
        'project_id' => $project->id,
        'team_member_id' => $teamMember->id,
        'month' => '2026-02-01',
        'allocated_hours' => 33.33,
    ]);

    Actual::factory()->create([
        'project_id' => $project->id,
        'team_member_id' => $teamMember->id,
        'month' => '2026-02-01',
        'hours_logged' => 66.66,
    ]);

    $service = app(ActualsService::class);
    $result = $service->calculateVariance($project->id, $teamMember->id, '2026-02');

    // Variance = ((66.66 - 33.33) / 33.33) * 100 = 100%
    expect($result['variance_percentage'])->toBe(100.0)
        ->and($result['indicator'])->toBe('red');
});

test('calculate_variance only_matches_exact_month', function () {
    $role = Role::factory()->create();
    $teamMember = TeamMember::factory()->create(['role_id' => $role->id]);
    $projectStatus = ProjectStatus::factory()->create(['name' => 'Active']);
    $project = Project::factory()->create(['status_id' => $projectStatus->id]);

    // Allocation in January
    Allocation::factory()->create([
        'project_id' => $project->id,
        'team_member_id' => $teamMember->id,
        'month' => '2026-01-01',
        'allocated_hours' => 100,
    ]);

    // Actual in February
    Actual::factory()->create([
        'project_id' => $project->id,
        'team_member_id' => $teamMember->id,
        'month' => '2026-02-01',
        'hours_logged' => 80,
    ]);

    $service = app(ActualsService::class);

    // Querying February should only find the actual, not the January allocation
    $result = $service->calculateVariance($project->id, $teamMember->id, '2026-02');
    expect($result['allocated'])->toBe(0.0)
        ->and($result['actual'])->toBe(80.0)
        ->and($result['variance_percentage'])->toBe(100.0);
});
