<?php

declare(strict_types=1);

namespace App\Services\Metrics;

use App\Enums\ExpenseCategory;
use App\Enums\IncomeCategory;
use App\Models\Expense;
use App\Models\Income;
use Carbon\CarbonInterface;

/**
 * Rentabilidad por periodo (flujo).
 *
 * El interes viene de InterestAttributionService (ledger de cuotas), NO de
 * `incomes`, porque los pagos se registran con la categoria generica `payment`.
 * Para no contar dos veces, los ingresos de `incomes` excluyen `payment` y
 * `loan_payment_interest`.
 *
 * Ver docs/superpowers/specs/2026-09-08-dashboard-salud-negocio-design.md §A.
 */
class BusinessMetricsService
{
    public function __construct(
        private readonly InterestAttributionService $interest,
    ) {}

    /**
     * @return array{
     *     interest_collected: float, other_profit_income: float, profit_income: float,
     *     operating_expenses: float, net_profit: float, margin: float|null, cash_flow: float
     * }
     */
    public function forPeriod(int $companyId, CarbonInterface $from, CarbonInterface $to): array
    {
        $interestCollected = $this->interest->interestBetween($companyId, $from, $to);
        $otherProfitIncome = $this->otherProfitIncome($companyId, $from, $to);
        $profitIncome = $interestCollected + $otherProfitIncome;
        $operatingExpenses = $this->operatingExpenses($companyId, $from, $to);
        $netProfit = $profitIncome - $operatingExpenses;

        return [
            'interest_collected' => $interestCollected,
            'other_profit_income' => $otherProfitIncome,
            'profit_income' => $profitIncome,
            'operating_expenses' => $operatingExpenses,
            'net_profit' => $netProfit,
            'margin' => $profitIncome > 0.0 ? ($netProfit / $profitIncome) * 100 : null,
            'cash_flow' => $this->cashFlow($companyId, $from, $to),
        ];
    }

    /**
     * Categorias de `incomes` que aportan utilidad SIN duplicar lo derivado.
     * Se excluye `payment` (ya viene por derivacion) y `loan_payment_interest`
     * (lo duplicaria el dia que alguien empiece a escribirlo).
     *
     * @return list<string>
     */
    public static function profitIncomeCategories(): array
    {
        $excluded = [IncomeCategory::PAYMENT, IncomeCategory::LOAN_PAYMENT_INTEREST];

        return array_values(array_map(
            static fn (IncomeCategory $c): string => $c->value,
            array_filter(
                IncomeCategory::cases(),
                static fn (IncomeCategory $c): bool => $c->affectsProfit() && ! in_array($c, $excluded, true),
            )
        ));
    }

    /**
     * Categorias de gasto que son COSTO de operar.
     *
     * Se decide por el ENUM, nunca por la columna `affects_profit`: en produccion
     * esa columna esta mal poblada (~$84M de desembolsos y $10,6M de retiros
     * marcados en 1). Un desembolso es capital colocado y un retiro es reparto
     * de utilidad — restarlos aqui convertiria un negocio rentable en una
     * perdida ficticia, o contaria la utilidad dos veces.
     *
     * @return list<string>
     */
    public static function operatingExpenseCategories(): array
    {
        $excluded = [ExpenseCategory::PROFIT_WITHDRAWAL, ExpenseCategory::DIVIDEND_PAYMENT];

        return array_values(array_map(
            static fn (ExpenseCategory $c): string => $c->value,
            array_filter(
                ExpenseCategory::cases(),
                static fn (ExpenseCategory $c): bool => $c->affectsProfit() && ! in_array($c, $excluded, true),
            )
        ));
    }

    private function otherProfitIncome(int $companyId, CarbonInterface $from, CarbonInterface $to): float
    {
        return (float) Income::query()
            ->withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->whereIn('category', self::profitIncomeCategories())
            ->whereBetween('operation_date', [$from->toDateString(), $to->toDateString()])
            ->sum('amount');
    }

    private function operatingExpenses(int $companyId, CarbonInterface $from, CarbonInterface $to): float
    {
        return (float) Expense::query()
            ->withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->whereIn('category', self::operatingExpenseCategories())
            ->effective()
            ->whereBetween('operation_date', [$from->toDateString(), $to->toDateString()])
            ->sum('amount');
    }

    /**
     * Movimiento de efectivo, NO utilidad: aqui los desembolsos SI cuentan,
     * porque la plata efectivamente sale de la caja.
     */
    private function cashFlow(int $companyId, CarbonInterface $from, CarbonInterface $to): float
    {
        $in = (float) Income::query()
            ->withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->whereBetween('operation_date', [$from->toDateString(), $to->toDateString()])
            ->sum('amount');

        $out = (float) Expense::query()
            ->withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('affects_cash', true)
            ->effective()
            ->whereBetween('operation_date', [$from->toDateString(), $to->toDateString()])
            ->sum('amount');

        return $in - $out;
    }
}
