<?php

namespace App\Http\Controllers\Components;

use App\Events\CheckoutableCheckedOut;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Component;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;

class ComponentCheckoutController extends Controller
{
    /**
     * Returns a view that allows the checkout of a component to an asset.
     *
     * @author [A. Gianotto] [<snipe@snipe.net>]
     *
     * @see ComponentCheckoutController::store() method that stores the data.
     * @since [v3.0]
     *
     * @param  int  $id
     * @return View
     *
     * @throws AuthorizationException
     */
    public function create($id)
    {

        if ($component = Component::find($id)) {

            $this->authorize('checkout', $component);

            // Make sure the category is valid
            if ($component->category) {

                // Make sure there is at least one available to checkout
                if ($component->numRemaining() <= 0) {
                    return redirect()->route('components.index')
                        ->with('error', trans('admin/components/message.checkout.unavailable'));
                }

                // Return the checkout view
                return view('components/checkout', compact('component'))
                    ->with('snipe_component', $component);
            }

            // Invalid category
            return redirect()->route('components.edit', ['component' => $component->id])
                ->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.component')]));
        }

        // Not found
        return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));

    }

    /**
     * Validate and store checkout data.
     *
     * @author [A. Gianotto] [<snipe@snipe.net>]
     *
     * @see ComponentCheckoutController::create() method that returns the form.
     * @since [v3.0]
     *
     * @param  int  $componentId
     * @return RedirectResponse
     *
     * @throws AuthorizationException
     */
    public function store(Request $request, $componentId)
    {
        // Check if the component exists
        if (! $component = Component::find($componentId)) {
            // Redirect to the component management page with error
            return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
        }

        $this->authorize('checkout', $component);

        $max_to_checkout = $component->numRemaining();

        // Make sure there are at least the requested number of components available to checkout
        if ($max_to_checkout < $request->input('assigned_qty')) {
            return redirect()->back()->withInput()->with('error', trans('admin/components/message.checkout.unavailable', ['remaining' => $max_to_checkout, 'requested' => $request->input('assigned_qty')]));
        }

        $validator = Validator::make($request->all(), [
            'asset_id' => 'required|exists:assets,id',
            'assigned_qty' => "required|numeric|min:1|digits_between:1,$max_to_checkout",
        ]);

        if ($validator->fails()) {
            return redirect()->back()
                ->withErrors($validator)
                ->withInput();
        }

        // Check if the asset exists
        $asset = Asset::find($request->input('asset_id'));

        if (! $component->canCheckoutTo($asset)) {
            return redirect()->route('components.checkout.show', $componentId)->with('error', trans('general.error_checkout_company_mismatch', [
                'item' => trans('general.component').' "'.$component->name.'"',
                'item_company' => $component->company?->name ?? trans('general.unassigned'),
                'target' => trans('general.asset').' "'.$asset->display_name.'"',
            ]));
        }

        $component->checkout_qty = $request->input('assigned_qty');

        // Concurrency guard. The numRemaining() check above is an unlocked
        // read, so two simultaneous checkout requests could both pass, both
        // attach a pivot row, and land the register at -1. Re-fetch the
        // parent under lockForUpdate INSIDE a transaction, re-check against
        // the locked snapshot, then write. Mirrors the License checkout
        // locking pattern.
        $overAllocated = false;

        DB::transaction(function () use ($component, $request, &$overAllocated): void {
            $locked = Component::whereKey($component->id)->lockForUpdate()->first();

            if (! $locked || $locked->numRemaining() < $component->checkout_qty) {
                $overAllocated = true;

                return;
            }

            $component->asset_id = $request->input('asset_id');
            $component->assets()->attach($component->id, [
                'component_id' => $component->id,
                'created_by' => auth()->user()->id,
                'created_at' => date('Y-m-d H:i:s'),
                'assigned_qty' => $component->checkout_qty,
                'asset_id' => $request->input('asset_id'),
                'note' => $request->input('note'),
            ]);
        });

        if ($overAllocated) {
            return redirect()->back()->withInput()->with('error', trans('admin/components/message.checkout.unavailable', [
                'remaining' => $component->fresh()->numRemaining(),
                'requested' => $component->checkout_qty,
            ]));
        }

        event(new CheckoutableCheckedOut(
            $component,
            $asset,
            auth()->user(),
            $request->input('note'),
            [],
            $component->checkout_qty,
        ));

        $request->request->add(['checkout_to_type' => 'asset']);
        $request->request->add(['assigned_asset' => $asset->id]);

        session()->put(['redirect_option' => $request->input('redirect_option'), 'checkout_to_type' => $request->input('checkout_to_type')]);

        return Helper::getRedirectOption($request, $component->id, 'Components')
            ->with('success', trans('admin/components/message.checkout.success'));
    }
}
