<?php

namespace App\Services\Workflow;

use App\Enums\DocumentStatus;
use App\Models\ApprovalHistory;
use DomainException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;

final class ApprovalWorkflowService
{
    public function submit(Model $document, ?string $reason = null): Model
    {
        $this->assertStatus($document, [DocumentStatus::DRAFT, DocumentStatus::REJECTED]);
        return $this->transition($document, DocumentStatus::PENDING_APPROVAL, 'submitted', $reason);
    }

    public function approve(Model $document, ?string $reason = null): Model
    {
        $this->assertStatus($document, [DocumentStatus::PENDING_APPROVAL]);
        if (config('accounting.maker_checker') && (int) $document->created_by === (int) auth()->id()) {
            throw new DomainException('Maker-checker is enabled. The creator cannot approve this document.');
        }

        return DB::transaction(function () use ($document, $reason): Model {
            $document->forceFill([
                'status' => DocumentStatus::APPROVED,
                'approved_by' => auth()->id(),
                'approved_at' => now(),
            ])->save();
            $this->history($document, 'approved', DocumentStatus::PENDING_APPROVAL->value, DocumentStatus::APPROVED->value, $reason);
            return $document->refresh();
        });
    }

    public function reject(Model $document, string $reason): Model
    {
        $this->assertStatus($document, [DocumentStatus::PENDING_APPROVAL]);
        return $this->transition($document, DocumentStatus::REJECTED, 'rejected', $reason);
    }

    private function transition(Model $document, DocumentStatus $to, string $action, ?string $reason): Model
    {
        return DB::transaction(function () use ($document, $to, $action, $reason): Model {
            $from = $this->statusValue($document);
            $document->forceFill(['status' => $to])->save();
            $this->history($document, $action, $from, $to->value, $reason);
            return $document->refresh();
        });
    }

    /** @param array<int, DocumentStatus> $allowed */
    private function assertStatus(Model $document, array $allowed): void
    {
        $current = $this->statusValue($document);
        if (! in_array($current, array_map(fn (DocumentStatus $status) => $status->value, $allowed), true)) {
            throw new DomainException('This action is not allowed from status: '.str_replace('_', ' ', $current));
        }
    }

    private function statusValue(Model $document): string
    {
        return $document->status instanceof DocumentStatus ? $document->status->value : (string) $document->status;
    }

    private function history(Model $document, string $action, ?string $from, string $to, ?string $reason): void
    {
        ApprovalHistory::query()->create([
            'approvable_type' => $document::class,
            'approvable_id' => $document->getKey(),
            'action' => $action,
            'from_status' => $from,
            'to_status' => $to,
            'reason' => $reason,
            'user_id' => auth()->id(),
            'acted_at' => now(),
        ]);
    }
}
