<?php

namespace App\Services;

use App\Enums\CashTransactionType;
use App\Enums\PaymentMethod;
use App\Models\CashDrawer;
use App\Models\CashTransaction;
use App\Models\DailyClosing;
use App\Models\Expense;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class CashDrawerService
{
    public function __construct(protected AuditLogService $audit)
    {
    }

    public function current(): ?CashDrawer
    {
        return CashDrawer::currentOpen();
    }

    public function open(User $user, string $openingBalance = '0.00', ?string $name = null): CashDrawer
    {
        if ($this->current()) {
            throw ValidationException::withMessages([
                'drawer' => __('messages.drawer_already_open'),
            ]);
        }

        $drawer = CashDrawer::query()->create([
            'name' => $name ?: __('messages.main_drawer'),
            'opened_on' => now()->toDateString(),
            'opening_balance' => $openingBalance,
            'expected_balance' => $openingBalance,
            'status' => 'open',
            'opened_by' => $user->id,
            'opened_at' => now(),
        ]);

        $this->audit->record('cash.opened', $drawer, [], $drawer->toArray(), $user->id);

        return $drawer;
    }

    public function requireOpen(?User $user = null): CashDrawer
    {
        $drawer = $this->current();
        if ($drawer) {
            return $drawer;
        }

        $user = $user ?? Auth::user();
        if (! $user) {
            throw ValidationException::withMessages([
                'drawer' => __('messages.no_open_drawer'),
            ]);
        }

        return $this->open($user, '0.00');
    }

    public function record(
        CashTransactionType $type,
        string $amount,
        PaymentMethod $method,
        User $user,
        ?Model $reference = null,
        ?string $description = null,
        bool $requireDrawer = true
    ): ?CashTransaction {
        if ($method !== PaymentMethod::Cash && ! in_array($type, [CashTransactionType::CashIn, CashTransactionType::CashOut], true)) {
            // Non-cash payments still create a transaction record against the open drawer for reporting.
        }

        $drawer = $requireDrawer ? $this->requireOpen($user) : $this->current();
        if (! $drawer) {
            return null;
        }

        if (! $drawer->isOpen()) {
            throw ValidationException::withMessages([
                'drawer' => __('messages.drawer_closed'),
            ]);
        }

        $direction = $type->direction();
        $txn = CashTransaction::query()->create([
            'cash_drawer_id' => $drawer->id,
            'type' => $type->value,
            'payment_method' => $method->value,
            'direction' => $direction,
            'amount' => $amount,
            'reference_type' => $reference ? $reference::class : null,
            'reference_id' => $reference?->getKey(),
            'description' => $description,
            'user_id' => $user->id,
            'date' => now()->toDateString(),
        ]);

        if ($method === PaymentMethod::Cash || in_array($type, [CashTransactionType::CashIn, CashTransactionType::CashOut], true)) {
            $expected = $drawer->expected_balance;
            $drawer->expected_balance = $direction === 'in'
                ? bcadd((string) $expected, $amount, 2)
                : bcsub((string) $expected, $amount, 2);
            $drawer->save();
        }

        return $txn;
    }

    public function addExpense(array $data, User $user): Expense
    {
        return DB::transaction(function () use ($data, $user) {
            $drawer = $this->requireOpen($user);
            $expense = Expense::query()->create([
                'cash_drawer_id' => $drawer->id,
                'amount' => $data['amount'],
                'category' => $data['category'] ?? null,
                'description' => $data['description'],
                'payment_method' => $data['payment_method'] ?? PaymentMethod::Cash->value,
                'date' => $data['date'] ?? now()->toDateString(),
                'user_id' => $user->id,
            ]);

            $this->record(
                CashTransactionType::Expense,
                (string) $expense->amount,
                PaymentMethod::from($expense->payment_method->value ?? $data['payment_method'] ?? 'cash'),
                $user,
                $expense,
                $expense->description
            );

            $this->audit->record('expense.created', $expense, [], $expense->toArray(), $user->id);

            return $expense;
        });
    }

    public function summary(?CashDrawer $drawer = null): array
    {
        $drawer ??= $this->current();
        if (! $drawer) {
            return [
                'opening_balance' => '0.00',
                'sales' => '0.00',
                'payments' => '0.00',
                'expenses' => '0.00',
                'refunds' => '0.00',
                'cash_in' => '0.00',
                'cash_out' => '0.00',
                'expected_balance' => '0.00',
                'actual_balance' => null,
                'difference' => null,
                'closing_balance' => null,
            ];
        }

        $txns = $drawer->transactions;
        $sum = function (string $type, ?string $direction = null) use ($txns) {
            return (string) $txns
                ->filter(fn ($t) => $t->type->value === $type && ($direction === null || $t->direction === $direction) && $t->payment_method === PaymentMethod::Cash)
                ->sum(fn ($t) => (float) $t->amount);
        };

        $sales = number_format((float) $sum(CashTransactionType::Sale->value), 2, '.', '');
        $payments = number_format((float) $sum(CashTransactionType::Payment->value), 2, '.', '');
        $expenses = number_format((float) $sum(CashTransactionType::Expense->value), 2, '.', '');
        $refunds = number_format((float) $sum(CashTransactionType::Refund->value), 2, '.', '');
        $cashIn = number_format((float) $sum(CashTransactionType::CashIn->value), 2, '.', '');
        $cashOut = number_format((float) $sum(CashTransactionType::CashOut->value), 2, '.', '');

        return [
            'opening_balance' => (string) $drawer->opening_balance,
            'sales' => $sales,
            'payments' => $payments,
            'expenses' => $expenses,
            'refunds' => $refunds,
            'cash_in' => $cashIn,
            'cash_out' => $cashOut,
            'expected_balance' => (string) $drawer->expected_balance,
            'actual_balance' => $drawer->actual_balance,
            'difference' => $drawer->difference,
            'closing_balance' => $drawer->status === 'closed' ? $drawer->actual_balance : null,
            'drawer' => $drawer,
        ];
    }

    public function close(User $user, string $actualCash, ?string $notes = null): DailyClosing
    {
        return DB::transaction(function () use ($user, $actualCash, $notes) {
            $drawer = $this->requireOpen($user);
            $summary = $this->summary($drawer);
            $expected = (string) $summary['expected_balance'];
            $difference = bcsub($actualCash, $expected, 2);

            $closing = DailyClosing::query()->create([
                'cash_drawer_id' => $drawer->id,
                'closing_date' => $drawer->opened_on,
                'opening_balance' => $summary['opening_balance'],
                'sales_total' => $summary['sales'],
                'payments_total' => $summary['payments'],
                'expenses_total' => $summary['expenses'],
                'refunds_total' => $summary['refunds'],
                'cash_in' => $summary['cash_in'],
                'cash_out' => $summary['cash_out'],
                'expected_cash' => $expected,
                'actual_cash' => $actualCash,
                'difference' => $difference,
                'notes' => $notes,
                'closed_by' => $user->id,
            ]);

            $drawer->update([
                'actual_balance' => $actualCash,
                'difference' => $difference,
                'status' => 'closed',
                'closed_by' => $user->id,
                'closed_at' => now(),
                'notes' => $notes,
            ]);

            $this->audit->record('cash.closed', $drawer, ['status' => 'open'], ['status' => 'closed', 'actual' => $actualCash], $user->id);

            return $closing;
        });
    }
}
