<?php

namespace App\Services;

use App\Models\Expense;
use App\Models\Income;
use App\ValueObjects\Money;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;

/**
 * Servicio de flujo de caja.
 *
 * Usa Money Value Object con BC Math para precisión financiera exacta.
 */
class CashFlowService
{
    /**
     * Resuelve el company_id respetando multi-tenant.
     * - Si el usuario NO es super_admin → siempre su company_id.
     * - Si es super_admin → usa el que venga por parámetro (o el suyo si no se pasa).
     */
    protected function resolveCompanyId(?int $companyId = null): int
    {
        $user = Auth::user();

        if (! $user->isSuperAdmin()) {
            return (int) $user->company_id;
        }

        return (int) ($companyId ?? $user->company_id);
    }

    /**
     * Resumen de flujo de caja por rango de fechas (CUALQUIER rango).
     *
     * Retorna:
     * [
     *   'from'        => '2025-01-01',
     *   'to'          => '2025-01-31',
     *   'company_id'  => 1,
     *   'totals'      => [
     *       'incomes'  => 1000000.00,
     *       'expenses' => 500000.00,
     *       'net'      => 500000.00,
     *   ],
     *   'incomes_by_category'  => [...],
     *   'expenses_by_category' => [...],
     * ]
     */
    public function summarizeForRange(
        Carbon $from,
        Carbon $to,
        ?int $companyId = null,
    ): array {
        $companyId = $this->resolveCompanyId($companyId);

        // Normalizamos fechas al inicio/fin del día
        $from = $from->copy()->startOfDay();
        $to = $to->copy()->endOfDay();

        // INCOMES (entradas)
        $incomesQuery = Income::query()
            ->where('company_id', $companyId)
            ->whereNotNull('operation_date')
            ->whereBetween('operation_date', [$from->toDateString(), $to->toDateString()]);

        $totalIncomesRaw = $incomesQuery->clone()->sum('amount');
        $totalIncomes = Money::of($totalIncomesRaw ?? 0);

        $incomesByCategory = $incomesQuery->clone()
            ->selectRaw('category, SUM(amount) as total')
            ->groupBy('category')
            ->pluck('total', 'category')
            ->map(fn ($value) => Money::of($value ?? 0)->value())
            ->toArray();

        // EXPENSES (salidas) — solo egresos efectivos (aprobados o sin aprobación
        // requerida). Los pendientes/rechazados no descuentan caja.
        $expensesQuery = Expense::query()
            ->where('company_id', $companyId)
            ->effective()
            ->whereNotNull('operation_date')
            ->whereBetween('operation_date', [$from->toDateString(), $to->toDateString()]);

        $totalExpensesRaw = $expensesQuery->clone()->sum('amount');
        $totalExpenses = Money::of($totalExpensesRaw ?? 0);

        $expensesByCategory = $expensesQuery->clone()
            ->selectRaw('category, SUM(amount) as total')
            ->groupBy('category')
            ->pluck('total', 'category')
            ->map(fn ($value) => Money::of($value ?? 0)->value())
            ->toArray();

        $net = $totalIncomes->subtract($totalExpenses);

        return [
            'from' => $from->toDateString(),
            'to' => $to->toDateString(),
            'company_id' => $companyId,
            'totals' => [
                'incomes' => $totalIncomes->value(),
                'expenses' => $totalExpenses->value(),
                'net' => $net->value(),
            ],
            'incomes_by_category' => $incomesByCategory,
            'expenses_by_category' => $expensesByCategory,
        ];
    }

    /**
     * Serie diaria de flujo de caja entre dos fechas:
     * [
     *   ['date' => '2025-01-01', 'incomes' => 100000, 'expenses' => 50000, 'net' => 50000],
     *   ...
     * ]
     */
    public function dailySeries(
        Carbon $from,
        Carbon $to,
        ?int $companyId = null,
    ): Collection {
        $companyId = $this->resolveCompanyId($companyId);

        $from = $from->copy()->startOfDay();
        $to = $to->copy()->endOfDay();

        // Incomes agrupados por día
        $incomes = Income::query()
            ->where('company_id', $companyId)
            ->whereNotNull('operation_date')
            ->whereBetween('operation_date', [$from->toDateString(), $to->toDateString()])
            ->selectRaw('DATE(operation_date) as date, SUM(amount) as total')
            ->groupBy('date')
            ->pluck('total', 'date')
            ->map(fn ($value) => Money::of($value ?? 0));

        // Expenses agrupados por día (solo egresos efectivos)
        $expenses = Expense::query()
            ->where('company_id', $companyId)
            ->effective()
            ->whereNotNull('operation_date')
            ->whereBetween('operation_date', [$from->toDateString(), $to->toDateString()])
            ->selectRaw('DATE(operation_date) as date, SUM(amount) as total')
            ->groupBy('date')
            ->pluck('total', 'date')
            ->map(fn ($value) => Money::of($value ?? 0));

        // Construimos rango día a día
        $cursor = $from->copy();
        $series = collect();

        while ($cursor->lessThanOrEqualTo($to)) {
            $dateKey = $cursor->toDateString();
            $dayIn = $incomes->get($dateKey, Money::zero());
            $dayOut = $expenses->get($dateKey, Money::zero());
            $dayNet = $dayIn->subtract($dayOut);

            $series->push([
                'date' => $dateKey,
                'incomes' => $dayIn->value(),
                'expenses' => $dayOut->value(),
                'net' => $dayNet->value(),
            ]);

            $cursor->addDay();
        }

        return $series;
    }

    /**
     * Serie mensual de flujo de caja entre dos fechas:
     * [
     *   ['month' => '2025-01', 'incomes' => 1000000, 'expenses' => 800000, 'net' => 200000],
     *   ...
     * ]
     */
    public function monthlySeries(
        Carbon $from,
        Carbon $to,
        ?int $companyId = null,
    ): Collection {
        $companyId = $this->resolveCompanyId($companyId);

        $from = $from->copy()->startOfMonth();
        $to = $to->copy()->endOfMonth();

        // Incomes agrupados por mes (YYYY-MM)
        $incomes = Income::query()
            ->where('company_id', $companyId)
            ->whereNotNull('operation_date')
            ->whereBetween('operation_date', [$from->toDateString(), $to->toDateString()])
            ->selectRaw('DATE_FORMAT(operation_date, "%Y-%m") as ym, SUM(amount) as total')
            ->groupBy('ym')
            ->pluck('total', 'ym')
            ->map(fn ($value) => Money::of($value ?? 0));

        // Expenses agrupados por mes (YYYY-MM) (solo egresos efectivos)
        $expenses = Expense::query()
            ->where('company_id', $companyId)
            ->effective()
            ->whereNotNull('operation_date')
            ->whereBetween('operation_date', [$from->toDateString(), $to->toDateString()])
            ->selectRaw('DATE_FORMAT(operation_date, "%Y-%m") as ym, SUM(amount) as total')
            ->groupBy('ym')
            ->pluck('total', 'ym')
            ->map(fn ($value) => Money::of($value ?? 0));

        // Construimos el rango mes a mes
        $cursor = $from->copy()->startOfMonth();
        $series = collect();

        while ($cursor->lessThanOrEqualTo($to)) {
            $key = $cursor->format('Y-m');
            $monthIn = $incomes->get($key, Money::zero());
            $monthOut = $expenses->get($key, Money::zero());
            $monthNet = $monthIn->subtract($monthOut);

            $series->push([
                'month' => $key,
                'incomes' => $monthIn->value(),
                'expenses' => $monthOut->value(),
                'net' => $monthNet->value(),
            ]);

            $cursor->addMonth();
        }

        return $series;
    }
}
