<?php

namespace App\Http\Controllers\Care;

use App\Http\Controllers\Care\Concerns\ScopesToAccount;
use App\Http\Controllers\Controller;
use App\Models\FinancialObligation;
use App\Models\Payment;
use App\Models\WorkflowStage;
use App\Services\Care\AuditLogger;
use App\Services\Care\BillService;
use App\Services\Care\BranchContext;
use App\Services\Care\CareFeatures;
use App\Services\Care\CarePermissions;
use App\Services\Care\CareQueueBridge;
use App\Services\Care\CareQueueContexts;
use App\Services\Care\OrganizationResolver;
use App\Services\Care\Workflow\FinancialGateService;
use App\Services\Care\Workflow\WorkflowEngine;
use App\Services\Care\Workflow\WorkflowQueueReleaseService;
use App\Services\Care\Workflow\WorkflowStageCompletionService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;

class FinancialObligationController extends Controller
{
    use ScopesToAccount;

    public function __construct(
        protected CareFeatures $features,
        protected FinancialGateService $gates,
        protected WorkflowEngine $workflows,
        protected WorkflowQueueReleaseService $queueRelease,
        protected WorkflowStageCompletionService $workflowCompletion,
        protected BillService $bills,
        protected CareQueueBridge $queueBridge,
        protected BranchContext $branchContext,
    ) {}

    public function index(Request $request): View
    {
        $this->authorizeAbility($request, 'bills.view');
        $organization = $this->organization($request);
        abort_unless($this->features->enabled($organization, CareFeatures::WORKFLOW_ENGINE), 404);

        $member = $this->member($request);
        $permissions = app(CarePermissions::class);
        $branchScope = app(OrganizationResolver::class)->branchScope($member);
        $branchId = $this->branchContext->resolve($request, $organization, $member);
        $status = (string) $request->query('status', FinancialObligation::STATUS_PENDING);

        $obligations = FinancialObligation::owned($this->ownerRef($request))
            ->where('organization_id', $organization->id)
            ->when($branchScope, fn ($query) => $query->where('branch_id', $branchScope))
            ->when($status !== '', fn ($query) => $query->where('status', $status))
            ->with(['patient', 'branch', 'stage', 'visit', 'bill'])
            ->latest('created_at')
            ->paginate(20)
            ->withQueryString();

        return view('care.financial-obligations.index', [
            'obligations' => $obligations,
            'statuses' => config('care_workflows.obligation_statuses', []),
            'clearanceMethods' => config('care_workflows.clearance_methods', []),
            'paymentModes' => config('care_workflows.payment_modes', []),
            'paymentMethods' => collect(config('care.payment_methods', []))->except(['gateway'])->all(),
            'canClear' => $permissions->can($member, 'payments.manage'),
            'branchId' => $branchId,
            '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 clear(Request $request, FinancialObligation $financialObligation): RedirectResponse
    {
        $this->authorizeAbility($request, 'payments.manage');
        $this->authorizeObligation($request, $financialObligation);

        if ($financialObligation->status !== FinancialObligation::STATUS_PENDING) {
            return back()->with('info', 'This financial obligation is no longer pending.');
        }

        $manualPaymentMethods = array_keys(
            collect(config('care.payment_methods', []))->except(['gateway'])->all()
        );
        $validated = $request->validate([
            'method' => ['required', Rule::in(array_keys(config('care_workflows.clearance_methods', [])))],
            'payment_mode' => ['nullable', Rule::in(array_keys(config('care_workflows.payment_modes', [])))],
            'payment_method' => ['nullable', Rule::in($manualPaymentMethods)],
            'reference' => ['nullable', 'string', 'max:100'],
        ]);

        $stage = $financialObligation->stage;
        $method = (string) $validated['method'];
        $paymentMode = $validated['payment_mode'] ?? null;
        $reference = trim((string) ($validated['reference'] ?? '')) ?: null;

        $this->validateClearanceRules($stage, $method, $paymentMode, $reference);
        if ($method === 'bank_receipt') {
            $paymentMode = 'external_bank';
        }

        $settled = DB::transaction(function () use ($financialObligation, $validated, $method, $paymentMode, $reference, $request) {
            $locked = FinancialObligation::query()->lockForUpdate()->findOrFail($financialObligation->id);
            if ($locked->status !== FinancialObligation::STATUS_PENDING) {
                return $locked;
            }

            if (in_array($method, ['payment', 'bank_receipt'], true) && $locked->amount_minor > 0) {
                $bill = $this->bills->billForObligation(
                    $locked,
                    $this->ownerRef($request),
                    $this->ownerRef($request),
                );

                $this->bills->recordPayment($bill, $this->ownerRef($request), [
                    'amount_minor' => $locked->amount_minor,
                    'method' => $method === 'bank_receipt'
                        ? Payment::METHOD_OTHER
                        : ($validated['payment_method'] ?? Payment::METHOD_CASH),
                    'reference' => $reference,
                    'notes' => 'Workflow financial obligation '.$locked->uuid,
                ], $this->ownerRef($request));
            }

            $this->gates->clear(
                $locked,
                $method,
                $this->ownerRef($request),
                $paymentMode,
                $reference,
            );

            AuditLogger::record(
                $this->ownerRef($request),
                'financial_obligation.cleared',
                $locked->organization_id,
                $this->ownerRef($request),
                FinancialObligation::class,
                $locked->id,
                ['method' => $method, 'amount_minor' => $locked->amount_minor],
            );

            return $locked->fresh();
        });

        $organization = $this->organization($request);

        $visit = $settled->visit;
        if ($visit) {
            $advance = $this->workflows->refreshGate($visit);
            if ($advance?->stage?->type === 'payment') {
                $this->workflowCompletion->complete(
                    $organization,
                    $visit,
                    CareQueueContexts::BILLING,
                    $this->ownerRef($request),
                    $this->ownerRef($request),
                );
            } else {
                $this->queueRelease->releaseCurrent($organization, $visit);
            }
        }

        return back()->with('success', 'Financial obligation cleared. The patient may now continue.');
    }

    protected function validateClearanceRules(?WorkflowStage $stage, string $method, ?string $paymentMode, ?string $reference): void
    {
        if ($method === 'insurance' && ! $stage?->insurance_eligible) {
            throw ValidationException::withMessages(['method' => 'Insurance is not allowed for this stage.']);
        }

        if ($method === 'credit' && ! $stage?->credit_allowed) {
            throw ValidationException::withMessages(['method' => 'Credit is not allowed for this stage.']);
        }

        if (in_array($method, ['waiver', 'override'], true) && ! $stage?->allow_override) {
            throw ValidationException::withMessages(['method' => 'Waivers and overrides are not allowed for this stage.']);
        }

        if ($method === 'bank_receipt' && $reference === null) {
            throw ValidationException::withMessages(['reference' => 'Enter the external bank receipt or slip number.']);
        }

        if ($method === 'bank_receipt' && $stage && ! in_array('external_bank', (array) $stage->payment_modes, true)) {
            throw ValidationException::withMessages(['method' => 'External bank receipts are not allowed for this stage.']);
        }

        if ($method === 'payment') {
            $allowedModes = (array) ($stage?->payment_modes ?? []);
            if ($paymentMode === null || ($allowedModes !== [] && ! in_array($paymentMode, $allowedModes, true))) {
                throw ValidationException::withMessages(['payment_mode' => 'Select a payment mode allowed for this stage.']);
            }

            if ($paymentMode === 'external_bank') {
                throw ValidationException::withMessages(['method' => 'Use external bank receipt verification for bank payments.']);
            }
        }
    }

    protected function authorizeObligation(Request $request, FinancialObligation $obligation): void
    {
        $this->authorizeOwner($request, $obligation);
        abort_unless($obligation->organization_id === $this->organization($request)->id, 404);

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