<?php

namespace App\Services\Finance;

use App\Models\Expense;
use App\Models\User;
use App\Services\Media\MediaService;
use App\Services\System\PushNotificationService;
use App\Support\Media\MediaPresenter;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;

class ExpenseService
{
    private const MAX_PHOTOS = 1;

    public function __construct(
        private readonly CashService $cashService,
        private readonly MediaService $mediaService,
        private readonly PushNotificationService $pushNotificationService,
    ) {}

    public function paginateForIndex(array $tableQuery): LengthAwarePaginator
    {
        $query = Expense::query()
            ->with(['createdBy.profile', 'media'])
            ->when($tableQuery['search'] !== '', function (Builder $query) use ($tableQuery): void {
                $search = $tableQuery['search'];
                $query->where(function (Builder $query) use ($search): void {
                    $query->where('description', 'like', "%{$search}%");
                });
            });

        $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);

        return $query
            ->paginate(25)
            ->withQueryString()
            ->through(function (Expense $expense) {
                $expense->setAttribute(
                    'photos',
                    MediaPresenter::collection($expense, 'photos'),
                );

                return $expense;
            });
    }

    public function create(array $validated, User $user): void
    {
        try {
            $expense = DB::transaction(function () use ($validated, $user): Expense {
                $amount = (int) $validated['amount'];
                $description = $validated['description'];

                $expense = Expense::create([
                    'amount' => $amount,
                    'description' => $description,
                    'created_by_id' => $user->id,
                ]);

                $cashTransaction = $this->cashService->recordOutgoing(
                    $expense,
                    $amount,
                    $description,
                    $user,
                );

                $expense->update([
                    'cash_transaction_id' => $cashTransaction->id,
                ]);

                $this->syncPhotos($expense, $validated);

                return $expense;
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal membuat pengeluaran: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

            throw ValidationException::withMessages([
                'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
            ]);
        }

        $this->pushNotificationService->sendToRoles(
            '💸 Pengeluaran Baru',
            "Pengeluaran baru sebesar {$expense->amount_formatted} dengan keterangan {$expense->description} telah dicatat oleh {$user->profile?->full_name}.",
            ['owner', 'developer'],
            route('admin.finance.expenses.index'),
        );
    }

    public function update(Expense $expense, array $validated): void
    {
        try {
            DB::transaction(function () use ($expense, $validated): void {
                $amount = (int) $validated['amount'];
                $description = $validated['description'];

                $expense->update([
                    'amount' => $amount,
                    'description' => $description,
                ]);

                if ($expense->cashTransaction) {
                    $this->cashService->updateReferencedTransaction(
                        $expense->cashTransaction,
                        $amount,
                        $description,
                    );
                }

                $this->syncPhotos($expense, $validated);
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal memperbarui pengeluaran: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

            throw ValidationException::withMessages([
                'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
            ]);
        }

        $this->pushNotificationService->sendToRoles(
            '✏️ Pengeluaran Diperbarui',
            "Pengeluaran dengan keterangan {$expense->description} diperbarui menjadi sebesar {$expense->amount_formatted}.",
            ['owner', 'developer'],
            route('admin.finance.expenses.index'),
        );
    }

    public function delete(Expense $expense): void
    {
        try {
            DB::transaction(function () use ($expense): void {
                if ($expense->cashTransaction) {
                    $this->cashService->deleteReferencedTransaction($expense->cashTransaction);
                }

                $expense->clearMediaCollection('photos');
                $expense->delete();
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal menghapus pengeluaran: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

            throw ValidationException::withMessages([
                'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
            ]);
        }

        $this->pushNotificationService->sendToRoles(
            '🗑️ Pengeluaran Dihapus',
            "Pengeluaran sebesar {$expense->amount_formatted} dengan keterangan {$expense->description} telah dihapus.",
            ['owner', 'developer'],
            route('admin.finance.expenses.index'),
        );
    }

    private function syncPhotos(Expense $expense, array $validated): void
    {
        $this->mediaService->syncCollection(
            $expense,
            'photos',
            $validated['photos'] ?? null,
            $validated['remove_media_ids'] ?? null,
            self::MAX_PHOTOS,
            required: true,
            errorKey: 'photos',
        );
    }

    private function applySorting(Builder $query, string $sort, string $direction): void
    {
        if (in_array($sort, ['created_at', 'amount', 'description'], true)) {
            $query->orderBy($sort, $direction);

            return;
        }

        $query->latest();
    }
}
