<?php

declare(strict_types=1);

namespace App\Services;

use App\Enums\AssetStatus;
use App\Models\Asset;
use App\Models\AssetCategory;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

/**
 * The two things a plain `Asset::create()` / `$asset->update()` cannot do
 * safely on their own: generate a collision-free tag, and refuse an illegal
 * status transition.
 *
 * Everything else about an asset — name, location, purchase details — is
 * ordinary Eloquent, which is why this stays a thin class rather than growing
 * a method per field.
 */
class AssetService
{
    public function __construct(
        private readonly AssetTagGenerator $tagGenerator,
    ) {}

    /**
     * @param  array<string, mixed>  $data  StoreAssetRequest::validated()
     * @param  bool  $silent  Skip the per-row `created` audit entry. Set by
     *                        ImportService, which writes one summary
     *                        `imported` event for the whole batch instead of
     *                        one `created` entry per row — see Auditable's
     *                        own docblock, which anticipated exactly this.
     */
    public function create(array $data, bool $silent = false): Asset
    {
        /** @var AssetCategory $category */
        $category = AssetCategory::query()->findOrFail($data['category_id']);

        // Auto-generation stays the default: a blank/omitted/null asset_tag
        // means the caller did not supply one manually. Uniqueness for a
        // manually supplied tag is already enforced by StoreAssetRequest
        // before this runs; ImportService's rows go through the same
        // validated() shape.
        $suppliedTag = trim((string) ($data['asset_tag'] ?? ''));
        unset($data['asset_tag']);

        $asset = new Asset;

        if ($silent) {
            $asset->disableAuditing();
        }

        $asset->fill($data);
        $asset->uuid = (string) Str::uuid();
        $asset->asset_tag = $suppliedTag !== '' ? $suppliedTag : $this->tagGenerator->next($category);
        $asset->save();

        return $asset;
    }

    /**
     * General field edits. Never touches `status` or `uuid` — status moves
     * only through changeStatus(), and uuid is permanent once assigned
     * (it's baked into every already-printed QR label). `asset_tag` is
     * editable here deliberately: unlike uuid, a tag is a human-facing label
     * an administrator may legitimately need to correct.
     *
     * @param  array<string, mixed>  $data  UpdateAssetRequest::validated()
     */
    public function update(Asset $asset, array $data): Asset
    {
        unset($data['status'], $data['uuid']);

        if (array_key_exists('asset_tag', $data) && trim((string) $data['asset_tag']) === '') {
            unset($data['asset_tag']);
        }

        $asset->update($data);

        return $asset;
    }

    /**
     * Move an asset to a new status, refusing anything not on its own legal
     * transition list — the one thing standing between this and a UI dropdown
     * that eventually lets someone move a disposed asset back into stock.
     */
    public function changeStatus(Asset $asset, AssetStatus $target): Asset
    {
        if (! $asset->status->canTransitionTo($target)) {
            throw ValidationException::withMessages([
                'status' => __(':from cannot move directly to :to.', [
                    'from' => $asset->status->label(),
                    'to' => $target->label(),
                ]),
            ]);
        }

        $asset->update(['status' => $target]);

        return $asset;
    }

    public function delete(Asset $asset): void
    {
        $asset->delete();
    }
}
