<?php

namespace App\Domain\Accounting\Models;

use App\Domain\Accounting\Enums\AccountType;
use App\Support\Concerns\HasUuid;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;

/**
 * One account in the chart (§33).
 *
 * A group account is a heading: it has children and takes no postings, so a
 * total can never be double-counted against its own parent.
 */
class LedgerAccount extends Model
{
    use HasUuid, SoftDeletes;

    protected $guarded = ['id', 'uuid'];

    protected function casts(): array
    {
        return [
            'type' => AccountType::class,
            'is_group' => 'boolean',
            'is_active' => 'boolean',
            'is_cash_equivalent' => 'boolean',
            'is_system' => 'boolean',
        ];
    }

    public function parent(): BelongsTo
    {
        return $this->belongsTo(self::class, 'parent_id');
    }

    public function children(): HasMany
    {
        return $this->hasMany(self::class, 'parent_id')->orderBy('code');
    }

    public function lines(): HasMany
    {
        return $this->hasMany(JournalLine::class);
    }

    /**
     * The balance in the account's own natural direction, so an expense reads
     * positive when money was spent. Signed: a credit balance on a debit
     * account is real and must not be clamped away.
     */
    public function balance(?string $upTo = null): int
    {
        $totals = JournalLine::query()
            ->where('ledger_account_id', $this->getKey())
            ->whereHas('entry', function (Builder $entry) use ($upTo) {
                $entry->affectingBalances();

                if ($upTo !== null) {
                    $entry->whereDate('entry_date', '<=', $upTo);
                }
            })
            ->selectRaw('COALESCE(SUM(debit), 0) as debit, COALESCE(SUM(credit), 0) as credit')
            ->first();

        return $this->type->normalBalance() === 'debit'
            ? (int) $totals->debit - (int) $totals->credit
            : (int) $totals->credit - (int) $totals->debit;
    }

    public function scopePostable(Builder $query): Builder
    {
        return $query->where('is_group', false)->where('is_active', true);
    }

    public function scopeOfType(Builder $query, AccountType $type): Builder
    {
        return $query->where('type', $type->value);
    }

    public function scopeCash(Builder $query): Builder
    {
        return $query->where('is_cash_equivalent', true);
    }
}
