<?php

namespace App\Domains\Staff\Services;

use App\Domains\Auth\Models\ResetPasswordAttempt;
use App\Domains\Auth\Models\User;
use App\Domains\Auth\Services\ResetPasswordAttemptService;
use App\Domains\Authorization\Enums\RoleCode;
use App\Domains\Catalog\Models\Category;
use App\Domains\Tenant\Models\Tenant;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class StaffService
{
    public function __construct(
        private readonly ResetPasswordAttemptService $resetPasswordAttemptService,
    ) {}

    /** @return Collection<int, User> */
    public function list(Tenant $tenant, ?string $search = null): Collection
    {
        return $this->staffQuery($tenant)
            ->with(['role', 'scanCategories' => fn ($query) => $query->orderBy('nombre')])
            ->when($search, function (Builder $query, string $search): void {
                $query->where(function (Builder $query) use ($search): void {
                    $query->where('nombre_apellido', 'like', "%{$search}%")
                        ->orWhere('dni', 'like', "%{$search}%")
                        ->orWhere('email', 'like', "%{$search}%");
                });
            })
            ->orderBy('nombre_apellido')
            ->get();
    }

    /** @return Collection<int, Category> */
    private function assignableCategories(Tenant $tenant): Collection
    {
        return Category::query()
            ->whereNull('categoria_id')
            ->where(function (Builder $query) use ($tenant): void {
                $query->where('tenant_code', $tenant->codigo)
                    ->orWhereHas('catalogItems', fn (Builder $items) => $items
                        ->where('tenant_code', $tenant->codigo));
            })
            ->orderBy('nombre')
            ->get();
    }

    /** @param array<string, mixed> $data */
    public function create(Tenant $tenant, array $data): User
    {
        $categoryIds = $this->categoryIdsFor($tenant, $data);
        $this->assertCategoriesBelongToTenant($tenant, $categoryIds);

        return DB::transaction(function () use ($tenant, $data, $categoryIds): User {
            $staff = User::query()->create([
                ...Arr::only($data, ['nombre_apellido', 'dni', 'email']),
                'email' => mb_strtolower(trim((string) $data['email'])),
                'password' => Str::random(64),
                'rol_codigo' => RoleCode::Scanner->value,
                'tenant_codigo' => $tenant->codigo,
            ]);
            $staff->scanCategories()->sync($categoryIds);
            $this->resetPasswordAttemptService->createForScannerEmail(
                $staff->email,
                ResetPasswordAttempt::REASON_STAFF_CREATED,
            );

            return $staff->load('role', 'scanCategories');
        });
    }

    /** @param array<string, mixed> $data */
    public function update(Tenant $tenant, int $staffId, array $data): User
    {
        $staff = $this->find($tenant, $staffId);
        $categoryIds = $this->categoryIdsFor($tenant, $data);
        $this->assertCategoriesBelongToTenant($tenant, $categoryIds);

        return DB::transaction(function () use ($staff, $data, $categoryIds): User {
            $attributes = Arr::only($data, ['nombre_apellido', 'dni', 'email']);
            $attributes['email'] = mb_strtolower(trim((string) $data['email']));
            $staff->update($attributes);
            $staff->scanCategories()->sync($categoryIds);

            return $staff->load('role', 'scanCategories');
        });
    }

    public function delete(Tenant $tenant, int $staffId): void
    {
        $this->find($tenant, $staffId)->delete();
    }

    public function find(Tenant $tenant, int $staffId): User
    {
        return $this->staffQuery($tenant)->findOrFail($staffId);
    }

    private function staffQuery(Tenant $tenant): Builder
    {
        return User::query()
            ->where('tenant_codigo', $tenant->codigo)
            ->where('rol_codigo', RoleCode::Scanner->value);
    }

    /**
     * @param  array<string, mixed>  $data
     * @return array<int, int>
     */
    private function categoryIdsFor(Tenant $tenant, array $data): array
    {
        if (! $tenant->requiresScannerCategoryValidation()) {
            return [];
        }

        return $data['category_ids'];
    }

    /** @param array<int, int> $categoryIds */
    private function assertCategoriesBelongToTenant(Tenant $tenant, array $categoryIds): void
    {
        $validIds = $this->assignableCategories($tenant)
            ->whereIn('id', $categoryIds)
            ->pluck('id');

        if ($validIds->count() !== count($categoryIds)) {
            throw ValidationException::withMessages([
                'category_ids' => 'Una o más categorías no pertenecen al tenant.',
            ]);
        }
    }
}
