<?php

namespace App\Livewire\Users;

use App\Domain\Identity\Services\AccountMergeService;
use App\Domain\Shared\Exceptions\DomainException;
use App\Models\User;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Title;
use Livewire\Component;

/**
 * Clearing the duplicate accounts the credential normalisation left behind.
 *
 * This is the prerequisite for ever putting a unique index on users.phone.
 * Until the duplicates are gone, `->first()` on a phone number is an
 * account-selection primitive, which is why AuthService refuses an ambiguous
 * login rather than picking one.
 */
#[Layout('layouts.app')]
#[Title('دمج الحسابات المكررة')]
class DuplicateAccountMerge extends Component
{
    #[Locked]
    public ?int $keepId = null;

    #[Locked]
    public ?int $mergeId = null;

    public function mount(): void
    {
        $this->authorize('users.merge');
    }

    public function choose(int $keepId, int $mergeId): void
    {
        $this->authorize('users.merge');

        // Ids arrive from the browser, so they are re-checked against the
        // academy rather than trusted from the list that rendered them.
        $this->keepId = $this->inScope($keepId)->id;
        $this->mergeId = $this->inScope($mergeId)->id;
    }

    public function cancel(): void
    {
        $this->keepId = null;
        $this->mergeId = null;
    }

    public function merge(AccountMergeService $merger): void
    {
        $this->authorize('users.merge');

        if (! $this->keepId || ! $this->mergeId) {
            return;
        }

        try {
            $merger->merge($this->inScope($this->keepId), $this->inScope($this->mergeId), auth()->user());
            session()->flash('success', __('تم دمج الحسابين'));
        } catch (DomainException $e) {
            session()->flash('error', $e->getMessage());
        }

        $this->cancel();
    }

    /**
     * Deliberately academy-wide, not branch-filtered.
     *
     * A duplicate pair is two accounts for one person, and the second one
     * usually exists precisely because a different desk created it — so the
     * pair straddles branches more often than not. Filtering the candidates by
     * branch would show one half of such a pair and hide the other, which
     * leaves the duplicate unmergeable and blocks the unique index on
     * users.phone that this screen exists to unblock.
     *
     * The safety here is the `users.merge` permission plus inScope(), which
     * re-checks every id against the academy. `users` carries no BranchScope in
     * the first place (see FiltersUsersByBranch), so there is nothing to opt
     * out of — this note is the opt-out being made visible.
     */
    public function render()
    {
        $academyId = app('current_academy')->id;

        return view('livewire.users.duplicate-account-merge', [
            'groups' => app(AccountMergeService::class)->findDuplicates($academyId),
            'keep' => $this->keepId ? $this->inScope($this->keepId) : null,
            'merge' => $this->mergeId ? $this->inScope($this->mergeId) : null,
        ]);
    }

    private function inScope(int $userId): User
    {
        // Only the academy scope comes off, and only because the line below
        // re-applies it by hand. The blanket withoutGlobalScopes() this
        // replaces also dropped SoftDeletingScope, so a soft-deleted account
        // resolved here and could be merged into a live one.
        return User::withoutGlobalScope('academy')
            ->where('academy_id', app('current_academy')->id)
            ->findOrFail($userId);
    }
}
