<?php

namespace App\Services;

use App\Models\User;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;

class UserBranchAssignmentService
{
    /**
     * @param array<int|string> $branchIds
     * @return array<int>
     */
    public function normalize(int $primaryBranchId, array $branchIds = []): array
    {
        if ($primaryBranchId <= 0) {
            throw new InvalidArgumentException('A valid primary branch is required.');
        }

        return collect($branchIds)
            ->push($primaryBranchId)
            ->map(fn ($id) => (int) $id)
            ->filter(fn ($id) => $id > 0)
            ->unique()
            ->values()
            ->all();
    }

    /**
     * Persist the canonical primary branch and branch_user pivot together.
     *
     * @param array<int|string> $branchIds
     */
    public function synchronize(User $user, int $primaryBranchId, array $branchIds = []): void
    {
        $branchIds = $this->normalize($primaryBranchId, $branchIds);

        DB::transaction(function () use ($user, $primaryBranchId, $branchIds): void {
            if ((int) $user->primary_branch_id !== $primaryBranchId) {
                $user->forceFill(['primary_branch_id' => $primaryBranchId])->save();
            }

            $user->branches()->sync($this->pivotPayload($branchIds, $primaryBranchId));
            $user->unsetRelation('branches');
        });
    }

    /**
     * Inspect and optionally repair a legacy user's primary/pivot mismatch.
     *
     * If users.primary_branch_id is present it is authoritative. If it is null and
     * exactly one pivot row is already marked primary, that row is promoted to the
     * canonical primary branch. Otherwise no primary branch is guessed.
     *
     * @return array{changed: bool, primary_before: int|null, primary_after: int|null, branch_ids: array<int>}
     */
    public function repair(User $user, bool $dryRun = false): array
    {
        $rows = DB::table('branch_user')
            ->where('user_id', $user->getKey())
            ->orderBy('branch_id')
            ->get(['branch_id', 'is_primary']);

        $primaryBefore = $user->primary_branch_id === null ? null : (int) $user->primary_branch_id;
        $primaryAfter = $primaryBefore;

        if ($primaryAfter === null) {
            $markedPrimary = $rows
                ->filter(fn ($row) => (bool) $row->is_primary)
                ->pluck('branch_id')
                ->map(fn ($id) => (int) $id)
                ->values();

            if ($markedPrimary->count() === 1) {
                $primaryAfter = $markedPrimary->first();
            }
        }

        $branchIds = $rows
            ->pluck('branch_id')
            ->map(fn ($id) => (int) $id)
            ->when($primaryAfter !== null, fn ($ids) => $ids->push($primaryAfter))
            ->unique()
            ->sort()
            ->values()
            ->all();

        $currentPrimaryFlags = $rows
            ->mapWithKeys(fn ($row) => [(int) $row->branch_id => (bool) $row->is_primary])
            ->all();

        $desiredPrimaryFlags = collect($branchIds)
            ->mapWithKeys(fn ($branchId) => [
                $branchId => $primaryAfter !== null && $branchId === $primaryAfter,
            ])
            ->all();

        $changed = $primaryBefore !== $primaryAfter
            || $currentPrimaryFlags !== $desiredPrimaryFlags;

        if ($changed && ! $dryRun) {
            DB::transaction(function () use ($user, $primaryAfter, $branchIds): void {
                if ($primaryAfter === null) {
                    DB::table('branch_user')
                        ->where('user_id', $user->getKey())
                        ->update(['is_primary' => false, 'updated_at' => now()]);

                    return;
                }

                $user->forceFill(['primary_branch_id' => $primaryAfter])->save();
                $user->branches()->sync($this->pivotPayload($branchIds, $primaryAfter));
                $user->unsetRelation('branches');
            });
        }

        return [
            'changed' => $changed,
            'primary_before' => $primaryBefore,
            'primary_after' => $primaryAfter,
            'branch_ids' => $branchIds,
        ];
    }

    /**
     * @param array<int> $branchIds
     * @return array<int, array{is_primary: bool}>
     */
    private function pivotPayload(array $branchIds, int $primaryBranchId): array
    {
        return collect($branchIds)
            ->mapWithKeys(fn ($branchId) => [
                (int) $branchId => ['is_primary' => (int) $branchId === $primaryBranchId],
            ])
            ->all();
    }
}
