<?php

namespace Modules\Bms\Http\Controllers;

use Illuminate\Http\Request;
use Modules\Bms\Entities\BmsAddon;
use Modules\Bms\Entities\BmsBooking;
use Modules\Bms\Entities\BmsHall;
use Modules\Bms\Entities\BmsHallAvailability;
use Modules\Bms\Entities\BmsLead;
use Modules\Bms\Entities\BmsLeadHall;
use Modules\Bms\Entities\BmsPackage;
use Modules\Bms\Services\Booking\BookingService;
use Modules\Bms\Services\Booking\CancellationService;
use Modules\Bms\Services\Booking\HoldConflictException;
use Modules\Bms\Services\Contracts\ContactServiceContract;
use Modules\Bms\Services\Contracts\InvoiceServiceContract;
use Modules\Bms\Services\Contracts\PaymentServiceContract;
use Modules\Bms\Services\Documents\DocumentService;
use Modules\Bms\Services\Finance\PaymentPlanService;
use Yajra\DataTables\Facades\DataTables;

/**
 * P1 booking ops console. Every action delegates to the SAME shared services
 * the employee PWA uses (BookingService / CancellationService / Erp payment +
 * invoice adapters / DocumentService / PaymentPlanService), so P1 and the PWAs
 * behave identically — P1 is the master back-office (full parity).
 */
class BookingController extends BmsBaseController
{
    public function index()
    {
        $this->guard('bms.bookings.view');

        if (request()->ajax()) {
            $rows = BmsBooking::where('bms_bookings.business_id', $this->businessId())
                ->leftJoin('contacts', 'contacts.id', '=', 'bms_bookings.contact_id')
                ->select([
                    'bms_bookings.id', 'bms_bookings.booking_no', 'bms_bookings.status',
                    'bms_bookings.event_type', 'bms_bookings.event_date', 'bms_bookings.attendees',
                    'bms_bookings.total_amount', 'bms_bookings.deposit_amount', 'contacts.name as contact_name',
                ]);

            return DataTables::of($rows)
                ->editColumn('contact_name', fn ($b) => $b->contact_name ?: '—')
                ->editColumn('event_date', fn ($b) => $b->event_date ? \Carbon\Carbon::parse($b->event_date)->format('Y-m-d') : '—')
                ->editColumn('status', fn ($b) => '<span class="label '.$this->statusClass($b->status).'">'.$this->statusLabel($b->status).'</span>')
                ->editColumn('total_amount', fn ($b) => '<span class="display_currency" data-currency_symbol="true">'.$b->total_amount.'</span>')
                ->addColumn('action', fn ($b) => '<a href="'.route('bms.bookings.show', $b->id).'" class="tw-dw-btn tw-dw-btn-xs tw-dw-btn-outline tw-dw-btn-primary"><i class="fa fas fa-eye"></i> '.__('messages.view').'</a>')
                ->rawColumns(['status', 'total_amount', 'action'])
                ->make(true);
        }

        return view('bms::bookings.index');
    }

    /** New-booking builder (staff funnel): contact → event → hall/package/add-ons. */
    public function create(Request $request)
    {
        $this->guard('bms.bookings.manage');

        $halls = BmsHall::where('business_id', $this->businessId())->where('is_active', true)
            ->orderBy('name')->get(['id', 'name', 'name_ar']);
        $packages = BmsPackage::where('business_id', $this->businessId())->where('is_active', true)
            ->with('halls:id')->get(['id', 'name', 'price'])
            ->map(fn ($p) => ['id' => $p->id, 'name' => $p->name, 'price' => (float) $p->price, 'hall_ids' => $p->halls->pluck('id')->all()]);
        $addons = BmsAddon::where('business_id', $this->businessId())->where('is_active', true)
            ->get(['id', 'name', 'price']);

        return view('bms::bookings.create', [
            'halls'      => $halls,
            'packages'   => $packages,
            'addons'     => $addons,
            'eventTypes' => $this->eventTypes(),
            // Prefill from the Match & Build funnel (query params).
            'prefill'    => [
                'hall_id'    => (int) $request->query('hall_id') ?: null,
                'attendees'  => $request->query('attendees'),
                'event_date' => $request->query('event_date'),
            ],
        ]);
    }

    /**
     * Package items for the staff funnel (AJAX): the same product-inherited shape
     * the client funnel uses (name/contents/options), so staff can customize
     * inclusions, variant swaps and buffet substitutions with price deltas.
     */
    public function packageItems($id)
    {
        $this->guard('bms.bookings.manage');
        $package = BmsPackage::where('business_id', $this->businessId())
            ->with(['items' => fn ($q) => $q->orderBy('sort_order')])->findOrFail($id);

        return response()->json([
            'items' => app(\Modules\Bms\Services\Catalog\PackagePresenter::class)->present($package->items),
        ]);
    }

    /** Create the booking: build contact + lead + selected hall, then the booking. */
    public function store(Request $request, ContactServiceContract $contacts, BookingService $bookings)
    {
        $this->guard('bms.bookings.manage');
        $data = $this->validate($request, [
            'contact_id'  => 'nullable|integer',
            'new_name'    => 'required_without:contact_id|nullable|string|max:191',
            'new_phone'   => 'required_without:contact_id|nullable|string|max:30',
            'new_email'   => 'nullable|email|max:191',
            'event_type'  => 'nullable|string|max:60',
            'attendees'   => 'nullable|integer|min:0',
            'event_date'  => 'required|date',
            'hall_id'        => 'required|integer',
            'package_id'     => 'nullable|integer',
            'addons'         => 'nullable|array',
            'addons.*'       => 'integer',
            'package_items'  => 'nullable|string', // JSON from the customization editor
        ]);

        try {
            $contactId = $data['contact_id'] ?? null;
            if (! $contactId) {
                $contact = $contacts->findOrCreateCustomer($this->businessId(), [
                    'name' => $data['new_name'], 'phone' => $data['new_phone'],
                    'email' => $data['new_email'] ?? null, 'source' => 'p1_booking',
                ]);
                $contactId = $contact->id;
            }

            $lead = BmsLead::create([
                'business_id'      => $this->businessId(),
                'contact_id'       => $contactId,
                'whatsapp'         => $data['new_phone'] ?? null,
                'stage'            => 'won',
                'source'           => 'p1',
                'event_type'       => $data['event_type'] ?? null,
                'attendees'        => $data['attendees'] ?? null,
                'last_activity_at' => now(),
            ]);
            // Per-item customization (kept items, variant swaps, buffet
            // substitutions) — decoded from the editor's hidden JSON field.
            $packageItems = json_decode($data['package_items'] ?? '[]', true);
            $packageItems = is_array($packageItems) ? $packageItems : [];

            BmsLeadHall::create([
                'business_id'   => $this->businessId(),
                'lead_id'       => $lead->id,
                'hall_id'       => $data['hall_id'],
                'package_id'    => $data['package_id'] ?? null,
                'addons'        => collect($data['addons'] ?? [])->map(fn ($id) => ['addon_id' => (int) $id, 'qty' => 1])->all(),
                'package_items' => $packageItems ?: null,
            ]);

            $booking = $bookings->createFromLead($lead->fresh(), $data['event_date'], $this->userId());

            return redirect()->route('bms.bookings.show', $booking->id)
                ->with('status', __('bms::lang.added_success'));
        } catch (HoldConflictException $e) {
            return back()->withInput()->with('error', $e->getMessage());
        } catch (\Throwable $e) {
            \Log::emergency($e->getMessage());
            return back()->withInput()->with('error', __('messages.something_went_wrong'));
        }
    }

    public function show($id)
    {
        $this->guard('bms.bookings.view');
        $booking = BmsBooking::with(['contact:id,name,mobile,email', 'halls'])
            ->where('business_id', $this->businessId())->findOrFail($id);

        $invoice = app(InvoiceServiceContract::class)->statusForBooking($booking);
        $plan = app(PaymentPlanService::class);

        return view('bms::bookings.show', [
            'booking'   => $booking,
            'invoice'   => $invoice,
            'schedule'  => $plan->schedule($booking),
            'payments'  => app(PaymentServiceContract::class)->scheduleForBooking($this->businessId(), $booking->id)['payments'] ?? [],
            'tasks'     => \Modules\Bms\Entities\BmsTask::where('booking_id', $booking->id)->orderBy('due_at')->get(),
            'documents' => \Modules\Bms\Entities\BmsDocument::where('booking_id', $booking->id)->latest()->get(),
            'hallNames' => BmsHall::whereIn('id', $booking->halls->pluck('hall_id'))->pluck('name', 'id'),
            'customizations' => $this->bookingCustomizations($booking),
            'reasons'   => app(CancellationService::class)->reasons($this->businessId()),
            'canManage' => auth()->user()->can('bms.bookings.manage'),
            'canCancel' => auth()->user()->can('bms.bookings.cancel'),
        ]);
    }

    /** Light field edit (notes/attendees/times) — lifecycle goes through actions. */
    public function update(Request $request, $id)
    {
        $this->guard('bms.bookings.manage');
        $data = $this->validate($request, [
            'event_type' => 'nullable|string|max:191',
            'start_time' => 'nullable', 'end_time' => 'nullable',
            'attendees'  => 'nullable|integer|min:0',
            'notes'      => 'nullable|string',
        ]);
        BmsBooking::where('business_id', $this->businessId())->findOrFail($id)->fill($data)->save();

        return redirect()->route('bms.bookings.show', $id)->with('status', __('bms::lang.updated_success'));
    }

    // ---- Lifecycle actions (delegate to shared services) -----------------

    public function confirm($id, BookingService $bookings)
    {
        $this->guard('bms.bookings.manage');
        $booking = BmsBooking::where('business_id', $this->businessId())->findOrFail($id);
        $bookings->confirm($booking, $this->userId());

        return redirect()->route('bms.bookings.show', $id)->with('status', __('bms::lang.success'));
    }

    public function contractBooking($id, BookingService $bookings)
    {
        $this->guard('bms.bookings.manage');
        $booking = BmsBooking::where('business_id', $this->businessId())->findOrFail($id);
        try {
            $bookings->markContracted($booking, $this->userId());
        } catch (\RuntimeException $e) {
            return back()->with('error', $e->getMessage());
        }
        return redirect()->route('bms.bookings.show', $id)->with('status', __('bms::lang.success'));
    }

    public function complete($id, BookingService $bookings)
    {
        $this->guard('bms.bookings.manage');
        $booking = BmsBooking::where('business_id', $this->businessId())->findOrFail($id);
        $bookings->markCompleted($booking, $this->userId());

        return redirect()->route('bms.bookings.show', $id)->with('status', __('bms::lang.success'));
    }

    public function recordPayment(Request $request, $id, PaymentServiceContract $payments)
    {
        $this->guard('bms.bookings.manage');
        $data = $this->validate($request, [
            'amount' => 'required|numeric|min:0.01',
            'method' => 'nullable|string|max:30',
            'note'   => 'nullable|string|max:255',
        ]);
        try {
            $payments->recordPayment($this->businessId(), (int) $id, array_merge($data, ['user_id' => $this->userId()]));
            $booking = BmsBooking::find($id);
            app(BookingService::class)->notifyContact($booking, 'payment_received', 'Payment received',
                'We received your payment of ' . number_format((float) $data['amount']) . ' for booking ' . $booking->booking_no . '.');
        } catch (\Throwable $e) {
            return back()->with('error', $e->getMessage());
        }
        return redirect()->route('bms.bookings.show', $id)->with('status', __('bms::lang.success'));
    }

    public function generateDocument($id, $type, DocumentService $docs)
    {
        $this->guard('bms.bookings.manage');
        $booking = BmsBooking::where('business_id', $this->businessId())->with('halls')->findOrFail($id);
        $docs->forBooking($booking, $type, $this->userId());

        return redirect()->route('bms.bookings.show', $id)->with('status', __('bms::lang.added_success'));
    }

    /** Inline view/print of a booking document in EN or AR (no version churn). */
    public function viewDocument($docId, $lang, DocumentService $docs)
    {
        $this->guard('bms.bookings.view');
        $doc = \Modules\Bms\Entities\BmsDocument::where('business_id', $this->businessId())->findOrFail($docId);
        $booking = BmsBooking::with('halls')->findOrFail($doc->booking_id);
        $file = $docs->viewBooking($booking, $doc->type, $lang === 'ar' ? 'ar' : 'en');

        return response($file['contents'], 200, [
            'Content-Type' => 'application/pdf',
            'Content-Disposition' => 'inline; filename="' . $file['filename'] . '"',
        ]);
    }

    public function cancelBooking(Request $request, $id, CancellationService $cancellations)
    {
        $this->guard('bms.bookings.cancel');
        $booking = BmsBooking::where('business_id', $this->businessId())->findOrFail($id);
        $request->validate(['reason' => 'nullable|string|max:255', 'reason_code' => 'nullable|string|max:60', 'override_penalty_percent' => 'nullable|numeric|min:0|max:100']);

        $override = null;
        if ($request->filled('override_penalty_percent') && auth()->user()->can('bms.bookings.override_penalty')) {
            $override = (float) $request->input('override_penalty_percent');
        }
        try {
            $cancellations->cancel($booking, [
                'reason' => $request->input('reason'), 'reason_code' => $request->input('reason_code'),
                'override_penalty_percent' => $override, 'user_id' => $this->userId(),
            ]);
        } catch (\Throwable $e) {
            return back()->with('error', $e->getMessage());
        }
        return redirect()->route('bms.bookings.show', $id)->with('status', __('bms::lang.success'));
    }

    public function reschedule(Request $request, $id, CancellationService $cancellations)
    {
        $this->guard('bms.bookings.cancel');
        $booking = BmsBooking::where('business_id', $this->businessId())->findOrFail($id);
        $request->validate(['event_date' => 'required|date', 'override_penalty_percent' => 'nullable|numeric|min:0|max:100']);

        $override = null;
        if ($request->filled('override_penalty_percent') && auth()->user()->can('bms.bookings.override_penalty')) {
            $override = (float) $request->input('override_penalty_percent');
        }
        try {
            $cancellations->reschedule($booking, $request->input('event_date'), ['override_penalty_percent' => $override, 'user_id' => $this->userId()]);
        } catch (HoldConflictException $e) {
            return back()->with('error', $e->getMessage());
        } catch (\Throwable $e) {
            return back()->with('error', $e->getMessage());
        }
        return redirect()->route('bms.bookings.show', $id)->with('status', __('bms::lang.success'));
    }

    // ---- helpers ----------------------------------------------------------

    /**
     * Read-only customization summary per booking hall (kept add-ons, variant
     * swaps, buffet substitutions) from the persisted package_items — so the
     * ops console reflects exactly what the client chose, not just the price.
     */
    protected function bookingCustomizations(BmsBooking $booking): array
    {
        $presenter = app(\Modules\Bms\Services\Catalog\PackagePresenter::class);
        $out = [];
        foreach ($booking->halls as $bh) {
            $items = (array) ($bh->package_items ?? []);
            if (! $items) {
                continue;
            }
            $swaps = [];
            $extras = [];
            foreach ($items as $pi) {
                if (empty($pi['included'])) {
                    continue;
                }
                $item = \Modules\Bms\Entities\BmsPackageItem::find($pi['item_id'] ?? null);
                if (! $item) {
                    continue;
                }
                if (! empty($pi['variant_index']) && is_array($item->options) && isset($item->options[$pi['variant_index'] - 1])) {
                    $o = $item->options[$pi['variant_index'] - 1];
                    $swaps[] = ['item' => $item->name, 'label' => $o['label'] ?? '', 'delta' => (float) ($o['price_delta'] ?? 0)];
                }
                foreach ((array) ($pi['content_swaps'] ?? []) as $cs) {
                    $line = $presenter->swapLine($item, $cs);
                    $swaps[] = ['item' => $item->name, 'label' => $line['label'], 'delta' => $line['delta']];
                }
                if ($item->is_addon) {
                    $extras[] = ['name' => $item->name, 'qty' => (int) ($pi['qty'] ?? 1), 'price' => (float) ($item->addon_price ?? 0)];
                }
            }
            if ($swaps || $extras) {
                $out[] = ['hall' => optional($bh->hall)->name ?: ('#' . $bh->hall_id), 'swaps' => $swaps, 'extras' => $extras];
            }
        }
        return $out;
    }

    protected function eventTypes(): array
    {
        return ['wedding' => __('bms::lang.et_wedding'), 'engagement' => __('bms::lang.et_engagement'),
            'corporate' => __('bms::lang.et_corporate'), 'birthday' => __('bms::lang.et_birthday'), 'other' => __('bms::lang.et_other')];
    }

    protected function statusLabel(string $s): string
    {
        $key = 'bms::lang.st_'.$s;
        $label = __($key);
        return $label === $key ? ucfirst($s) : $label;
    }

    protected function statusClass(string $s): string
    {
        return [
            'tentative' => 'label-warning', 'confirmed' => 'label-primary', 'contracted' => 'label-info',
            'paid' => 'label-success', 'completed' => 'label-success', 'cancelled' => 'label-default',
        ][$s] ?? 'label-default';
    }
}
