<?php

namespace App\Http\Controllers\Admin;

use App\Actions\Hardware\PrepareZReportPayload;
use App\Actions\Shifts\CloseShift;
use App\Actions\Shifts\ComputeShiftTotals;
use App\Actions\Shifts\OpenShift;
use App\Actions\Shifts\RecordCashDrawerEntry;
use App\Exceptions\ShiftAlreadyOpen;
use App\Exceptions\ShiftNotOpen;
use App\Exceptions\VarianceReasonRequired;
use App\Http\Controllers\Concerns\RespondsJsonOrRedirect;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\CloseShiftRequest;
use App\Http\Requests\Admin\OpenShiftRequest;
use App\Http\Requests\Admin\RecordCashDrawerEntryRequest;
use App\Models\CashDrawerEntry;
use App\Models\Shift;
use App\Models\ShiftVarianceReason;
use App\Models\Store;
use InvalidArgumentException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;

/**
 * Admin surface for shifts — index / open / close / show.
 *
 * Slice 1 scopes shift uniqueness to (store, user). The session-active
 * store (set by the topbar switcher) is the store every action runs
 * against.
 */
class ShiftController extends Controller
{
    use RespondsJsonOrRedirect;

    public function __construct(
        private readonly ComputeShiftTotals $compute,
    ) {}

    /* ── Index ──────────────────────────────────────────────────── */

    public function index(Request $request): View
    {
        $this->authorize('viewAny', Shift::class);

        $user      = $request->user();
        $storeId   = current_store_id() ?: default_store_id();
        $canViewAll = $user?->hasPermission('shifts.view_all') ?? false;

        $shifts = Shift::query()
            ->with(['store:id,name', 'cashier:id,name'])
            ->when($storeId, fn ($q) => $q->where('store_id', $storeId))
            ->when(! $canViewAll, fn ($q) => $q->where('user_id', $user->id))
            ->orderByDesc('opened_at')
            ->limit(200)
            ->get();

        $activeShift = $user
            ? Shift::openForCashier($storeId, (int) $user->id)
            : null;

        // Summary cards — initial values from the loaded set. Shifts filter
        // CLIENT-side (shiftsIndexPage / dataTable), so `_dtOnRender`
        // recomputes these from the visible rows on every search. `key`
        // ties each value to its DOM node so the JS can update it.
        $summaryCards = [
            ['label' => __('shifts.summary.total'),    'value' => number_format($shifts->count()),                                         'key' => 'total'],
            ['label' => __('shifts.summary.open'),     'value' => number_format($shifts->where('status', 'open')->count()),                 'key' => 'open',     'tone' => 'accent'],
            ['label' => __('shifts.summary.variance'), 'value' => number_format($shifts->where('status', 'closed_with_variance')->count()), 'key' => 'variance', 'tone' => 'warning'],
        ];

        return view('admin.shifts.index', [
            'shifts'       => $shifts,
            'summaryCards' => $summaryCards,
            'activeShift'  => $activeShift,
        ]);
    }

    /* ── Open ───────────────────────────────────────────────────── */

    public function openForm(Request $request): View|RedirectResponse
    {
        $this->authorize('create', Shift::class);

        $user    = $request->user();
        $storeId = current_store_id() ?: default_store_id();
        if (! $storeId) {
            return redirect()
                ->route('admin.shifts.index')
                ->with('error', __('shifts.errors.no_store_selected'));
        }

        $existing = Shift::openForCashier($storeId, (int) $user->id);
        if ($existing) {
            return redirect()->route('admin.shifts.show', $existing);
        }

        $store = Store::query()->findOrFail($storeId);

        return view('admin.shifts.open', [
            'store'         => $store,
            'denominations' => \App\Support\Denominations::forActiveCurrency(),
        ]);
    }

    public function store(OpenShiftRequest $request, OpenShift $open): JsonResponse|RedirectResponse
    {
        $user    = $request->user();
        $storeId = current_store_id() ?: default_store_id();
        $store   = Store::query()->findOrFail($storeId);

        // Bind the shift to the cookie-selected terminal (Slice B), if any.
        $data = $request->validated() + ['terminal_id' => current_terminal_id()];

        try {
            $shift = $open($data, $user, $store);
        } catch (ShiftAlreadyOpen $e) {
            return $this->jsonOrError(
                $request,
                $e->getMessage(),
                route('admin.shifts.show', $e->existing),
            );
        } catch (\App\Exceptions\TerminalShiftOpen $e) {
            return $this->jsonOrError($request, $e->getMessage());
        }

        return $this->jsonOrRedirect(
            $request,
            __('shifts.flash.opened', ['number' => '#'.$shift->id]),
            route('admin.shifts.show', $shift),
        );
    }

    /* ── Close ──────────────────────────────────────────────────── */

    public function closeForm(Request $request, Shift $shift): View|RedirectResponse
    {
        $this->authorize('close', $shift);

        if (! $shift->isOpen()) {
            return redirect()
                ->route('admin.shifts.show', $shift)
                ->with('error', __('shifts.errors.not_open'));
        }

        $totals = ($this->compute)($shift);

        return view('admin.shifts.close', [
            'shift'           => $shift,
            'totals'          => $totals,
            'varianceReasons' => ShiftVarianceReason::query()->active()->ordered()->get(['id', 'code', 'name']),
            'denominations'   => \App\Support\Denominations::forActiveCurrency(),
        ]);
    }

    public function close(CloseShiftRequest $request, Shift $shift, CloseShift $close): JsonResponse|RedirectResponse
    {
        try {
            $shift = $close($shift, $request->validated(), $request->user());
        } catch (VarianceReasonRequired $e) {
            return $this->jsonOrError($request, $e->getMessage());
        }

        return $this->jsonOrRedirect(
            $request,
            __('shifts.flash.closed', ['number' => '#'.$shift->id]),
            route('admin.shifts.show', $shift),
        );
    }

    /* ── X-report (live, printable) ─────────────────────────────── */

    public function xReport(Shift $shift): View
    {
        $this->authorize('view', $shift);

        $shift->load(['store:id,name,currency_code', 'cashier:id,name']);

        return view('admin.shifts.x-report', [
            'shift'  => $shift,
            'totals' => ($this->compute)($shift),
        ]);
    }

    /**
     * Dual-format Z-report print payload (HTML + ESC/POS) for the print
     * bridge — same contract as the sale receipt payload. The cashier /
     * admin "Print Z-report" button fetches this and hands it to the
     * bridge (WebUSB → browser-print).
     */
    public function zReportPayload(Shift $shift, PrepareZReportPayload $prepare): JsonResponse
    {
        $this->authorize('view', $shift);

        return response()->json(($prepare)($shift, current_terminal()));
    }

    /* ── Show ───────────────────────────────────────────────────── */

    public function show(Shift $shift): View
    {
        $this->authorize('view', $shift);

        $shift->load(['store:id,name,currency_code', 'cashier:id,name', 'forceCloser:id,name']);

        $totals = ($this->compute)($shift);

        $entries = $shift->cashDrawerEntries()
            ->with('createdBy:id,name')
            ->orderByDesc('created_at')
            ->limit(100)
            ->get();

        return view('admin.shifts.show', [
            'shift'   => $shift,
            'totals'  => $totals,
            'entries' => $entries,
        ]);
    }

    /* ── Cash drawer entries ────────────────────────────────────── */

    public function recordCashDrawerEntry(
        RecordCashDrawerEntryRequest $request,
        Shift $shift,
        RecordCashDrawerEntry $record,
    ): JsonResponse|RedirectResponse {
        try {
            $record($shift, $request->validated(), $request->user());
        } catch (ShiftNotOpen $e) {
            return $this->jsonOrError($request, $e->getMessage());
        } catch (InvalidArgumentException $e) {
            return $this->jsonOrError($request, $e->getMessage());
        }

        $msg = match ((string) $request->input('type')) {
            CashDrawerEntry::TYPE_PAY_IN              => __('cash_drawer.flash.pay_in_recorded'),
            CashDrawerEntry::TYPE_PAY_OUT             => __('cash_drawer.flash.pay_out_recorded'),
            CashDrawerEntry::TYPE_DRAWER_OPEN_NO_SALE => __('cash_drawer.flash.drawer_opened_no_sale'),
            default                                   => __('cash_drawer.flash.recorded'),
        };

        return $this->jsonOrRedirect($request, $msg, route('admin.shifts.show', $shift));
    }
}
