<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Franchise;
use App\Models\FranchiseCommissionRule;
use App\Models\FranchiseStockRequest;
use App\Models\User;
use App\Models\Warehouse;
use App\Models\WarehouseStock;
use App\Models\WarehouseStockLog;
use App\Services\FranchiseNotificationService;
use App\Services\MemberCodeService;
use App\Services\WarehouseDistributionService;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;

class FranchiseController extends Controller
{
    public function index(Request $request): View
    {
        $q = trim((string) $request->input('q', ''));
        $status = trim((string) $request->input('status', ''));
        $warehouseId = (int) $request->input('warehouse_id', 0);

        $franchisesQuery = Franchise::query()->with('commissionRules');
        if ($q !== '') {
            $franchisesQuery->where(function ($query) use ($q): void {
                $query->where('code', 'like', "%{$q}%")
                    ->orWhere('name', 'like', "%{$q}%")
                    ->orWhere('mobile', 'like', "%{$q}%")
                    ->orWhere('email', 'like', "%{$q}%")
                    ->orWhere('city', 'like', "%{$q}%");
            });
        }
        if ($status !== '') {
            $franchisesQuery->where('is_active', $status === 'active');
        }

        $franchises = $franchisesQuery->latest('id')->paginate(20)->withQueryString();

        $summary = [
            'total_franchises' => Franchise::query()->count(),
            'active_franchises' => Franchise::query()->where('is_active', true)->count(),
            'total_wallet_liability' => (float) Franchise::query()->sum('wallet_balance'),
            'pending_stock_requests' => FranchiseStockRequest::query()->whereIn('status', ['pending', 'approved', 'dispatched'])->count(),
        ];

        $stockRequests = FranchiseStockRequest::query()
            ->with(['franchise', 'items.product', 'requestedWarehouse'])
            ->whereIn('status', ['pending', 'approved', 'dispatched'])
            ->latest('id')
            ->paginate(12, ['*'], 'stock_requests')
            ->withQueryString();

        $warehouses = Warehouse::query()->where('is_active', true)->orderBy('name')->get();
        $warehouseStocks = collect();
        if ($warehouseId > 0) {
            $warehouseStocks = DB::table('warehouse_stocks as ws')
                ->join('products as p', 'p.id', '=', 'ws.product_id')
                ->where('ws.warehouse_id', $warehouseId)
                ->select(['p.id as product_id', 'p.name', 'p.sku', 'p.dp_price', 'p.bv', 'ws.quantity', 'ws.reserved_quantity'])
                ->orderBy('p.name')
                ->get();
        }

        $warehouseLogs = WarehouseStockLog::query()->latest('id')->limit(30)->get();
        $franchiseUsers = User::query()->where('role', 'franchise')->orderBy('name')->limit(200)->get();
        $commissionRules = FranchiseCommissionRule::query()->with('franchise')->latest('id')->limit(100)->get();

        return view('admin.franchises', compact(
            'q',
            'status',
            'summary',
            'franchises',
            'stockRequests',
            'warehouses',
            'warehouseId',
            'warehouseStocks',
            'warehouseLogs',
            'franchiseUsers',
            'commissionRules'
        ));
    }

    public function store(Request $request, MemberCodeService $memberCodeService): RedirectResponse
    {
        $validated = $request->validate([
            'code' => ['required', 'string', 'max:50', 'unique:franchises,code'],
            'name' => ['required', 'string', 'max:191'],
            'owner_name' => ['nullable', 'string', 'max:191'],
            'mobile' => ['nullable', 'string', 'max:20'],
            'email' => ['nullable', 'email', 'max:191'],
            'address' => ['nullable', 'string', 'max:500'],
            'city' => ['nullable', 'string', 'max:191'],
            'state' => ['nullable', 'string', 'max:191'],
            'pincode' => ['nullable', 'string', 'max:12'],
            'gst_number' => ['nullable', 'string', 'max:20'],
            'commission_percent' => ['nullable', 'numeric', 'min:0', 'max:100'],
            'owner_user_id' => ['nullable', 'exists:users,id'],
            'create_owner_login' => ['nullable', 'boolean'],
            'owner_login_name' => ['nullable', 'string', 'max:191'],
            'owner_login_email' => ['nullable', 'email', 'max:191'],
            'owner_login_mobile' => ['nullable', 'string', 'max:20'],
            'owner_login_password' => ['nullable', 'string', 'min:8'],
        ]);

        $ownerUserId = $this->resolveOwnerUserId($validated, $memberCodeService);

        Franchise::query()->create([
            'code' => strtoupper(trim($validated['code'])),
            'name' => $validated['name'],
            'owner_name' => $validated['owner_name'] ?? null,
            'mobile' => $validated['mobile'] ?? null,
            'email' => $validated['email'] ?? null,
            'address' => $validated['address'] ?? null,
            'city' => $validated['city'] ?? null,
            'state' => $validated['state'] ?? null,
            'pincode' => $validated['pincode'] ?? null,
            'gst_number' => $validated['gst_number'] ?? null,
            'commission_percent' => (float) ($validated['commission_percent'] ?? 0),
            'wallet_balance' => 0,
            'is_active' => true,
            'created_by' => $ownerUserId,
        ]);

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

    public function updateStatus(Request $request, Franchise $franchise): RedirectResponse
    {
        $validated = $request->validate([
            'status' => ['required', Rule::in(['active', 'inactive'])],
        ]);

        $franchise->is_active = $validated['status'] === 'active';
        $franchise->save();

        return back()->with('status', 'Franchise status updated.');
    }

    public function storeWarehouse(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'code' => ['required', 'string', 'max:50', 'unique:warehouses,code'],
            'name' => ['required', 'string', 'max:191'],
            'contact_number' => ['nullable', 'string', 'max:20'],
            'manager_name' => ['nullable', 'string', 'max:191'],
            'address' => ['nullable', 'string', 'max:500'],
            'city' => ['nullable', 'string', 'max:191'],
            'state' => ['nullable', 'string', 'max:191'],
            'pincode' => ['nullable', 'string', 'max:12'],
        ]);

        Warehouse::query()->create([
            'code' => strtoupper(trim($validated['code'])),
            'name' => $validated['name'],
            'contact_number' => $validated['contact_number'] ?? null,
            'manager_name' => $validated['manager_name'] ?? null,
            'address' => $validated['address'] ?? null,
            'city' => $validated['city'] ?? null,
            'state' => $validated['state'] ?? null,
            'pincode' => $validated['pincode'] ?? null,
            'is_active' => true,
        ]);

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

    public function stockWarehouse(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'warehouse_id' => ['required', 'exists:warehouses,id'],
            'product_id' => ['required', 'exists:products,id'],
            'quantity' => ['required', 'numeric', 'min:0.01'],
            'transaction_type' => ['required', Rule::in(['stock_in', 'stock_out'])],
            'reference' => ['nullable', 'string', 'max:120'],
        ]);

        try {
            DB::transaction(function () use ($validated): void {
                $stock = WarehouseStock::query()
                    ->firstOrCreate(
                        [
                            'warehouse_id' => (int) $validated['warehouse_id'],
                            'product_id' => (int) $validated['product_id'],
                        ],
                        ['quantity' => 0, 'reserved_quantity' => 0]
                    );

                $stock = WarehouseStock::query()->whereKey($stock->id)->lockForUpdate()->first();
                $opening = (float) $stock->quantity;
                $qty = (float) $validated['quantity'];

                $closing = $validated['transaction_type'] === 'stock_in'
                    ? $opening + $qty
                    : $opening - $qty;

                if ($closing < 0) {
                    throw new \RuntimeException('Warehouse stock cannot go negative.');
                }

                $stock->quantity = round($closing, 4);
                $stock->save();

                WarehouseStockLog::query()->create([
                    'warehouse_id' => (int) $validated['warehouse_id'],
                    'franchise_id' => null,
                    'product_id' => (int) $validated['product_id'],
                    'transaction_type' => $validated['transaction_type'],
                    'quantity' => $qty,
                    'opening_qty' => $opening,
                    'closing_qty' => round($closing, 4),
                    'reference' => $validated['reference'] ?? null,
                    'meta' => null,
                    'created_by' => auth()->id(),
                    'transacted_at' => now(),
                ]);
            });
        } catch (\RuntimeException $exception) {
            return back()->withErrors(['warehouse_stock' => $exception->getMessage()]);
        }

        return back()->with('status', 'Warehouse stock entry saved.');
    }

    public function approveStockRequest(Request $request, $requestId,
        FranchiseNotificationService $notificationService
    ): RedirectResponse {
        $stockRequest = FranchiseStockRequest::query()->with('franchise')->whereKey($requestId)->first();
        if (! $stockRequest) {
            return back()->withErrors(['stock_request' => 'Stock request not found.']);
        }

        if ($stockRequest->status === 'delivered') {
            return back()->withErrors(['stock_request' => 'Stock request is already delivered.']);
        }

        $validated = $request->validate([
            'warehouse_id' => ['nullable', 'exists:warehouses,id'],
        ]);

        $stockRequest->status = 'approved';
        $stockRequest->approved_at = now();
        if (! empty($validated['warehouse_id'])) {
            $stockRequest->requested_warehouse_id = (int) $validated['warehouse_id'];
        }
        $stockRequest->save();

        if ($stockRequest->franchise) {
            $notificationService->notify(
                $stockRequest->franchise,
                'Stock Request Approved',
                "Request {$stockRequest->request_number} approved by admin.",
                route('franchise.stock-requests')
            );
        }

        return back()->with('status', 'Stock request approved.');
    }

    public function dispatchStockRequest(Request $request, $requestId,
        WarehouseDistributionService $distributionService,
        FranchiseNotificationService $notificationService
    ): RedirectResponse {
        $validated = $request->validate([
            'warehouse_id' => ['required', 'exists:warehouses,id'],
        ]);

        $stockRequest = FranchiseStockRequest::query()
            ->with(['franchise', 'items.product'])
            ->whereKey($requestId)
            ->first();

        if (! $stockRequest) {
            return back()->withErrors(['stock_request' => 'Stock request not found.']);
        }
        if ($stockRequest->status === 'delivered') {
            return back()->withErrors(['stock_request' => 'Stock request already delivered.']);
        }

        try {
            DB::transaction(function () use ($stockRequest, $validated, $distributionService): void {
                foreach ($stockRequest->items as $item) {
                    $remaining = round((float) $item->quantity_requested - (float) $item->quantity_delivered, 4);
                    if ($remaining <= 0) {
                        continue;
                    }

                    $distributionService->dispatchToFranchise(
                        (int) $validated['warehouse_id'],
                        (int) $stockRequest->franchise_id,
                        (int) $item->product_id,
                        $remaining,
                        (float) ($item->product?->dp_price ?? 0),
                        (float) ($item->product?->bv ?? 0),
                        [
                            'reference' => $stockRequest->request_number,
                            'created_by' => auth()->id(),
                            'narration' => 'Admin dispatch against stock request',
                        ]
                    );

                    $item->quantity_delivered = round((float) $item->quantity_delivered + $remaining, 4);
                    $item->save();
                }

                $stockRequest->requested_warehouse_id = (int) $validated['warehouse_id'];
                $stockRequest->status = 'delivered';
                $stockRequest->approved_at = $stockRequest->approved_at ?? now();
                $stockRequest->dispatched_at = now();
                $stockRequest->delivered_at = now();
                $stockRequest->save();
            });
        } catch (\RuntimeException $exception) {
            return back()->withErrors(['stock_request' => $exception->getMessage()]);
        }

        if ($stockRequest->franchise) {
            $notificationService->notify(
                $stockRequest->franchise,
                'Stock Request Delivered',
                "Request {$stockRequest->request_number} dispatched and delivered by admin.",
                route('franchise.stock')
            );
        }

        return back()->with('status', 'Stock request dispatched and delivered.');
    }

    public function storeCommissionRule(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'franchise_id' => ['nullable', 'exists:franchises,id'],
            'rule_type' => ['required', Rule::in(['sale_margin', 'id_activation', 'service_charge', 'pickup_fee'])],
            'calc_type' => ['required', Rule::in(['percent', 'fixed'])],
            'value' => ['required', 'numeric', 'min:0'],
            'min_base_amount' => ['nullable', 'numeric', 'min:0'],
            'max_base_amount' => ['nullable', 'numeric', 'min:0'],
            'effective_from' => ['nullable', 'date'],
            'effective_to' => ['nullable', 'date'],
        ]);

        if (
            isset($validated['min_base_amount'], $validated['max_base_amount']) &&
            (float) $validated['min_base_amount'] > (float) $validated['max_base_amount']
        ) {
            return back()->withErrors(['max_base_amount' => 'Max amount must be greater than or equal to min amount.']);
        }

        FranchiseCommissionRule::query()->create([
            'franchise_id' => $validated['franchise_id'] ?? null,
            'rule_type' => $validated['rule_type'],
            'calc_type' => $validated['calc_type'],
            'value' => (float) $validated['value'],
            'min_base_amount' => $validated['min_base_amount'] ?? null,
            'max_base_amount' => $validated['max_base_amount'] ?? null,
            'effective_from' => $validated['effective_from'] ?? null,
            'effective_to' => $validated['effective_to'] ?? null,
            'is_active' => true,
        ]);

        return back()->with('status', 'Commission rule added.');
    }

    public function toggleCommissionRule(Request $request, FranchiseCommissionRule $rule): RedirectResponse
    {
        $validated = $request->validate([
            'state' => ['required', Rule::in(['active', 'inactive'])],
        ]);

        $rule->is_active = $validated['state'] === 'active';
        $rule->save();

        return back()->with('status', 'Commission rule status updated.');
    }

    /**
     * @param array<string, mixed> $validated
     */
    private function resolveOwnerUserId(array $validated, MemberCodeService $memberCodeService): ?int
    {
        if (! empty($validated['owner_user_id'])) {
            return (int) $validated['owner_user_id'];
        }

        $createOwnerLogin = (bool) ($validated['create_owner_login'] ?? false);
        if (! $createOwnerLogin) {
            return null;
        }

        $name = trim((string) ($validated['owner_login_name'] ?? $validated['owner_name'] ?? ''));
        $mobile = trim((string) ($validated['owner_login_mobile'] ?? $validated['mobile'] ?? ''));
        $email = trim((string) ($validated['owner_login_email'] ?? $validated['email'] ?? ''));
        $password = (string) ($validated['owner_login_password'] ?? '');

        if ($name === '' || $mobile === '' || $password === '') {
            throw ValidationException::withMessages([
                'owner_login' => 'Owner login requires name, mobile, and password.',
            ]);
        }

        $existing = User::query()
            ->where('mobile', $mobile)
            ->when($email !== '', fn ($query) => $query->orWhere('email', $email))
            ->first();

        if ($existing) {
            if ($existing->role !== 'franchise') {
                throw ValidationException::withMessages([
                    'owner_login' => 'Existing user is not a franchise role.',
                ]);
            }

            $existing->name = $name;
            if ($email !== '') {
                $existing->email = $email;
            }
            $existing->password = Hash::make($password);
            $existing->status = 'active';
            $existing->save();

            return $existing->id;
        }

        $owner = User::query()->create([
            'member_code' => $memberCodeService->generateMemberCode(),
            'referral_code' => $memberCodeService->generateReferralCode(),
            'name' => $name,
            'email' => $email === '' ? null : $email,
            'mobile' => $mobile,
            'password' => Hash::make($password),
            'role' => 'franchise',
            'status' => 'active',
        ]);

        return $owner->id;
    }
}

