<?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\BinaryLedger;
use App\Models\Franchise;
use App\Services\IdentityLimitService;
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');

        return back()->with('status', 'OTP sent. Test OTP: '.$otpPayload['otp']);
    }

    public function store(
        Request $request,
        IdentityLimitService $identityLimitService,
        OtpService $otpService,
        PaymentVerificationService $paymentVerificationService,
        UPIVerificationService $upiVerificationService
    ): 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'],
            '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'],
        ]);

        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.']);
        }

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

        $placementLeg = $validated['placement_leg'];
        if ($placementLeg === 'auto') {
            $placementLeg = $this->resolvePlacementLeg((int) $validated['placement_parent_id']);
        }

        $placementTaken = User::query()
            ->where('placement_parent_id', (int) $validated['placement_parent_id'])
            ->where('placement_leg', $placementLeg)
            ->exists();
        if ($placementTaken) {
            return back()->withErrors(['placement_leg' => 'Selected leg already occupied.']);
        }

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

        DB::transaction(function () use (
            $validated,
            $sponsor,
            $franchise,
            $paymentVerificationService,
            $upiVerificationService,
            $placementLeg
        ): void {
            $user = User::query()->create([
                'member_code' => $this->generateUniqueCode('AKR', 'member_code'),
                'referral_code' => $this->generateUniqueCode('REF', 'referral_code'),
                '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) $validated['placement_parent_id'],
                'placement_leg' => $placementLeg,
                'package_amount' => (float) $validated['package_amount'],
                'address' => $validated['address'] ?? 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();
                $franchise->wallet_balance = round(((float) $franchise->wallet_balance) - (float) $validated['package_amount'], 2);
                $franchise->save();

                DB::table('franchise_transactions')->insert([
                    'franchise_id' => $franchise->id,
                    'transaction_type' => 'commission_debit',
                    'amount' => (float) $validated['package_amount'],
                    '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 back()->with('status', 'Member created successfully.');
    }

    private function generateUniqueCode(string $prefix, string $column): string
    {
        do {
            $candidate = $prefix.strtoupper(bin2hex(random_bytes(4)));
        } while (User::query()->where($column, $candidate)->exists());

        return $candidate;
    }

    private function resolvePlacementLeg(int $parentId): string
    {
        $left = (float) BinaryLedger::query()
            ->where('beneficiary_user_id', $parentId)
            ->where('leg', 'left')
            ->selectRaw("COALESCE(SUM(CASE WHEN direction = 'credit' THEN units ELSE -units END), 0) as total")
            ->value('total');

        $right = (float) BinaryLedger::query()
            ->where('beneficiary_user_id', $parentId)
            ->where('leg', 'right')
            ->selectRaw("COALESCE(SUM(CASE WHEN direction = 'credit' THEN units ELSE -units END), 0) as total")
            ->value('total');

        return $left <= $right ? 'left' : 'right';
    }
}
