<?php

namespace App\Models;

use Config\Lookups;

/**
 * `method` is cash/mobile_money/card/bank_transfer in Phase 1 — plain
 * VARCHAR, validated against App\Config\Lookups::$paymentMethods, so
 * Phase 3 can add `hubtel_momo` etc. as just another string. No
 * `updated_at`: rows here are immutable once recorded (see migration).
 *
 * `recorded_by` is nullable as of Phase 3: null means the row was written
 * automatically by the Hubtel webhook, not by a staff member at the
 * keyboard (see MakeFolioPaymentsRecordedByNullable + PaymentWebhookController).
 */
class FolioPaymentModel extends TenantModel
{
    protected $table            = 'folio_payments';
    protected $primaryKey       = 'id';
    protected $useAutoIncrement = true;
    protected $returnType       = 'object';
    protected $useTimestamps    = true;
    protected $createdField     = 'created_at';
    protected $updatedField     = '';

    protected $allowedFields = [
        'folio_id',
        'amount',
        'is_refund',
        'refund_reason',
        'method',
        'reference',
        'recorded_by',
        'paid_at',
    ];

    // Loyalty points are earned off every real (non-refund) payment,
    // wherever it's recorded from (FolioController, CheckInController's
    // initial-payment path, PosOrderModel::settleDirect(), the Hubtel
    // webhook) — hooking the model itself means every caller earns
    // points automatically, with nothing to remember to call.
    protected $afterInsert = ['awardLoyaltyPoints'];

    public function __construct()
    {
        parent::__construct();

        $methods = (new Lookups())->paymentMethodKeys();

        $this->validationRules = [
            'folio_id'    => 'required|integer',
            'amount'      => 'required|decimal|greater_than[0]',
            'method'      => 'required|in_list[' . implode(',', $methods) . ']',
            'recorded_by' => 'permit_empty|integer',
        ];
    }

    public function forFolio(int $folioId): array
    {
        return $this->where('folio_id', $folioId)->orderBy('paid_at', 'asc')->findAll();
    }

    /**
     * Sum of payments actually recorded within [startDate, endDate]
     * (inclusive, by paid_at date) — "cash collected", as distinct from
     * FolioLineItemModel::totalBetween() ("charges posted"). The two
     * rarely match for any given day since a payment often settles
     * charges posted on earlier days.
     */
    public function totalBetween(string $startDate, string $endDate): string
    {
        $result = $this->where('DATE(paid_at) >=', $startDate)->where('DATE(paid_at) <=', $endDate)->selectSum('amount')->first();

        return $result->amount ?? '0.00';
    }

    /**
     * @param array{id: int, data: array, result: bool} $eventData
     */
    protected function awardLoyaltyPoints(array $eventData): array
    {
        $data = $eventData['data'];

        if ((int) ($data['is_refund'] ?? 0) === 1) {
            return $eventData;
        }

        $folio = (new FolioModel())->find($data['folio_id']);

        if ($folio === null) {
            return $eventData;
        }

        $rate = (float) (service('settings')->get('loyalty.points_per_ghs_rate') ?: 10);

        if ($rate <= 0) {
            return $eventData;
        }

        $points = (int) floor((float) $data['amount'] / $rate);

        if ($points <= 0) {
            return $eventData;
        }

        (new LoyaltyAccountModel())->creditPoints(
            (int) $folio->guest_id,
            $points,
            'earn',
            'folio_payment',
            $eventData['id'],
            format_money($data['amount']) . ' paid on folio #' . $data['folio_id'],
            isset($data['recorded_by']) ? (int) $data['recorded_by'] : null,
        );

        return $eventData;
    }
}
