<?php

namespace App\Modules\HR\Recruitment\Services;

use App\Modules\HR\Recruitment\Events\RecruitmentOfferLetterStatusChanged;
use App\Modules\HR\Recruitment\Models\RecruitmentOfferLetter;
use App\Modules\HR\Recruitment\Repositories\RecruitmentOfferLetterRepository;
use App\Modules\HR\Recruitment\Support\OfferLetterStatus;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class RecruitmentOfferLetterWorkflowService
{
    public function __construct(
        private readonly RecruitmentOfferLetterRepository $repository,
        private readonly RecruitmentOfferLetterStatusHistoryService $history,
        private readonly RecruitmentOfferLetterExpiryService $expiry,
        private readonly Dispatcher $events,
    ) {}

    public function submit(RecruitmentOfferLetter $row, int $userId): RecruitmentOfferLetter
    {
        return $this->transition($row, OfferLetterStatus::DRAFT, OfferLetterStatus::UNDER_REVIEW, 'submit', $userId, [
            'submitted_at' => now(),
        ]);
    }

    public function approve(RecruitmentOfferLetter $row, int $userId): RecruitmentOfferLetter
    {
        return $this->transition($row, OfferLetterStatus::UNDER_REVIEW, OfferLetterStatus::APPROVED, 'approve', $userId, [
            'approved_by' => $userId,
            'approved_at' => now(),
        ]);
    }

    public function send(RecruitmentOfferLetter $row, int $userId): RecruitmentOfferLetter
    {
        $this->expiry->assertReadyToSend($row);

        return $this->transition($row, OfferLetterStatus::APPROVED, OfferLetterStatus::SENT, 'send', $userId, [
            'sent_by' => $userId,
            'sent_at' => now(),
            'sent_count' => ((int) $row->sent_count) + 1,
            'email_status' => 'queued',
        ]);
    }

    public function resend(RecruitmentOfferLetter $row, int $userId): RecruitmentOfferLetter
    {
        $this->assertStatus($row, OfferLetterStatus::SENT, 'only_sent_resend');
        $this->expiry->assertNotPastDeadline($row);

        return $this->transition($row, OfferLetterStatus::SENT, OfferLetterStatus::SENT, 'resend', $userId, [
            'sent_by' => $userId,
            'sent_at' => now(),
            'sent_count' => ((int) $row->sent_count) + 1,
            'email_status' => 'queued',
        ]);
    }

    public function accept(RecruitmentOfferLetter $row, int $userId, ?string $notes = null): RecruitmentOfferLetter
    {
        $this->expiry->assertNotPastDeadline($row);

        return $this->transition($row, OfferLetterStatus::SENT, OfferLetterStatus::ACCEPTED, 'accept', $userId, [
            'accepted_at' => now(),
            'decision_notes' => $notes,
        ], $notes);
    }

    public function reject(RecruitmentOfferLetter $row, int $userId, ?string $notes = null): RecruitmentOfferLetter
    {
        return $this->transition($row, OfferLetterStatus::SENT, OfferLetterStatus::REJECTED, 'reject', $userId, [
            'rejected_at' => now(),
            'decision_notes' => $notes,
        ], $notes);
    }

    public function expire(RecruitmentOfferLetter $row, int $userId, ?string $notes = null): RecruitmentOfferLetter
    {
        return $this->transition($row, OfferLetterStatus::SENT, OfferLetterStatus::EXPIRED, 'expire', $userId, [
            'expired_at' => now(),
            'decision_notes' => $notes,
        ], $notes);
    }

    public function withdraw(RecruitmentOfferLetter $row, int $userId, ?string $notes = null): RecruitmentOfferLetter
    {
        if (! in_array($row->status, OfferLetterStatus::withdrawable(), true)) {
            throw ValidationException::withMessages([
                'status' => __('recruitment.offer_letters.errors.cannot_withdraw'),
            ]);
        }

        return $this->transition($row, (string) $row->status, OfferLetterStatus::WITHDRAWN, 'withdraw', $userId, [
            'withdrawn_at' => now(),
            'decision_notes' => $notes,
        ], $notes);
    }

    /** @param  array<string, mixed>  $extra */
    private function transition(
        RecruitmentOfferLetter $row,
        string $from,
        string $to,
        string $action,
        int $userId,
        array $extra = [],
        ?string $notes = null,
    ): RecruitmentOfferLetter {
        if ($from !== $to || $action !== 'resend') {
            $this->assertStatus($row, $from, 'invalid_transition');
        }

        $fresh = DB::transaction(function () use ($row, $from, $to, $action, $userId, $extra, $notes) {
            $locked = $this->repository->lockForTenant((int) $row->tenant_id, (int) $row->id);
            abort_if($locked === null, 404);
            if ($locked->status !== $from) {
                throw ValidationException::withMessages([
                    'status' => __('recruitment.offer_letters.errors.invalid_transition'),
                ]);
            }
            $updated = $this->repository->update($locked, array_merge(['status' => $to], $extra));
            $this->history->record($updated, $from, $to, $action, $userId, $notes);

            return $updated;
        });

        $this->events->dispatch(new RecruitmentOfferLetterStatusChanged($fresh, $action, $from, $to));

        return $fresh;
    }

    private function assertStatus(RecruitmentOfferLetter $row, string $expected, string $errorKey): void
    {
        if ($row->status !== $expected) {
            throw ValidationException::withMessages([
                'status' => __('recruitment.offer_letters.errors.'.$errorKey),
            ]);
        }
    }
}
