<?php

namespace App\Services\Finance;

use App\Models\Invoice;
use App\Models\Receipt;
use App\Models\Student;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;

class BillingService
{
    public function __construct(
        private readonly DocumentNumberService $documentNumbers,
        private readonly InvoiceService $invoiceService,
    ) {}

    /**
     * @return array{
     *     student: Student,
     *     invoices: Collection<int, Invoice>,
     *     receipts: Collection<int, Receipt>,
     *     total_invoiced: string,
     *     total_paid: string,
     *     total_outstanding: string
     * }
     */
    public function studentStatement(Student $student): array
    {
        $student->loadMissing(['invoices' => fn ($q) => $q->latest('issue_date'), 'receipts' => fn ($q) => $q->latest('payment_date')]);

        $totalInvoiced = $student->invoices->reduce(
            fn (string $carry, Invoice $inv) => bcadd($carry, (string) $inv->total_amount, 2),
            '0.00'
        );

        $totalPaid = $student->receipts->reduce(
            fn (string $carry, Receipt $rcp) => bcadd($carry, (string) $rcp->amount, 2),
            '0.00'
        );

        $totalOutstanding = $student->invoices->reduce(
            fn (string $carry, Invoice $inv) => bcadd($carry, (string) $inv->balance_due, 2),
            '0.00'
        );

        return [
            'student' => $student,
            'invoices' => $student->invoices,
            'receipts' => $student->receipts,
            'total_invoiced' => $totalInvoiced,
            'total_paid' => $totalPaid,
            'total_outstanding' => $totalOutstanding,
        ];
    }

    /**
     * @param  array<string, mixed>  $data
     * @param  array<int, array<string, mixed>>  $lines
     */
    public function assignFee(Student $student, array $data, array $lines): Invoice
    {
        if ($lines === []) {
            throw new InvalidArgumentException('Fee assignment requires at least one line item.');
        }

        return DB::transaction(function () use ($student, $data, $lines) {
            $subtotal = '0.00';

            foreach ($lines as $line) {
                $lineTotal = bcmul((string) $line['quantity'], (string) $line['unit_price'], 2);
                $subtotal = bcadd($subtotal, $lineTotal, 2);
            }

            $taxAmount = (string) ($data['tax_amount'] ?? '0.00');
            $totalAmount = bcadd($subtotal, $taxAmount, 2);

            $invoice = Invoice::query()->create([
                'invoice_number' => $this->documentNumbers->next('INV', Invoice::class, 'invoice_number'),
                'student_id' => $student->id,
                'issue_date' => $data['issue_date'],
                'due_date' => $data['due_date'] ?? null,
                'status' => 'draft',
                'subtotal' => $subtotal,
                'tax_amount' => $taxAmount,
                'total_amount' => $totalAmount,
                'amount_paid' => 0,
                'balance_due' => $totalAmount,
                'notes' => $data['notes'] ?? null,
            ]);

            foreach ($lines as $line) {
                $lineTotal = bcmul((string) $line['quantity'], (string) $line['unit_price'], 2);

                $invoice->lines()->create([
                    'description' => $line['description'],
                    'quantity' => $line['quantity'],
                    'unit_price' => $line['unit_price'],
                    'line_total' => $lineTotal,
                    'account_id' => $line['account_id'] ?? null,
                ]);
            }

            return $this->invoiceService->issue($invoice->fresh(['lines', 'student']));
        });
    }
}
