<?php

namespace App\Http\Controllers;

use App\Contracts\NationalIdentityProvider;
use App\Models\Branch;
use App\Models\Customer;
use App\Services\AuditService;
use App\Services\BranchAccessService;
use App\Services\CustomerDuplicateDetector;
use App\Services\CustomerRiskService;
use App\Services\NumberGenerator;
use App\Services\SecureFileService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;

class CustomerController extends Controller
{
    public function __construct(
        private BranchAccessService $branchAccess,
        private NationalIdentityProvider $identityProvider,
    ) {
    }

    public function index(Request $request): View
    {
        Gate::authorize('viewAny', Customer::class);

        $query = Customer::with('branch')->latest();
        $this->branchAccess->constrainBranchOwnedQuery($query, $request->user());

        if ($request->filled('search')) {
            $search = trim((string) $request->string('search'));
            $query->where(function ($q) use ($search): void {
                $q->where('customer_number', 'like', "%{$search}%")
                    ->orWhere('first_name', 'like', "%{$search}%")
                    ->orWhere('middle_name', 'like', "%{$search}%")
                    ->orWhere('last_name', 'like', "%{$search}%")
                    ->orWhere('phone', 'like', "%{$search}%")
                    ->orWhere('nin', 'like', "%{$search}%")
                    ->orWhere('email', 'like', "%{$search}%");
            });
        }
        if ($request->filled('branch_id')) {
            $branchId = $request->integer('branch_id');
            $this->branchAccess->assertBranchAccessible($request->user(), $branchId);
            $query->where('branch_id', $branchId);
        }
        if ($request->filled('status')) {
            $query->where('status', $request->string('status'));
        }

        return view('customers.index', [
            'customers' => $query->paginate(25)->withQueryString(),
            'branches' => $this->branchAccess->accessibleBranches($request->user()),
        ]);
    }

    public function create(Request $request): View
    {
        Gate::authorize('create', Customer::class);

        return view('customers.form', [
            'customer' => new Customer(['identity_mode' => 'without_nin', 'status' => 'active']),
            'branches' => $this->branchAccess->accessibleBranches($request->user()),
            'ninLookupEnabled' => $this->identityProvider->isConfigured(),
        ]);
    }

    public function store(Request $request, CustomerDuplicateDetector $duplicates, NumberGenerator $numbers, CustomerRiskService $risk, SecureFileService $files): RedirectResponse
    {
        Gate::authorize('create', Customer::class);

        $data = $this->validated($request);
        $this->branchAccess->assertBranchAccessible($request->user(), (int) $data['branch_id']);
        $this->rejectDuplicates($duplicates, $data);

        $photo = $data['photo'] ?? null;
        unset($data['photo']);

        $customer = DB::transaction(function () use ($request, $data, $numbers, $photo, $files): Customer {
            $branch = Branch::findOrFail($data['branch_id']);
            $personal = $this->personalFields($data);
            $personal['customer_number'] = $numbers->customer($branch);
            $personal['created_by'] = $request->user()->id;
            $personal['updated_by'] = $request->user()->id;
            if ($photo) {
                $personal['photo_path'] = $files->store($photo, 'customers/photos');
            }

            $customer = Customer::create($personal);
            $customer->address()->create($this->addressFields($data));
            if (! blank($data['next_of_kin_name'] ?? null)) {
                $customer->nextOfKin()->create([
                    'full_name' => $data['next_of_kin_name'],
                    'relationship' => $data['next_of_kin_relationship'] ?? null,
                    'phone' => $data['next_of_kin_phone'] ?? null,
                    'address' => $data['next_of_kin_address'] ?? null,
                    'is_primary' => true,
                ]);
            }
            return $customer;
        });

        $risk->refresh($customer);
        AuditService::record('customer.created', $customer, [], $customer->fresh()->toArray(), $customer->branch_id);

        return redirect()->route('customers.show', $customer)->with('success', 'Customer registered successfully.');
    }

    public function show(Request $request, Customer $customer): View
    {
        Gate::authorize('view', $customer);

        $relations = ['branch', 'address', 'nextOfKin', 'guarantors.creator', 'documents.uploader', 'blacklistRecords.author'];
        if ($request->user()->can('credit-score.view')) {
            $relations[] = 'riskProfile';
        }
        $customer->load($relations);

        $creditScoreHistory = $request->user()->can('credit-score.history')
            ? $customer->creditScoreHistories()->latest('calculated_at')->latest('id')->limit(8)->get()
            : collect();
        $recentLoans = $request->user()->can('loans.view')
            ? $customer->loans()->with('product')->latest('id')->limit(5)->get()
            : collect();

        $activity = \App\Models\AuditLog::with('user')
            ->where('auditable_type', Customer::class)
            ->where('auditable_id', $customer->id)
            ->latest()->limit(50)->get();

        return view('customers.show', compact('customer', 'activity', 'creditScoreHistory', 'recentLoans'));
    }

    public function edit(Request $request, Customer $customer): View
    {
        Gate::authorize('update', $customer);
        $customer->load(['address', 'nextOfKin']);
        return view('customers.form', [
            'customer' => $customer,
            'branches' => $this->branchAccess->accessibleBranches($request->user()),
            'ninLookupEnabled' => $this->identityProvider->isConfigured(),
        ]);
    }

    public function update(Request $request, Customer $customer, CustomerDuplicateDetector $duplicates, CustomerRiskService $risk, SecureFileService $files): RedirectResponse
    {
        Gate::authorize('update', $customer);
        $data = $this->validated($request, $customer);
        $this->branchAccess->assertBranchAccessible($request->user(), (int) $data['branch_id']);
        $this->rejectDuplicates($duplicates, $data, $customer->id);
        $before = $customer->toArray();
        $photo = $data['photo'] ?? null;
        unset($data['photo']);

        DB::transaction(function () use ($request, $customer, $data, $photo, $files): void {
            $personal = $this->personalFields($data);
            $personal['updated_by'] = $request->user()->id;
            if ($photo) {
                $files->delete($customer->photo_path);
                $personal['photo_path'] = $files->store($photo, 'customers/photos');
            }
            $customer->update($personal);
            $customer->address()->updateOrCreate(['customer_id' => $customer->id], $this->addressFields($data));

            if (! blank($data['next_of_kin_name'] ?? null)) {
                $kin = $customer->nextOfKin()->where('is_primary', true)->first();
                $values = [
                    'full_name' => $data['next_of_kin_name'], 'relationship' => $data['next_of_kin_relationship'] ?? null,
                    'phone' => $data['next_of_kin_phone'] ?? null, 'address' => $data['next_of_kin_address'] ?? null, 'is_primary' => true,
                ];
                $kin ? $kin->update($values) : $customer->nextOfKin()->create($values);
            }
        });

        $risk->refresh($customer->fresh());
        AuditService::record('customer.updated', $customer, $before, $customer->fresh()->toArray(), $customer->branch_id);
        return redirect()->route('customers.show', $customer)->with('success', 'Customer updated.');
    }

    private function validated(Request $request, ?Customer $customer = null): array
    {
        $ninRules = [
            Rule::requiredIf($request->input('identity_mode') === 'with_nin'),
            'nullable',
            'string',
            'max:100',
        ];

        if ($this->identityProvider->isConfigured()) {
            $ninRules[] = 'regex:/^\d{20}$/';
        }

        return $request->validate([
            'branch_id' => ['required', 'exists:branches,id'],
            'identity_mode' => ['required', Rule::in(['with_nin', 'without_nin'])],
            'first_name' => ['required', 'string', 'max:120'],
            'middle_name' => ['nullable', 'string', 'max:120'],
            'last_name' => ['required', 'string', 'max:120'],
            'gender' => ['nullable', Rule::in(['male', 'female', 'other'])],
            'date_of_birth' => ['nullable', 'date', 'before_or_equal:today'],
            'nin' => $ninRules,
            'phone' => ['required', 'string', 'max:50'],
            'alternative_phone' => ['nullable', 'string', 'max:50'],
            'email' => ['nullable', 'email', 'max:255'],
            'occupation' => ['nullable', 'string', 'max:150'],
            'employer_business' => ['nullable', 'string', 'max:255'],
            'monthly_income' => ['nullable', 'numeric', 'min:0', 'max:9999999999999999.99'],
            'marital_status' => ['nullable', Rule::in(['single', 'married', 'divorced', 'widowed', 'other'])],
            'status' => ['required', Rule::in(['active', 'inactive', 'deceased'])],
            'photo' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:4096'],
            'region' => ['nullable', 'string', 'max:120'],
            'district' => ['nullable', 'string', 'max:120'],
            'ward' => ['nullable', 'string', 'max:120'],
            'street_village' => ['nullable', 'string', 'max:180'],
            'house_number' => ['nullable', 'string', 'max:100'],
            'latitude' => ['nullable', 'numeric', 'between:-90,90'],
            'longitude' => ['nullable', 'numeric', 'between:-180,180'],
            'next_of_kin_name' => ['nullable', 'string', 'max:255'],
            'next_of_kin_relationship' => ['nullable', 'string', 'max:100'],
            'next_of_kin_phone' => ['nullable', 'string', 'max:50'],
            'next_of_kin_address' => ['nullable', 'string', 'max:255'],
        ], [
            'nin.regex' => 'NIN must contain exactly 20 digits while NIN verification is enabled.',
        ]);
    }

    private function personalFields(array $data): array
    {
        return collect($data)->only(['branch_id','identity_mode','first_name','middle_name','last_name','gender','date_of_birth','nin','phone','alternative_phone','email','occupation','employer_business','monthly_income','marital_status','status'])->all();
    }

    private function addressFields(array $data): array
    {
        return collect($data)->only(['region','district','ward','street_village','house_number','latitude','longitude'])->all();
    }

    private function rejectDuplicates(CustomerDuplicateDetector $duplicates, array $data, ?int $excludeId = null): void
    {
        $matches = $duplicates->find($data['nin'] ?? null, $data['phone'] ?? null, $data['email'] ?? null, $excludeId);
        if ($matches->isNotEmpty()) {
            throw ValidationException::withMessages([
                'duplicate' => 'Possible duplicate customer found: '.$matches->map(fn ($c) => "{$c->customer_number} ({$c->full_name})")->join(', ').'. Review the existing record before continuing.',
            ]);
        }
    }
}
