<?php

namespace App\Http\Controllers\Franchise;

use App\Http\Controllers\Controller;
use App\Models\PaymentTransaction;
use App\Models\User;
use App\Models\UserBinaryCarry;
use App\Models\WalletBalance;
use App\Models\Franchise;
use App\Services\BinaryPlacementService;
use App\Services\TreeService;
use App\Services\IdentityLimitService;
use App\Services\FranchiseNotificationService;
use App\Services\FranchiseTerritoryService;
use App\Services\MemberCodeService;
use App\Services\OtpService;
use App\Services\PaymentVerificationService;
use App\Services\UPIVerificationService;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;

class MemberController extends Controller
{
    public function index(Request $request): View
    {
        $franchise = DB::table('franchises')
            ->where('created_by', auth()->id())
            ->first();

        $members = new LengthAwarePaginator([], 0, 20);
        if ($franchise) {
            $members = DB::table('franchise_member_creations as fmc')
                ->join('users as u', 'u.id', '=', 'fmc.user_id')
                ->where('fmc.franchise_id', $franchise->id)
                ->select([
                    'u.id',
                    'u.member_code',
                    'u.name',
                    'u.mobile',
                    'u.status',
                    'u.joined_at',
                    'fmc.created_at',
                ])
                ->orderByDesc('fmc.id')
                ->paginate(20);
        }

        return view('franchise.members', [
            'franchise' => $franchise,
            'members' => $members,
        ]);
    }

    public function sendOtp(Request $request, OtpService $otpService): RedirectResponse
    {
        $validated = $request->validate([
            'mobile' => ['required', 'string', 'max:20'],
        ]);

        $otpPayload = $otpService->generateForMobile($validated['mobile'], 'franchise_create_member');

        $message = 'OTP sent.';
        if ((bool) config('otp.expose_in_response', false)) {
            $message .= ' Test OTP: '.$otpPayload['otp'];
        }

        return back()->with('status', $message);
    }

    public function store(
        Request $request,
        IdentityLimitService $identityLimitService,
        BinaryPlacementService $binaryPlacementService,
        TreeService $treeService,
        OtpService $otpService,
        MemberCodeService $memberCodeService,
        PaymentVerificationService $paymentVerificationService,
        UPIVerificationService $upiVerificationService,
        FranchiseNotificationService $notificationService,
        FranchiseTerritoryService $territoryService
    ): RedirectResponse {
        $validated = $request->validate([
            'name' => ['required', 'string', 'max:191'],
            'email' => ['nullable', 'email', 'max:191'],
            'mobile' => ['required', 'string', 'max:20'],
            'password' => ['required', 'string', 'min:8'],
            'sponsor_code' => ['nullable', 'string', 'max:191', 'regex:/^LW\d{7}$/i'],
            'placement_parent_id' => ['required', 'exists:users,id'],
            'placement_leg' => ['required', Rule::in(['left', 'right', 'auto'])],
            'package_amount' => ['required', 'numeric', 'min:0'],
            'payment_mode' => ['required', Rule::in(['cash', 'upi', 'gateway', 'wallet'])],
            'utr' => ['nullable', 'string', 'min:12', 'max:30'],
            'otp' => ['required', 'string'],
            'address' => ['nullable', 'string', 'max:500'],
            'pincode' => ['nullable', 'string', 'max:12'],
        ]);

        try {
            $otpVerified = $otpService->verifyMobile($validated['mobile'], 'franchise_create_member', $validated['otp']);
        } catch (\RuntimeException $exception) {
            return back()->withErrors(['otp' => $exception->getMessage()]);
        }
        if (! $otpVerified) {
            return back()->withErrors(['otp' => 'Invalid OTP.']);
        }

        $identityLimitService->assertCanCreate([
            'mobile' => $validated['mobile'],
            'email' => $validated['email'] ?? null,
        ]);

        $franchise = Franchise::query()->where('created_by', auth()->id())->first();

        if (! $franchise) {
            return back()->withErrors(['franchise' => 'Franchise not found.']);
        }

        try {
            $territoryService->enforcePincode($franchise, $validated['pincode'] ?? null, 'Distributor ID creation');
        } catch (\RuntimeException $exception) {
            return back()->withErrors(['pincode' => $exception->getMessage()]);
        }

        $sponsor = null;
        if (! empty($validated['sponsor_code'])) {
            $sponsorCode = strtoupper(trim((string) $validated['sponsor_code']));
            $sponsor = User::query()->where('referral_code', $sponsorCode)->first();
            if (! $sponsor) {
                return back()->withErrors(['sponsor_code' => 'Sponsor not found.']);
            }
        }


        if ($validated['payment_mode'] === 'wallet' && (float) $franchise->wallet_balance < (float) $validated['package_amount']) {
            return back()->withErrors(['payment_mode' => 'Insufficient franchise wallet balance.']);
        }

        $createdUser = DB::transaction(function () use (
            $validated,
            $sponsor,
            $franchise,
            $memberCodeService,
            $paymentVerificationService,
            $upiVerificationService,
            $binaryPlacementService
        ): User {
            $rootParent = User::query()
                ->whereKey((int) $validated['placement_parent_id'])
                ->where('role', 'user')
                ->lockForUpdate()
                ->first();

            if (! $rootParent) {
                throw ValidationException::withMessages(['placement_parent_id' => 'Placement parent not found.']);
            }

            $slot = $binaryPlacementService->resolveSlot($rootParent->id, (string) $validated['placement_leg']);

            $placementTaken = User::query()
                ->where('placement_parent_id', (int) $slot['parent_id'])
                ->where('placement_leg', (string) $slot['position'])
                ->lockForUpdate()
                ->exists();
            if ($placementTaken) {
                throw ValidationException::withMessages(['placement_leg' => 'Selected leg already occupied.']);
            }

            $user = User::query()->create([
                'member_code' => $memberCodeService->generateMemberCode(),
                'referral_code' => $memberCodeService->generateReferralCode(),
                'name' => $validated['name'],
                'email' => $validated['email'] ?? null,
                'mobile' => $validated['mobile'],
                'password' => Hash::make($validated['password']),
                'role' => 'user',
                'status' => 'pending',
                'sponsor_id' => $sponsor?->id,
                'placement_parent_id' => (int) $slot['parent_id'],
                'placement_leg' => (string) $slot['position'],
                'package_amount' => (float) $validated['package_amount'],
                'address' => $validated['address'] ?? null,
                'pincode' => $validated['pincode'] ?? null,
                ]);

            WalletBalance::query()->create(['user_id' => $user->id]);
            UserBinaryCarry::query()->create(['user_id' => $user->id]);

            DB::table('franchise_member_creations')->insert([
                'franchise_id' => $franchise->id,
                'user_id' => $user->id,
                'created_by' => auth()->id(),
                'created_at' => now(),
                'updated_at' => now(),
            ]);

            $payment = PaymentTransaction::query()->create([
                'user_id' => $user->id,
                'gateway' => $validated['payment_mode'],
                'gateway_transaction_id' => null,
                'merchant_order_ref' => 'JOIN-'.$user->member_code,
                'amount' => (float) $validated['package_amount'],
                'payment_for' => 'joining',
                'status' => 'pending',
            ]);

            if ($validated['payment_mode'] === 'cash') {
                $paymentVerificationService->markPaymentSuccess($payment, ['source' => 'franchise_cash']);
            }

            if ($validated['payment_mode'] === 'upi') {
                $utr = strtoupper(trim((string) ($validated['utr'] ?? '')));
                if ($utr === '') {
                    throw ValidationException::withMessages(['utr' => 'UTR is required for UPI payments.']);
                }

                $upi = $upiVerificationService->submitUTR($user->id, [
                    'purpose' => 'joining',
                    'utr' => $utr,
                    'amount' => (float) $validated['package_amount'],
                    'bank_name' => null,
                    'remark' => 'Franchise joining',
                ]);
                $upi->payment_transaction_id = $payment->id;
                $upi->save();
            }

            if ($validated['payment_mode'] === 'wallet') {
                $franchise = Franchise::query()->whereKey($franchise->id)->lockForUpdate()->first();
                if (! $franchise) {
                    throw ValidationException::withMessages(['franchise' => 'Franchise not found.']);
                }

                $packageAmount = (float) $validated['package_amount'];
                if ((float) $franchise->wallet_balance < $packageAmount) {
                    throw ValidationException::withMessages(['payment_mode' => 'Insufficient franchise wallet balance.']);
                }

                $franchise->wallet_balance = round(((float) $franchise->wallet_balance) - $packageAmount, 2);
                $franchise->save();

                DB::table('franchise_transactions')->insert([
                    'franchise_id' => $franchise->id,
                    'transaction_type' => 'commission_debit',
                    'amount' => $packageAmount,
                    'bv' => 0,
                    'quantity' => 0,
                    'reference' => 'JOIN-WALLET-'.$user->member_code,
                    'narration' => 'Joining paid via franchise wallet',
                    'transacted_at' => now(),
                    'created_by' => auth()->id(),
                    'created_at' => now(),
                    'updated_at' => now(),
                ]);

                $paymentVerificationService->markPaymentSuccess($payment, ['source' => 'franchise_wallet']);
            }

            return $user;
        });

        $notificationService->notify(
            $franchise,
            'Distributor ID Created',
            "New distributor {$createdUser->member_code} created successfully.",
            route('franchise.members')
        );
        $treeService->rebuildClosure($createdUser->id);

        return back()->with('status', 'Member created successfully.');
    }
}


