<?php

namespace App\Domain\Financial\Services;

use App\Domain\Financial\Models\FinancialAccount;
use App\Domain\Shared\Exceptions\DomainException;

/**
 * Resolves chart-of-accounts rows by CODE, scoped to an academy.
 *
 * Before this existed, PaymentService::resolveDebitAccount() and
 * resolveCreditAccount() returned the literal ids 1 and 2 with a `// TODO`.
 * Seeder order made that "Dr Cash / Cr Bank" on every payment ever recorded:
 * no revenue account was ever credited, no receivable ever cleared, and
 * FinancialOverview::getRevenueBySource() — which groups transactions by
 * credit_account_id restricted to revenue accounts — therefore always
 * returned an empty array.
 *
 * Resolution is by code because ids differ per tenant (each client DB seeded
 * its own chart) and hard-fails when the account is absent, following the
 * precedent already set by PayrollService::createPayslipTransaction().
 */
class LedgerAccountResolver
{
    /** Assets — where money lands. */
    public const CASH = '1000';
    public const BANK = '1010';
    public const RECEIVABLE = '1100';
    public const WALLET = '1200';

    /** Revenue — what the money was for. */
    public const REVENUE_TRAINING = '4000';
    public const REVENUE_REGISTRATION = '4010';
    public const REVENUE_EQUIPMENT = '4020';
    public const REVENUE_FACILITY = '4030';
    public const REVENUE_PRIVATE = '4040';
    public const REVENUE_TOURNAMENT = '4050';
    public const REVENUE_OTHER = '4060';

    /** @var array<string, FinancialAccount> keyed "{academyId}:{code}" */
    private array $cache = [];

    public function byCode(int $academyId, string $code): FinancialAccount
    {
        $key = "{$academyId}:{$code}";

        if (isset($this->cache[$key])) {
            return $this->cache[$key];
        }

        $account = FinancialAccount::withoutGlobalScopes()
            ->where('academy_id', $academyId)
            ->where('code', $code)
            ->first();

        if (! $account) {
            throw new DomainException(
                "حساب رقم {$code} غير موجود في شجرة الحسابات — يرجى إعداد شجرة الحسابات"
            );
        }

        return $this->cache[$key] = $account;
    }

    /**
     * The asset account a payment settles into, from its method.
     *
     * `wallet` means the academy's own internal wallet, not mobile money —
     * settling from it moves the balance held for the member, not cash.
     */
    public function assetAccountForMethod(int $academyId, ?string $method): FinancialAccount
    {
        $code = match ($method) {
            'cash' => self::CASH,
            'wallet' => self::WALLET,
            'card', 'bank_transfer', 'instapay', 'cheque', 'online' => self::BANK,
            default => self::CASH,
        };

        return $this->byCode($academyId, $code);
    }

    /**
     * Deciding WHICH revenue account a collection belongs to no longer lives
     * here.
     *
     * splitRevenue() and revenueCodeForItemable() used to answer that with a
     * `match` over five itemable classes, which meant the answer was the same
     * for every client and could only be changed by a deploy — and there was
     * nowhere at all to say that part of an amount is tax. RevenueRouter reads
     * the academy's own routing rules instead, and falls back to exactly these
     * five pairings when a tenant has configured nothing.
     *
     * They were deleted rather than deprecated: a second implementation of
     * "where does this money go" is one a caller will eventually reach for, and
     * it would silently bypass every rule the academy's accountant wrote.
     *
     * What stays here is what the router still needs and what has no second
     * opinion: resolving an account by code, and the asset account a payment
     * method settles into.
     */
}
