<?php

namespace App\Services;

use CodeIgniter\Database\BaseConnection;

/**
 * Invoice payment recording with partial / full settlement.
 */
class PaymentService
{
    public function __construct(private BaseConnection $db)
    {
    }

    /**
     * @return array{paid_total: float, balance: float, status: string}
     */
    public function recordPayment(
        int $invoiceId,
        float $amount,
        string $method,
        int $recordedBy,
        ?string $referenceNo = null,
        ?string $notes = null
    ): array {
        $inv = $this->db->table('invoices')->where('id', $invoiceId)->get()->getRowArray();
        if (!$inv) {
            throw new \RuntimeException('Invoice not found.');
        }
        if (!in_array($inv['status'], ['sent', 'overdue', 'partial'], true)) {
            throw new \RuntimeException('Invoice cannot accept payments in status: ' . $inv['status']);
        }
        if ($amount <= 0) {
            throw new \RuntimeException('Payment amount must be greater than zero.');
        }

        $paidSoFar = $this->getPaidTotal($invoiceId);
        $balance   = round((float) $inv['total'] - $paidSoFar, 2);
        if ($amount > $balance + 0.01) {
            throw new \RuntimeException("Payment exceeds balance due ({$balance}).");
        }

        if (!$this->db->tableExists('invoice_payments')) {
            throw new \RuntimeException('Run enterprise migration to enable payment ledger.');
        }

        $this->db->table('invoice_payments')->insert([
            'invoice_id'     => $invoiceId,
            'amount'         => round($amount, 2),
            'payment_method' => in_array($method, ['cash', 'bank', 'card', 'cheque', 'online'], true) ? $method : 'bank',
            'reference_no'   => $referenceNo,
            'notes'          => $notes,
            'paid_at'        => date('Y-m-d H:i:s'),
            'recorded_by'    => $recordedBy,
            'created_at'     => date('Y-m-d H:i:s'),
        ]);

        $newPaid   = $paidSoFar + $amount;
        $newStatus = $newPaid >= (float) $inv['total'] - 0.01 ? 'paid' : 'partial';
        $update    = ['status' => $newStatus];
        if ($newStatus === 'paid') {
            $update['paid_at'] = date('Y-m-d H:i:s');
            $update['paid_by'] = $recordedBy;
        }

        $this->db->table('invoices')->where('id', $invoiceId)->update($update);

        return [
            'paid_total' => round($newPaid, 2),
            'balance'    => round(max(0, (float) $inv['total'] - $newPaid), 2),
            'status'     => $newStatus,
        ];
    }

    public function getPaidTotal(int $invoiceId): float
    {
        if (!$this->db->tableExists('invoice_payments')) {
            return $this->db->table('invoices')->where('id', $invoiceId)->where('status', 'paid')->countAllResults()
                ? (float) ($this->db->table('invoices')->where('id', $invoiceId)->get()->getRowArray()['total'] ?? 0)
                : 0;
        }

        return (float) ($this->db->table('invoice_payments')
            ->selectSum('amount', 't')
            ->where('invoice_id', $invoiceId)
            ->get()
            ->getRowArray()['t'] ?? 0);
    }

    /** @return list<array<string, mixed>> */
    public function paymentHistory(int $invoiceId): array
    {
        if (!$this->db->tableExists('invoice_payments')) {
            return [];
        }

        return $this->db->table('invoice_payments p')
            ->select('p.*, u.name AS recorded_by_name')
            ->join('users u', 'u.id = p.recorded_by', 'left')
            ->where('p.invoice_id', $invoiceId)
            ->orderBy('p.paid_at', 'DESC')
            ->get()
            ->getResultArray();
    }
}
