<?php

namespace App\Modules\HR\Recruitment\Services;

use App\Models\User;
use App\Modules\HR\Recruitment\DTOs\JobRequestWriteData;
use App\Modules\HR\Recruitment\Events\RecruitmentJobRequestApproved;
use App\Modules\HR\Recruitment\Events\RecruitmentJobRequestCreated;
use App\Modules\HR\Recruitment\Events\RecruitmentJobRequestDeleted;
use App\Modules\HR\Recruitment\Events\RecruitmentJobRequestUpdated;
use App\Modules\HR\Recruitment\Models\RecruitmentJobRequest;
use App\Modules\HR\Recruitment\Repositories\RecruitmentJobRequestRepository;
use App\Modules\HR\Recruitment\Support\JobRequestStatus;
use App\Support\SystemReferenceAssigner;
use App\Support\SystemReferenceGenerator;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class RecruitmentJobRequestService
{
    public function __construct(
        private readonly RecruitmentJobRequestRepository $repository,
        private readonly SystemReferenceAssigner $references,
        private readonly Dispatcher $events,
    ) {}

    public function store(JobRequestWriteData $data, int $userId): RecruitmentJobRequest
    {
        $row = DB::transaction(function () use ($data, $userId) {
            $created = $this->repository->create(array_merge($data->toArray(), [
                'tenant_id' => $this->tenantId(),
                'reference_code' => $this->references->pendingPlaceholder(SystemReferenceGenerator::JOB_REQUEST),
                'status' => JobRequestStatus::DRAFT,
                'requested_by' => $userId,
            ]));

            return $this->references->assign($created, SystemReferenceGenerator::JOB_REQUEST, 'reference_code');
        });

        $this->events->dispatch(new RecruitmentJobRequestCreated($row));

        return $row;
    }

    public function update(RecruitmentJobRequest $row, JobRequestWriteData $data): RecruitmentJobRequest
    {
        if ($row->status !== JobRequestStatus::DRAFT) {
            throw ValidationException::withMessages([
                'status' => __('recruitment.job_requests.errors.only_draft_editable'),
            ]);
        }
        $fresh = $this->repository->update($row, $data->toArray());
        $this->events->dispatch(new RecruitmentJobRequestUpdated($fresh));

        return $fresh;
    }

    public function destroy(RecruitmentJobRequest $row): void
    {
        $this->repository->softDelete($row);
        $this->events->dispatch(new RecruitmentJobRequestDeleted($row));
    }

    public function submit(RecruitmentJobRequest $row): RecruitmentJobRequest
    {
        if ($row->status !== JobRequestStatus::DRAFT) {
            throw ValidationException::withMessages([
                'status' => __('recruitment.job_requests.errors.only_draft_submit'),
            ]);
        }
        $fresh = $this->repository->update($row, ['status' => JobRequestStatus::PENDING_APPROVAL]);
        $this->events->dispatch(new RecruitmentJobRequestUpdated($fresh));

        return $fresh;
    }

    public function approve(RecruitmentJobRequest $row, User $reviewer): RecruitmentJobRequest
    {
        return $this->decide($row, $reviewer, JobRequestStatus::APPROVED);
    }

    public function reject(RecruitmentJobRequest $row, User $reviewer, ?string $notes = null): RecruitmentJobRequest
    {
        return $this->decide($row, $reviewer, JobRequestStatus::REJECTED, $notes);
    }

    private function decide(
        RecruitmentJobRequest $row,
        User $reviewer,
        string $status,
        ?string $notes = null,
    ): RecruitmentJobRequest {
        $fresh = DB::transaction(function () use ($row, $reviewer, $status, $notes) {
            $locked = $this->repository->lockForTenant((int) $row->tenant_id, (int) $row->id);
            abort_if($locked === null, 404);
            if ($locked->status !== JobRequestStatus::PENDING_APPROVAL) {
                throw ValidationException::withMessages([
                    'status' => __('recruitment.job_requests.errors.only_pending_review'),
                ]);
            }

            return $this->repository->update($locked, [
                'status' => $status,
                'reviewed_by' => $reviewer->id,
                'reviewed_at' => now(),
                'review_notes' => $notes,
            ]);
        });

        if ($status === JobRequestStatus::APPROVED) {
            $this->events->dispatch(new RecruitmentJobRequestApproved($fresh, (int) $reviewer->id));
        } else {
            $this->events->dispatch(new RecruitmentJobRequestUpdated($fresh));
        }

        return $fresh;
    }

    private function tenantId(): int
    {
        $id = auth()->user()?->tenant_id;
        abort_if($id === null, 403);

        return (int) $id;
    }
}
