<?php

namespace App\Services\Finance;

use App\Enums\JournalSource;
use App\Models\Invoice;
use App\Models\JournalEntry;
use App\Services\Accounting\AccountLookupService;
use App\Services\Accounting\JournalService;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;

class InvoiceService
{
    public function __construct(
        private readonly JournalService $journalService,
        private readonly AccountLookupService $accountLookup,
    ) {}

    public function issue(Invoice $invoice): Invoice
    {
        $invoice->loadMissing(['lines.account', 'student']);

        if ($invoice->lines->isEmpty()) {
            throw new InvalidArgumentException('Cannot issue an invoice without line items.');
        }

        if (bccomp((string) $invoice->total_amount, '0.00', 2) <= 0) {
            throw new InvalidArgumentException('Cannot issue an invoice with a zero or negative total.');
        }

        $lineTotal = $invoice->lines->reduce(
            fn (string $carry, $line) => bcadd($carry, (string) $line->line_total, 2),
            '0.00'
        );

        $expectedTotal = bcadd($lineTotal, (string) $invoice->tax_amount, 2);

        if (bccomp($expectedTotal, (string) $invoice->total_amount, 2) !== 0) {
            throw new InvalidArgumentException('Invoice totals do not balance with line items and tax.');
        }

        return DB::transaction(function () use ($invoice) {
            $receivableAccount = $this->accountLookup->configured('accounts_receivable');
            $defaultRevenueAccount = $this->accountLookup->configured('default_revenue');
            $taxAccount = $this->accountLookup->configured('tax_payable');

            $journalLines = [];
            $journalLines[] = [
                'account_id' => $receivableAccount->id,
                'debit_amount' => $invoice->total_amount,
                'credit_amount' => 0,
                'line_memo' => "Invoice {$invoice->invoice_number}",
            ];

            foreach ($invoice->lines as $line) {
                $revenueAccountId = $line->account_id ?? $defaultRevenueAccount->id;

                $journalLines[] = [
                    'account_id' => $revenueAccountId,
                    'debit_amount' => 0,
                    'credit_amount' => $line->line_total,
                    'line_memo' => $line->description,
                ];
            }

            if (bccomp((string) $invoice->tax_amount, '0.00', 2) > 0) {
                $journalLines[] = [
                    'account_id' => $taxAccount->id,
                    'debit_amount' => 0,
                    'credit_amount' => $invoice->tax_amount,
                    'line_memo' => "Tax on invoice {$invoice->invoice_number}",
                ];
            }

            $journalEntry = $this->journalService->post([
                'date' => $invoice->issue_date,
                'description' => "Invoice {$invoice->invoice_number} for {$invoice->student->full_name}",
                'source' => JournalSource::Invoicing->value,
                'source_reference' => $invoice->invoice_number,
            ], $journalLines);

            $invoice->update([
                'status' => 'issued',
                'balance_due' => bcsub((string) $invoice->total_amount, (string) $invoice->amount_paid, 2),
            ]);

            $invoice->setRelation('latestJournalEntry', $journalEntry);

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

    public function latestJournalEntry(Invoice $invoice): ?JournalEntry
    {
        return JournalEntry::query()
            ->where('source', JournalSource::Invoicing->value)
            ->where('source_reference', $invoice->invoice_number)
            ->latest('id')
            ->first();
    }
}
