<?php

namespace Modules\Bms\Services\Finance;

use Illuminate\Support\Carbon;
use Modules\Bms\Entities\BmsBooking;
use Modules\Bms\Entities\BmsPaymentSchedule;
use Modules\Bms\Entities\BmsSetting;

/**
 * Structured payment plan for a booking: a deposit / installment / balance
 * schedule derived from an admin-editable template, against which the ERP's
 * recorded payments are allocated (oldest due first). Backs the deposit gate.
 */
class PaymentPlanService
{
    /**
     * Build the schedule for a booking from the per-business template (falling
     * back to config). Idempotent — does nothing if a schedule already exists.
     * The final row absorbs any rounding remainder so the plan sums to the
     * booking total to the cent.
     *
     * @return int  number of schedule rows created
     */
    public function generateForBooking(BmsBooking $booking): int
    {
        if (BmsPaymentSchedule::where('booking_id', $booking->id)->exists()) {
            return 0;
        }

        $businessId = (int) $booking->business_id;
        $template = $this->template($businessId);
        if (empty($template)) {
            return 0;
        }

        $total = round((float) $booking->total_amount, 2);
        $eventDate = $booking->event_date ? Carbon::parse($booking->event_date) : now()->addMonth();

        // Pre-compute amounts; push any rounding remainder onto the last row.
        $amounts = [];
        $running = 0.0;
        foreach ($template as $row) {
            $amt = round($total * ((float) ($row['percent'] ?? 0)) / 100, 2);
            $amounts[] = $amt;
            $running += $amt;
        }
        if (! empty($amounts)) {
            $amounts[count($amounts) - 1] = round($amounts[count($amounts) - 1] + ($total - $running), 2);
        }

        $created = 0;
        foreach ($template as $i => $row) {
            BmsPaymentSchedule::create([
                'business_id' => $businessId,
                'booking_id'  => $booking->id,
                'label'       => $row['label'] ?? ('Installment ' . ($i + 1)),
                'kind'        => $row['kind'] ?? 'installment',
                'due_date'    => $eventDate->copy()->addDays((int) ($row['offset_days'] ?? 0))->toDateString(),
                'percent'     => (float) ($row['percent'] ?? 0),
                'amount'      => $amounts[$i],
                'paid_amount' => 0,
                'status'      => 'pending',
                'sort_order'  => $i + 1,
            ]);
            $created++;
        }

        return $created;
    }

    /**
     * Re-allocate the booking's total collected (from the ERP) across the
     * schedule rows, oldest due first. Idempotent — always recomputes from the
     * authoritative paid figure, so calling it after every payment is safe.
     */
    public function syncPaid(BmsBooking $booking, float $totalPaid): void
    {
        $rows = BmsPaymentSchedule::where('booking_id', $booking->id)
            ->where('status', '!=', 'cancelled')
            ->orderBy('sort_order')
            ->get();
        if ($rows->isEmpty()) {
            return;
        }

        $remaining = round($totalPaid, 2);
        $now = now()->startOfDay();

        foreach ($rows as $row) {
            $amount = round((float) $row->amount, 2);
            $applied = max(0.0, min($amount, $remaining));
            $remaining = round($remaining - $applied, 2);

            $status = 'pending';
            if ($applied >= $amount && $amount > 0) {
                $status = 'paid';
            } elseif ($applied > 0) {
                $status = 'partial';
            } elseif ($row->due_date && Carbon::parse($row->due_date)->lt($now)) {
                $status = 'overdue';
            }

            $row->forceFill(['paid_amount' => $applied, 'status' => $status])->save();
        }
    }

    /** The deposit row(s) are fully covered. (No deposit row → trivially met.) */
    public function depositMet(BmsBooking $booking): bool
    {
        $deposits = BmsPaymentSchedule::where('booking_id', $booking->id)
            ->where('kind', 'deposit')->get();
        if ($deposits->isEmpty()) {
            return true;
        }
        return $deposits->every(fn ($r) => $r->status === 'paid');
    }

    /** True when the business enforces the deposit-before-contract gate. */
    public function depositGateEnforced(int $businessId): bool
    {
        return (bool) BmsSetting::get($businessId, 'enforce_deposit_gate', config('bms.enforce_deposit_gate', false));
    }

    /** Schedule rows shaped for the API/UI. */
    public function schedule(BmsBooking $booking): array
    {
        return BmsPaymentSchedule::where('booking_id', $booking->id)
            ->orderBy('sort_order')
            ->get()
            ->map(fn ($r) => [
                'id'          => $r->id,
                'label'       => $r->label,
                'kind'        => $r->kind,
                'due_date'    => optional($r->due_date)->toDateString(),
                'percent'     => (float) $r->percent,
                'amount'      => (float) $r->amount,
                'paid_amount' => (float) $r->paid_amount,
                'due'         => $r->due,
                'status'      => $r->status,
            ])->values()->all();
    }

    /** Re-anchor due dates to a (new) event date — used on reschedule. */
    public function reanchor(BmsBooking $booking, Carbon $eventDate): void
    {
        $template = $this->template((int) $booking->business_id);
        $rows = BmsPaymentSchedule::where('booking_id', $booking->id)
            ->where('status', '!=', 'cancelled')
            ->orderBy('sort_order')->get();
        foreach ($rows as $i => $row) {
            $offset = (int) ($template[$i]['offset_days'] ?? 0);
            $row->forceFill(['due_date' => $eventDate->copy()->addDays($offset)->toDateString()])->save();
        }
    }

    /** Per-business template (bms_settings) merged over the config fallback. */
    protected function template(int $businessId): array
    {
        $tpl = BmsSetting::get($businessId, 'payment_schedule_template', null);
        if (is_array($tpl) && ! empty($tpl)) {
            return $tpl;
        }
        return (array) config('bms.payment_schedule_template', []);
    }
}
