<?php

declare(strict_types=1);

namespace Maestro\Core\Modules\HourRegistration\Services;

use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
use Maestro\Core\Modules\HourRegistration\Enums\HourLineApprovalStatus;
use Maestro\Core\Modules\HourRegistration\Enums\HourWorkflowStatus;
use Maestro\Core\Modules\HourRegistration\Models\HourTimesheetLine;

final class HourLineApprovalService
{
    public function __construct(
        private readonly HourApproverResolver $approverResolver,
        private readonly HourWeekWorkflowStatusService $workflowStatusService,
    ) {}

    public function approve(HourTimesheetLine $line, ?Authenticatable $actor = null): HourTimesheetLine
    {
        $actor ??= Auth::guard()->user();
        abort_unless($actor !== null && ($actor->can('hours.approve') || $actor->can('hours.admin')), 403);
        abort_unless($this->approverResolver->actingUserMayApprove($actor, $line), 403);

        return DB::transaction(function () use ($line, $actor): HourTimesheetLine {
            if ($line->line_approval_status !== HourLineApprovalStatus::Pending) {
                throw ValidationException::withMessages([
                    'line' => ['Only pending lines may be approved.'],
                ]);
            }

            $line->line_approval_status = HourLineApprovalStatus::Approved;
            /** @phpstan-ignore-next-line */
            $line->approved_by_id = $actor !== null ? (int) $actor->getAuthIdentifier() : null;
            $line->approved_at = now();
            $line->reject_reason = null;
            $line->save();

            $week = $line->week;
            $this->workflowStatusService->refreshAggregateStatus($week->fresh(['lines'])->load('lines'));

            return $line->fresh() ?? $line;
        });
    }

    public function reject(HourTimesheetLine $line, string $reason, ?Authenticatable $actor = null): HourTimesheetLine
    {
        $actor ??= Auth::guard()->user();
        abort_unless($actor !== null && ($actor->can('hours.approve') || $actor->can('hours.admin')), 403);
        abort_unless($this->approverResolver->actingUserMayApprove($actor, $line), 403);

        if (trim($reason) === '') {
            throw ValidationException::withMessages([
                'reason' => ['A rejection reason is required.'],
            ]);
        }

        return DB::transaction(function () use ($line, $actor, $reason): HourTimesheetLine {
            if ($line->line_approval_status !== HourLineApprovalStatus::Pending) {
                throw ValidationException::withMessages([
                    'line' => ['Only pending lines may be rejected.'],
                ]);
            }

            $line->line_approval_status = HourLineApprovalStatus::Rejected;
            /** @phpstan-ignore-next-line */
            $line->approved_by_id = $actor !== null ? (int) $actor->getAuthIdentifier() : null;
            $line->approved_at = now();
            $line->reject_reason = $reason;
            $line->save();

            $week = $line->week;
            $this->workflowStatusService->refreshAggregateStatus($week->fresh(['lines'])->load('lines'));

            return $line->fresh() ?? $line;
        });
    }

    /**
     * @return Collection<int, HourTimesheetLine>
     */
    public function pendingLinesForActor(Authenticatable $actor): Collection
    {
        $lines = HourTimesheetLine::query()
            ->with(['week.user', 'internalCategory', 'customer', 'hourProject', 'hourTask'])
            ->where('line_approval_status', HourLineApprovalStatus::Pending)
            ->whereHas('week', function ($q): void {
                $q->whereIn('workflow_status', [
                    HourWorkflowStatus::Submitted->value,
                    HourWorkflowStatus::Rejected->value,
                ]);
            })
            ->orderByDesc('id')
            ->limit(500)
            ->get();

        if ($actor->can('hours.admin')) {
            return $lines;
        }

        $uid = (int) $actor->getAuthIdentifier();

        return $lines->filter(function (HourTimesheetLine $line) use ($uid): bool {
            $expected = $this->approverResolver->resolveApproverUserId($line);

            return $expected !== null && $expected === $uid;
        })->values();
    }
}
