<?php

namespace App\Modules\HR\Recruitment\Services;

use App\Modules\HR\Recruitment\DTOs\ApplicationWriteData;
use App\Modules\HR\Recruitment\Events\RecruitmentApplicationCreated;
use App\Modules\HR\Recruitment\Events\RecruitmentApplicationDeleted;
use App\Modules\HR\Recruitment\Events\RecruitmentApplicationUpdated;
use App\Modules\HR\Recruitment\Models\RecruitmentApplication;
use App\Modules\HR\Recruitment\Repositories\RecruitmentApplicationRepository;
use App\Modules\HR\Recruitment\Support\ApplicationStatus;
use App\Modules\HR\Recruitment\Support\RecruitmentTenantId;
use App\Support\SystemReferenceAssigner;
use App\Support\SystemReferenceGenerator;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

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

    public function store(ApplicationWriteData $data, ?int $userId = null, bool $withTrackingToken = false): RecruitmentApplication
    {
        $this->assertUnique($data->vacancyId, $data->candidateId);

        $row = DB::transaction(function () use ($data, $userId, $withTrackingToken) {
            $payload = array_merge($data->toArray(), [
                'tenant_id' => RecruitmentTenantId::require(),
                'reference_code' => $this->references->pendingPlaceholder(SystemReferenceGenerator::APPLICATION),
                'status' => ApplicationStatus::RECEIVED,
                'applied_at' => now(),
                'created_by' => RecruitmentTenantId::actorId($userId),
            ]);
            if ($withTrackingToken) {
                $payload['tracking_token'] = Str::lower(Str::random(48));
            }

            $created = $this->repository->create($payload);

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

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

        return $row;
    }

    public function update(RecruitmentApplication $row, ApplicationWriteData $data): RecruitmentApplication
    {
        $this->assertUnique($data->vacancyId, $data->candidateId, (int) $row->id);
        $fresh = $this->repository->update($row, $data->toArray());
        $this->events->dispatch(new RecruitmentApplicationUpdated($fresh));

        return $fresh;
    }

    public function updateStatus(RecruitmentApplication $row, string $status): RecruitmentApplication
    {
        $fresh = $this->repository->update($row, ['status' => $status]);
        $this->events->dispatch(new RecruitmentApplicationUpdated($fresh));

        return $fresh;
    }

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

    private function assertUnique(int $vacancyId, int $candidateId, ?int $exceptId = null): void
    {
        if ($this->repository->existsForVacancyCandidate($vacancyId, $candidateId, $exceptId)) {
            throw ValidationException::withMessages([
                'candidate_id' => __('recruitment.applications.errors.duplicate'),
            ]);
        }
    }
}
