<?php

namespace Tests\Unit\Payroll;

use App\Models\Company;
use App\Models\Leave;
use App\Models\LeaveType;
use App\Models\User;
use App\Models\WeekoffRule;
use App\Services\Attendance\WeekoffResolver;
use App\Services\Payroll\LeaveLopCalculator;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Mockery;
use Tests\TestCase;

class LeaveLopCalculatorLeaveDaysTest extends TestCase
{
    use RefreshDatabase;

    protected function tearDown(): void
    {
        Mockery::close();
        parent::tearDown();
    }

    public function test_paid_leave_type_counts_as_paid_even_when_leave_row_is_unpaid(): void
    {
        [$company, $user, $paidType] = $this->seedLeaveFixture(true);

        Leave::create([
            'company_id' => $company->id,
            'user_id' => $user->id,
            'leave_type_id' => $paidType->id,
            'start_date' => '2026-03-10',
            'end_date' => '2026-03-11',
            'total_days' => 2,
            'is_half_day' => false,
            'reason' => 'Casual',
            'is_paid' => false,
            'status' => 'approved',
        ]);

        $calculator = $this->calculatorWithWeekendWeekoffs();
        $start = Carbon::create(2026, 3, 10)->startOfDay();
        $end = Carbon::create(2026, 3, 14)->startOfDay();

        $leaveData = $calculator->leaveDaysForUser($user->id, $company->id, $start, $end);

        $this->assertSame(2.0, $leaveData['paid']);
        $this->assertSame(0.0, $leaveData['unpaid']);
    }

    public function test_unpaid_leave_type_counts_toward_unpaid_days(): void
    {
        [$company, $user, $unpaidType] = $this->seedLeaveFixture(false);

        Leave::create([
            'company_id' => $company->id,
            'user_id' => $user->id,
            'leave_type_id' => $unpaidType->id,
            'start_date' => '2026-03-10',
            'end_date' => '2026-03-11',
            'total_days' => 2,
            'is_half_day' => false,
            'reason' => 'Unpaid',
            'is_paid' => false,
            'status' => 'approved',
        ]);

        $calculator = $this->calculatorWithWeekendWeekoffs();
        $start = Carbon::create(2026, 3, 10)->startOfDay();
        $end = Carbon::create(2026, 3, 14)->startOfDay();

        $leaveData = $calculator->leaveDaysForUser($user->id, $company->id, $start, $end);

        $this->assertSame(0.0, $leaveData['paid']);
        $this->assertSame(2.0, $leaveData['unpaid']);
    }

    public function test_leave_spanning_period_boundary_counts_only_in_period_working_days(): void
    {
        [$company, $user, $paidType] = $this->seedLeaveFixture(true);

        Leave::create([
            'company_id' => $company->id,
            'user_id' => $user->id,
            'leave_type_id' => $paidType->id,
            'start_date' => '2026-03-09',
            'end_date' => '2026-03-12',
            'total_days' => 4,
            'is_half_day' => false,
            'reason' => 'Spanning',
            'is_paid' => true,
            'status' => 'approved',
        ]);

        $calculator = $this->calculatorWithWeekendWeekoffs();
        $start = Carbon::create(2026, 3, 10)->startOfDay();
        $end = Carbon::create(2026, 3, 14)->startOfDay();

        $leaveData = $calculator->leaveDaysForUser($user->id, $company->id, $start, $end);

        $this->assertSame(3.0, $leaveData['paid']);
    }

    protected function calculatorWithWeekendWeekoffs(): LeaveLopCalculator
    {
        $weekoffResolver = Mockery::mock(WeekoffResolver::class);
        $weekoffResolver->shouldReceive('dayTypeMap')
            ->andReturnUsing(function ($companyId, $userId, Carbon $start, Carbon $end) {
                $map = [];
                $cursor = $start->copy()->startOfDay();
                $endDay = $end->copy()->startOfDay();
                while ($cursor->lte($endDay)) {
                    if ($cursor->isWeekend()) {
                        $map[$cursor->format('Y-m-d')] = WeekoffRule::DAY_WEEKLY_OFF;
                    }
                    $cursor->addDay();
                }

                return $map;
            });

        return new LeaveLopCalculator($weekoffResolver);
    }

    /**
     * @return array{0: Company, 1: User, 2: LeaveType}
     */
    protected function seedLeaveFixture(bool $isPaid): array
    {
        $company = Company::query()->create([
            'name' => 'Payroll Test Co',
            'short_name' => 'PTC',
            'email' => 'payroll-test@demo.local',
            'status' => 'active',
        ]);

        $user = User::factory()->create([
            'company_id' => $company->id,
            'status' => 'active',
        ]);

        $leaveType = LeaveType::create([
            'company_id' => $company->id,
            'name' => $isPaid ? 'Casual Leave' : 'Leave Without Pay',
            'is_paid' => $isPaid,
            'is_unlimited' => false,
            'total_leaves' => 12,
        ]);

        return [$company, $user, $leaveType];
    }
}
