<?php

namespace App\Services\Admin\Finance;

use App\Concerns\HasRoleChecks;
use App\Enums\CashTransactionType;
use App\Enums\EmployeeAdvanceStatus;
use App\Enums\Role;
use App\Models\EmployeeAdvance;
use App\Models\EmployeeAdvancePayment;
use App\Services\Concerns\HandlesCashTransactions;
use App\Services\NotificationService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class EmployeeAdvanceService
{
    use HandlesCashTransactions, HasRoleChecks;

    public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
    {
        return EmployeeAdvance::query()
            ->select(['id', 'employee_id', 'amount', 'paid_amount', 'description', 'due_date', 'status', 'created_at'])
            ->with(['employee.user.userProfile', 'payments.paidBy.userProfile'])
            ->when(! self::hasAnyRole([Role::DEVELOPER, Role::OWNER, Role::DIREKTUR, Role::ADMIN_TOKO]), fn ($q) => $q->whereHas('employee', fn ($eq) => $eq->where('user_id', auth()->id())))
            ->when($filters['status'] ?? null, fn ($q) => $q->where('status', $filters['status']))
            ->when($search, fn ($q) => $q->where('description', 'like', "%{$search}%"))
            ->orderBy($sort, $direction)
            ->paginate($perPage);
    }

    public function store(array $data): EmployeeAdvance
    {
        $employee = auth()->user()->employee;

        if (! $employee) {
            throw ValidationException::withMessages([
                'amount' => 'Anda tidak terdaftar sebagai karyawan.',
            ]);
        }

        $employeeAdvance = EmployeeAdvance::create([
            'employee_id' => $employee->id,
            'amount' => $data['amount'],
            'description' => $data['description'],
            'due_date' => $data['due_date'],
            'status' => EmployeeAdvanceStatus::PENDING,
        ]);

        $employeeAdvance->load('employee.user');

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO],
            title: 'Kasbon Baru',
            body: 'Kasbon sebesar Rp '.number_format($data['amount'], 0, ',', '.')." dari {$employeeAdvance->employee->name} menunggu persetujuan".' oleh '.auth()->user()->full_name.'.',
            url: route('admin.finance.employee-advances.index'),
            additionalUser: $employeeAdvance->employee->user ?? null,
        );

        return $employeeAdvance;
    }

    public function update(EmployeeAdvance $employeeAdvance, array $data): EmployeeAdvance
    {
        if ($employeeAdvance->status === EmployeeAdvanceStatus::REPAID) {
            throw ValidationException::withMessages([
                'amount' => 'Kasbon yang sudah dikembalikan tidak dapat diedit.',
            ]);
        }

        if ($employeeAdvance->status === EmployeeAdvanceStatus::DISBURSED && $employeeAdvance->cash_transaction_id) {
            $oldAmount = $employeeAdvance->amount;
            $newAmount = $data['amount'];

            if ($oldAmount !== $newAmount) {
                $employeeAdvance = DB::transaction(function () use ($employeeAdvance, $data, $oldAmount, $newAmount) {
                    $this->creditCash(
                        $oldAmount,
                        'Pembatalan kasbon: '.$employeeAdvance->description,
                        CashTransactionType::DEPOSIT,
                    );

                    $cashTransaction = $this->debitCash(
                        $newAmount,
                        'Kasbon: '.$data['description'],
                        CashTransactionType::EMPLOYEE_ADVANCE,
                    );

                    $employeeAdvance->update([
                        'amount' => $newAmount,
                        'description' => $data['description'],
                        'due_date' => $data['due_date'],
                        'cash_transaction_id' => $cashTransaction->id,
                    ]);

                    return $employeeAdvance;
                });

                return $employeeAdvance;
            }
        }

        $employeeAdvance->update([
            'amount' => $data['amount'],
            'description' => $data['description'],
            'due_date' => $data['due_date'],
        ]);

        return $employeeAdvance;
    }

    public function destroy(EmployeeAdvance $employeeAdvance): bool
    {
        return DB::transaction(function () use ($employeeAdvance) {
            if ($employeeAdvance->status === EmployeeAdvanceStatus::DISBURSED && $employeeAdvance->cash_transaction_id) {
                $this->creditCash(
                    $employeeAdvance->amount,
                    'Pembatalan kasbon: '.$employeeAdvance->description,
                    CashTransactionType::DEPOSIT,
                );
                $employeeAdvance->cashTransaction()->delete();
            }

            if ($employeeAdvance->status === EmployeeAdvanceStatus::REPAID || $employeeAdvance->status === EmployeeAdvanceStatus::PARTIAL) {
                $this->creditCash(
                    $employeeAdvance->amount,
                    'Pembatalan kasbon: '.$employeeAdvance->description,
                    CashTransactionType::DEPOSIT,
                );

                $employeeAdvance->load('payments');
                foreach ($employeeAdvance->payments as $payment) {
                    if ($payment->cash_transaction_id) {
                        $this->debitCash(
                            $payment->amount,
                            'Pembatalan pembayaran kasbon: '.$employeeAdvance->description,
                            CashTransactionType::EXPENSE,
                        );
                        $payment->cashTransaction()->delete();
                    }
                }

                $employeeAdvance->payments()->delete();
            }

            return $employeeAdvance->delete();
        });
    }

    public function approve(EmployeeAdvance $employeeAdvance): EmployeeAdvance
    {
        $employeeAdvance = DB::transaction(function () use ($employeeAdvance) {
            $cashTransaction = $this->debitCash(
                $employeeAdvance->amount,
                'Kasbon: '.$employeeAdvance->description,
                CashTransactionType::EMPLOYEE_ADVANCE
            );

            $employeeAdvance->update([
                'cash_transaction_id' => $cashTransaction->id,
                'status' => EmployeeAdvanceStatus::DISBURSED,
                'verified_by_id' => auth()->id(),
                'verified_at' => now(),
            ]);

            return $employeeAdvance;
        });

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO],
            title: 'Kasbon Dikeluarkan',
            body: 'Kasbon sebesar Rp '.number_format($employeeAdvance->amount, 0, ',', '.').' telah disetujui dan dikeluarkan'.' oleh '.auth()->user()->full_name.'.',
            url: route('admin.finance.employee-advances.index'),
            additionalUser: $employeeAdvance->employee->user ?? null,
        );

        return $employeeAdvance;
    }

    public function pay(EmployeeAdvance $employeeAdvance, int $amount): EmployeeAdvance
    {
        $remaining = $employeeAdvance->amount - $employeeAdvance->paid_amount;

        if ($amount > $remaining) {
            throw ValidationException::withMessages([
                'amount' => 'Jumlah bayar melebihi sisa kasbon.',
            ]);
        }

        $employeeAdvance = DB::transaction(function () use ($employeeAdvance, $amount) {
            $cashTransaction = $this->creditCash(
                $amount,
                'Pembayaran kasbon: '.$employeeAdvance->description,
                CashTransactionType::DEPOSIT
            );

            EmployeeAdvancePayment::create([
                'employee_advance_id' => $employeeAdvance->id,
                'paid_by_id' => auth()->id(),
                'cash_transaction_id' => $cashTransaction->id,
                'amount' => $amount,
                'paid_at' => now(),
            ]);

            $newPaidAmount = $employeeAdvance->paid_amount + $amount;
            $isFullyPaid = $newPaidAmount >= $employeeAdvance->amount;

            $employeeAdvance->update([
                'paid_amount' => $newPaidAmount,
                'status' => $isFullyPaid ? EmployeeAdvanceStatus::REPAID : EmployeeAdvanceStatus::PARTIAL,
                'paid_by_id' => $isFullyPaid ? auth()->id() : $employeeAdvance->paid_by_id,
                'paid_at' => $isFullyPaid ? now() : $employeeAdvance->paid_at,
            ]);

            return $employeeAdvance;
        });

        $notificationBody = $employeeAdvance->status === EmployeeAdvanceStatus::REPAID
            ? 'Kasbon sebesar Rp '.number_format($employeeAdvance->amount, 0, ',', '.').' telah dikembalikan lunas'.' oleh '.auth()->user()->full_name.'.'
            : 'Pembayaran kasbon sebesar Rp '.number_format($amount, 0, ',', '.').' oleh '.auth()->user()->full_name.'. Sisa: Rp '.number_format($employeeAdvance->amount - $employeeAdvance->paid_amount, 0, ',', '.').'.';

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO],
            title: $employeeAdvance->status === EmployeeAdvanceStatus::REPAID ? 'Kasbon Dikembalikan' : 'Pembayaran Kasbon',
            body: $notificationBody,
            url: route('admin.finance.employee-advances.index'),
            additionalUser: $employeeAdvance->employee->user ?? null,
        );

        return $employeeAdvance;
    }
}
