<?php

namespace App\Http\Controllers\Care;

use App\Http\Controllers\Controller;
use App\Http\Controllers\Care\Concerns\ScopesToAccount;
use App\Models\Bill;
use App\Models\Consultation;
use App\Models\Visit;
use App\Services\Care\AdminOverviewService;
use App\Services\Care\BillService;
use App\Services\Care\BranchContext;
use App\Services\Care\CarePermissions;
use App\Services\Care\CareQueueBridge;
use App\Services\Care\CareQueueContexts;
use App\Services\Care\ConsultationReturnContext;
use App\Services\Care\OrganizationResolver;
use App\Services\Payments\MerchantGatewayService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
use RuntimeException;

class BillController extends Controller
{
    use ScopesToAccount;

    public function __construct(
        protected BillService $bills,
        protected MerchantGatewayService $gateway,
        protected CareQueueBridge $queueBridge,
        protected BranchContext $branchContext,
        protected AdminOverviewService $adminOverview,
    ) {}

    public function index(Request $request): View
    {
        $this->authorizeAbility($request, 'bills.view');
        $organization = $this->organization($request);
        $member = $this->member($request);
        $permissions = app(CarePermissions::class);
        $branches = $this->branchContext->branches($request, $organization, $member);
        $branchId = $this->branchContext->resolve($request, $organization, $member);

        if ($permissions->isAdmin($member) && $request->query('view') !== 'list') {
            return view('care.admin.analytics', [
                'title' => 'Billing',
                'badge' => 'Analytics · Revenue',
                'description' => 'Collections, outstanding balances, and invoice throughput — not a cashier queue.',
                'adminOverview' => $this->adminOverview->billing(
                    $organization,
                    $this->ownerRef($request),
                    $branchId > 0 ? $branchId : null,
                ),
            ]);
        }

        $filters = $request->only(['status', 'patient_id']);
        // Cashiers land on collectible balances; walk-up unpaid list is the primary path
        // (booth Call next is secondary when financial gates are off).
        if (! $request->has('status') && $member?->role === 'cashier') {
            $filters['status'] = 'outstanding';
        }

        $bills = $this->bills->list(
            $this->ownerRef($request),
            $organization->id,
            $filters,
            $branchId > 0 ? $branchId : null,
        );

        return view('care.bills.index', [
            'organization' => $organization,
            'bills' => $bills,
            'statuses' => config('care.bill_statuses'),
            'statusFilter' => $filters['status'] ?? '',
            'branches' => $branches,
            'branchId' => $branchId,
            'canSwitchBranch' => $this->branchContext->canSwitch($member),
            'queueIntegration' => $branchId && $permissions->handlesFloorCare($member)
                ? $this->queueBridge->listMeta($organization, CareQueueContexts::BILLING, $branchId)
                : ['enabled' => false],
            'canManageQueue' => $permissions->can($member, 'service_queues.console')
                && $permissions->handlesFloorCare($member),
        ]);
    }

    public function callNext(Request $request): RedirectResponse
    {
        $permissions = app(CarePermissions::class);
        $member = $this->member($request);
        abort_unless(
            $permissions->can($member, 'payments.manage')
            || $permissions->can($member, 'bills.manage'),
            403,
        );
        $organization = $this->organization($request);
        $branchId = $this->branchContext->resolve($request, $organization, $member);
        abort_unless($branchId > 0, 422);
        abort_unless($this->queueBridge->isEnabled($organization), 404);

        $result = $this->queueBridge->callNext($organization, CareQueueContexts::BILLING, $branchId, $member);
        $ticket = $result['ticket'] ?? null;
        if (! $ticket) {
            return back()->with(
                'info',
                'No patients waiting at the billing booth. Open an unpaid invoice above to record a walk-up payment.',
            );
        }

        return back()->with('success', 'Called '.$ticket['ticket_number'].'.');
    }

    public function serve(Request $request, Bill $bill): RedirectResponse
    {
        $permissions = app(CarePermissions::class);
        $member = $this->member($request);
        abort_unless(
            $permissions->can($member, 'payments.manage')
            || $permissions->can($member, 'bills.manage'),
            403,
        );
        $this->authorizeBill($request, $bill);
        $organization = $this->organization($request);
        abort_unless($this->queueBridge->isEnabled($organization), 404);

        $ticket = $this->queueBridge->serve($organization, $bill);
        if (! $ticket) {
            return back()->with('error', 'Could not serve billing ticket.');
        }

        return back()->with('success', 'Now serving '.$ticket['ticket_number'].'.');
    }

    public function generate(Request $request, Visit $visit): RedirectResponse
    {
        $this->authorizeAbility($request, 'bills.manage');
        $this->authorizeVisit($request, $visit);

        $bill = $this->bills->generateFromVisit($visit, $this->ownerRef($request), $this->ownerRef($request));

        $return = app(ConsultationReturnContext::class);

        return redirect()->route('care.bills.show', array_filter([
            'bill' => $bill,
            ...$return->routeQuery($request),
        ]))->with('success', 'Bill generated from visit.');
    }

    public function show(Request $request, Bill $bill): View
    {
        $this->authorizeAbility($request, 'bills.view');
        $this->authorizeBill($request, $bill);

        $bill->load(['patient', 'branch', 'visit', 'lineItems', 'payments']);

        $canManage = app(\App\Services\Care\CarePermissions::class)
            ->can($this->member($request), 'bills.manage');
        $canPay = app(\App\Services\Care\CarePermissions::class)
            ->can($this->member($request), 'payments.manage');
        $organization = $this->organization($request);
        $gatewayConfigured = $this->gateway->isConfigured($organization);
        $gateway = $this->gateway->settingFor($organization);

        // Manual record form excludes gateway method (online checkout is separate).
        $manualMethods = collect(config('care.payment_methods', []))
            ->except(['gateway'])
            ->all();

        $return = app(ConsultationReturnContext::class);
        $linked = Consultation::query()
            ->where('visit_id', $bill->visit_id)
            ->orderByDesc('id')
            ->first();

        return view('care.bills.show', [
            'bill' => $bill,
            'statuses' => config('care.bill_statuses'),
            'lineTypes' => config('care.bill_line_types'),
            'paymentMethods' => config('care.payment_methods'),
            'manualPaymentMethods' => $manualMethods,
            'canManage' => $canManage,
            'canPay' => $canPay,
            'gatewayConfigured' => $gatewayConfigured,
            'gatewayProvider' => $gateway?->provider,
            'gatewayLabels' => config('care.payment_gateways'),
            'returnConsultation' => $return->resolve($request, (int) $bill->patient_id)
                ?? $return->resolveLinked($request, $linked),
        ]);
    }

    public function addLineItem(Request $request, Bill $bill): RedirectResponse
    {
        $this->authorizeAbility($request, 'bills.manage');
        $this->authorizeBill($request, $bill);

        $this->bills->addManualLineItem(
            $bill,
            $this->ownerRef($request),
            $request->validate([
                'type' => ['required', 'string', 'in:'.implode(',', array_keys(config('care.bill_line_types')))],
                'description' => ['required', 'string', 'max:255'],
                'quantity' => ['required', 'integer', 'min:1'],
                'unit_price_minor' => ['required', 'integer', 'min:0'],
            ]),
            $this->ownerRef($request),
        );

        return back()->with('success', 'Line item added.');
    }

    public function applyAdjustments(Request $request, Bill $bill): RedirectResponse
    {
        $this->authorizeAbility($request, 'bills.manage');
        $this->authorizeBill($request, $bill);

        $validated = $request->validate([
            'discount_minor' => ['nullable', 'integer', 'min:0'],
            'tax_minor' => ['nullable', 'integer', 'min:0'],
        ]);

        $this->bills->applyAdjustments(
            $bill,
            $this->ownerRef($request),
            (int) ($validated['discount_minor'] ?? 0),
            (int) ($validated['tax_minor'] ?? 0),
            $this->ownerRef($request),
        );

        return back()->with('success', 'Bill updated.');
    }

    public function recordPayment(Request $request, Bill $bill): RedirectResponse
    {
        $this->authorizeAbility($request, 'payments.manage');
        $this->authorizeBill($request, $bill);

        $manualMethods = array_keys(collect(config('care.payment_methods', []))->except(['gateway'])->all());

        $validated = $request->validate([
            'amount' => ['required', 'numeric', 'min:0.01'],
            'method' => ['required', 'string', 'in:'.implode(',', $manualMethods)],
            'reference' => ['nullable', 'string', 'max:100'],
            'notes' => ['nullable', 'string', 'max:500'],
        ]);

        $validated['amount_minor'] = max(1, (int) round(((float) $validated['amount']) * 100));
        unset($validated['amount']);

        $this->bills->recordPayment(
            $bill,
            $this->ownerRef($request),
            $validated,
            $this->ownerRef($request),
        );

        return back()->with('success', 'Payment recorded.');
    }

    public function startGatewayPayment(Request $request, Bill $bill): RedirectResponse
    {
        $this->authorizeAbility($request, 'payments.manage');
        $this->authorizeBill($request, $bill);

        $validated = $request->validate([
            'amount' => ['nullable', 'numeric', 'min:0.01'],
        ]);

        $amount = isset($validated['amount'])
            ? max(1, (int) round(((float) $validated['amount']) * 100))
            : (int) $bill->balance_minor;

        try {
            $result = $this->bills->startGatewayPayment(
                $bill->loadMissing('patient'),
                $this->organization($request),
                $this->ownerRef($request),
                $amount,
                $request->user(),
                $this->ownerRef($request),
            );
        } catch (RuntimeException|\InvalidArgumentException $e) {
            return back()->with('error', $e->getMessage());
        }

        return redirect()->away($result['checkout_url']);
    }

    public function paymentCallback(Request $request): RedirectResponse|View
    {
        $reference = trim((string) (
            $request->query('reference')
            ?: $request->query('tx_ref')
            ?: $request->query('clientReference')
            ?: ''
        ));

        if ($reference === '') {
            return view('care.bills.payment-return', [
                'redirect' => route('care.bills.index'),
            ]);
        }

        try {
            $payment = $this->bills->completeGatewayPayment($reference);
        } catch (\Throwable) {
            session()->flash('error', 'Payment could not be verified. If the customer was charged, re-check the bill or contact support with reference '.$reference.'.');

            return view('care.bills.payment-return', [
                'redirect' => route('care.bills.index'),
            ]);
        }

        $bill = $payment->bill;
        session()->flash('success', 'Online payment recorded for '.$bill->invoice_number.'.');

        return view('care.bills.payment-return', [
            'redirect' => route('care.bills.show', $bill),
        ]);
    }

    public function void(Request $request, Bill $bill): RedirectResponse
    {
        $this->authorizeAbility($request, 'bills.manage');
        $this->authorizeBill($request, $bill);

        $this->bills->void($bill, $this->ownerRef($request), $this->ownerRef($request));

        return redirect()->route('care.bills.index')->with('success', 'Bill voided.');
    }

    public function print(Request $request, Bill $bill): View
    {
        $this->authorizeAbility($request, 'bills.view');
        $this->authorizeBill($request, $bill);

        $bill->load(['patient', 'branch', 'lineItems', 'payments', 'organization']);

        return view('care.bills.print', [
            'bill' => $bill,
            'organization' => $this->organization($request),
        ]);
    }

    protected function authorizeBill(Request $request, Bill $bill): void
    {
        $this->authorizeOwner($request, $bill);
        abort_unless($bill->organization_id === $this->organization($request)->id, 404);

        $branchId = app(OrganizationResolver::class)->branchScope($this->member($request));
        if ($branchId !== null && $bill->branch_id !== $branchId) {
            abort(404);
        }
    }

    protected function authorizeVisit(Request $request, Visit $visit): void
    {
        $this->authorizeOwner($request, $visit);
        abort_unless($visit->organization_id === $this->organization($request)->id, 404);
    }
}
