<?php

namespace App\Services;

use App\Models\Collateral;
use App\Models\Customer;
use App\Models\Loan;
use App\Models\LoanPayment;
use App\Models\RemotePaymentSubmission;
use App\Models\User;
use App\Support\Authorization\PermissionName;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;

class GlobalSearchService
{
    public function __construct(
        private BranchAccessService $branchAccess,
        private BranchContext $branchContext,
    ) {
    }

    public function search(User $user, string $term, int $perType = 8): array
    {
        Gate::forUser($user)->authorize(PermissionName::SEARCH_USE->value);

        return $this->branchContext->forUser(
            $user,
            fn () => $this->searchWithinContext($user, $term, $perType),
        );
    }

    private function searchWithinContext(User $user, string $term, int $perType): array
    {
        $term = trim($term);
        if (mb_strlen($term) < 2) return [];

        $branchIds = $this->branchAccess->accessibleBranchIds($user);

        if ($branchIds === []) return [];

        $groups = [];

        if ($user->can(PermissionName::CUSTOMERS_VIEW->value)) {
            $customers = Customer::query()->with('branch')->whereIn('branch_id', $branchIds);
            $this->whereTerms($customers, $term, ['first_name','middle_name','last_name','phone','alternative_phone','nin','customer_number','email']);
            $rows = $customers->limit($perType * 3)->get();
            $groups[] = $this->group('customers', 'Customers', $this->rank($rows, $term, fn (Customer $customer) => [
                $customer->customer_number, $customer->phone, $customer->alternative_phone, $customer->nin, $customer->email,
                $customer->first_name, $customer->middle_name, $customer->last_name, $customer->full_name,
            ], fn (Customer $customer) => [
                'type'=>'customer','title'=>$customer->full_name,'identifier'=>$customer->customer_number,
                'subtitle'=>collect([$customer->phone, $customer->nin ? 'NIN '.$customer->nin : null, $customer->branch?->name])->filter()->join(' · '),
                'url'=>route('customers.show', $customer),
            ], $perType));
        }

        if ($user->can(PermissionName::LOANS_VIEW->value)) {
            $loans = Loan::query()->with(['customer','branch'])->whereIn('branch_id', $branchIds);
            $this->whereTerms($loans, $term, ['loan_number']);
            $rows = $loans->limit($perType * 3)->get();
            $groups[] = $this->group('loans', 'Loans', $this->rank($rows, $term, fn (Loan $loan) => [$loan->loan_number], fn (Loan $loan) => [
                'type'=>'loan','title'=>$loan->loan_number,'identifier'=>$loan->loan_number,
                'subtitle'=>collect([$loan->customer?->full_name, $loan->branch?->name, Str::headline(str_replace('_',' ', $loan->status))])->filter()->join(' · '),
                'url'=>route('loans.show', $loan),
            ], $perType));

            $payments = LoanPayment::query()->with(['loan','customer','branch'])->whereIn('branch_id', $branchIds);
            $this->whereTerms($payments, $term, ['receipt_number','reference']);
            $rows = $payments->limit($perType * 3)->get();
            $groups[] = $this->group('payments', 'Payment Receipts', $this->rank($rows, $term, fn (LoanPayment $payment) => [$payment->receipt_number, $payment->reference], fn (LoanPayment $payment) => [
                'type'=>'payment','title'=>$payment->receipt_number,'identifier'=>$payment->reference ?: $payment->receipt_number,
                'subtitle'=>collect([$payment->loan?->loan_number, $payment->customer?->full_name, $payment->reference ? 'Ref '.$payment->reference : null, $payment->branch?->name])->filter()->join(' · '),
                'url'=>route('loan-payments.receipt', $payment),
            ], $perType));
        }

        if ($user->can(PermissionName::COLLATERAL_VIEW->value)) {
            $collaterals = Collateral::query()->with(['customer','branch'])->whereIn('branch_id', $branchIds);
            $this->whereTerms($collaterals, $term, ['code']);
            $rows = $collaterals->limit($perType * 3)->get();
            $groups[] = $this->group('collateral', 'Collateral', $this->rank($rows, $term, fn (Collateral $collateral) => [$collateral->code], fn (Collateral $collateral) => [
                'type'=>'collateral','title'=>$collateral->code,'identifier'=>$collateral->code,
                'subtitle'=>collect([$collateral->item, $collateral->customer?->full_name, $collateral->branch?->name])->filter()->join(' · '),
                'url'=>route('collateral.show', $collateral),
            ], $perType));
        }

        if ($user->can(PermissionName::PAYMENTS_REMOTE_VERIFY->value)) {
            $remote = RemotePaymentSubmission::query()->with(['loan','customer','branch'])->whereIn('branch_id', $branchIds);
            $this->whereTerms($remote, $term, ['transaction_reference']);
            $rows = $remote->limit($perType * 3)->get();
            $groups[] = $this->group('remote-payments', 'Remote Payment References', $this->rank($rows, $term, fn (RemotePaymentSubmission $submission) => [$submission->transaction_reference], fn (RemotePaymentSubmission $submission) => [
                'type'=>'remote_payment','title'=>$submission->transaction_reference,'identifier'=>$submission->submission_number,
                'subtitle'=>collect([$submission->loan?->loan_number, $submission->customer?->full_name, Str::headline(str_replace('_',' ', $submission->status)), $submission->branch?->name])->filter()->join(' · '),
                'url'=>route('remote-payments.show', $submission),
            ], $perType));
        }

        return collect($groups)->filter(fn (array $group) => $group['results'] !== [])->values()->all();
    }

    private function whereTerms(Builder $query, string $term, array $columns): void
    {
        $terms = collect(preg_split('/\s+/', trim($term)) ?: [])->filter()->take(6);
        foreach ($terms as $part) {
            $query->where(function (Builder $inner) use ($columns, $part): void {
                foreach ($columns as $index => $column) {
                    $method = $index === 0 ? 'where' : 'orWhere';
                    $inner->{$method}($column, 'like', '%'.$part.'%');
                }
            });
        }
    }

    private function rank(Collection $rows, string $term, callable $identifiers, callable $mapper, int $limit): array
    {
        $needle = mb_strtolower(trim($term));
        return $rows->map(function ($row) use ($needle, $identifiers, $mapper): array {
            $values = collect($identifiers($row))->filter()->map(fn ($value) => mb_strtolower(trim((string) $value)));
            $score = $values->contains($needle) ? 0 : ($values->contains(fn ($value) => str_starts_with($value, $needle)) ? 1 : 2);
            return ['score'=>$score,'id'=>$row->getKey(),'result'=>$mapper($row)];
        })->sortBy(fn (array $row) => sprintf('%d-%020d', $row['score'], $row['id']))->take($limit)->pluck('result')->values()->all();
    }

    private function group(string $key, string $label, array $results): array
    {
        return ['key'=>$key,'label'=>$label,'results'=>$results];
    }
}
