<?php

namespace App\Services\Finance;

use App\Enums\EmployeeAdvanceStatus;
use App\Enums\Permission;
use App\Enums\Role;
use App\Models\EmployeeAdvance;
use App\Models\EmployeeAdvancePayment;
use App\Models\User;
use App\Services\Concerns\ResolvesAuthenticatedEmployee;
use App\Services\System\PushNotificationService;
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 EmployeeAdvanceService
{
    use ResolvesAuthenticatedEmployee;

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

    public function outstandingSummary(?User $user = null): array
    {
        $query = EmployeeAdvance::query()
            ->whereIn('status', [EmployeeAdvanceStatus::APPROVED, EmployeeAdvanceStatus::PARTIALLY_PAID])
            ->when(! $user->hasAnyRole([Role::OWNER->value, Role::DEVELOPER->value, Role::DIREKTUR->value]), fn (Builder $query) => $query->where('employee_id', $user->employee?->id ?? -1));

        $outstandingAmount = (int) $query->selectRaw('SUM(amount - paid_amount) as remaining')->value('remaining');

        $outstandingCount = $query->count();

        return [
            'outstanding_amount' => $outstandingAmount,
            'outstanding_amount_formatted' => 'Rp '.number_format($outstandingAmount, 0, ',', '.'),
            'outstanding_count' => $outstandingCount,
        ];
    }

    public function paginateForIndex(array $tableQuery, User $user, string $status = ''): LengthAwarePaginator
    {
        $query = EmployeeAdvance::query()
            ->with(['employee.user.profile', 'rejection', 'payments'])
            ->when(! $user->hasAnyRole([Role::OWNER->value, Role::DEVELOPER->value, Role::DIREKTUR->value]), function (Builder $query) use ($user): void {
                $employeeId = $user->employee?->id ?? -1;
                $query->where('employee_id', $employeeId);
            })
            ->when($status !== '', fn (Builder $query) => $query->where('status', $status))
            ->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}%")
                        ->orWhereHas('employee.user.profile', fn (Builder $query) => $query->where('full_name', 'like', "%{$search}%"))
                        ->orWhereHas('employee.user', fn (Builder $query) => $query->where('username', 'like', "%{$search}%"))
                        ->orWhereHas('rejection', fn (Builder $query) => $query->where('reason', 'like', "%{$search}%"));
                });
            });

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

        return $query
            ->paginate(25)
            ->withQueryString();
    }

    public function create(array $validated, User $user): void
    {
        $employee = $this->resolveAuthenticatedEmployee($user);

        try {
            $employeeAdvance = DB::transaction(function () use ($validated, $employee) {
                return EmployeeAdvance::create([
                    'employee_id' => $employee->id,
                    'amount' => (int) $validated['amount'],
                    'description' => $validated['description'],
                    'due_date' => $validated['due_date'],
                    'status' => EmployeeAdvanceStatus::PENDING,
                ]);
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal membuat pengajuan kasbon: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

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

        $this->pushNotificationService->sendToRoles(
            '💰 Pengajuan Kasbon Baru',
            "Karyawan {$user->profil?->full_name} mengajukan kasbon sebesar {$employeeAdvance->amount_formatted} dengan keterangan: {$employeeAdvance->description}.",
            ['owner', 'developer'],
            route('admin.finance.employee_advances.index'),
        );
    }

    public function update(EmployeeAdvance $employeeAdvance, array $validated, User $user): void
    {
        try {
            DB::transaction(function () use ($employeeAdvance, $validated): void {
                $employeeAdvance->update([
                    'amount' => (int) $validated['amount'],
                    'description' => $validated['description'],
                    'due_date' => $validated['due_date'],
                ]);
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal memperbarui kasbon: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

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

        $this->pushNotificationService->sendToRoles(
            '✏️ Kasbon Diperbarui',
            "Kasbon sebesar {$employeeAdvance->amount_formatted} telah diperbarui oleh {$user->profile?->full_name}.",
            ['owner', 'developer'],
            route('admin.finance.employee_advances.index'),
        );
    }

    public function delete(EmployeeAdvance $employeeAdvance, User $user): void
    {
        $amount = $employeeAdvance->amount_formatted;
        $description = $employeeAdvance->description;

        $employeeAdvance->delete();

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

    public function approve(EmployeeAdvance $employeeAdvance, User $user): void
    {
        try {
            DB::transaction(function () use ($employeeAdvance, $user): void {
                $employeeAdvance->loadMissing('employee.user.profile');

                $description = sprintf(
                    'Pencairan kasbon: %s',
                    $employeeAdvance->employeeName,
                );

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

                $employeeAdvance->update([
                    'cash_transaction_id' => $cashTransaction->id,
                    'status' => EmployeeAdvanceStatus::APPROVED,
                    'verified_at' => now(),
                    'verified_by_id' => $user->id,
                ]);
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal menyetujui kasbon: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

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

        $employeeAdvance->loadMissing('employee.user');
        if ($employeeAdvance->employee?->user_id) {
            $this->pushNotificationService->sendToUser(
                '💰 Kasbon Disetujui',
                "Pengajuan kasbon Anda sebesar {$employeeAdvance->amount_formatted} telah disetujui.",
                $employeeAdvance->employee->user_id,
                route('admin.finance.employee_advances.index'),
            );
        }
    }

    public function reject(EmployeeAdvance $employeeAdvance, string $reason, User $user): void
    {
        try {
            DB::transaction(function () use ($employeeAdvance, $user, $reason): void {
                $employeeAdvance->update([
                    'status' => EmployeeAdvanceStatus::REJECTED,
                    'verified_at' => now(),
                    'verified_by_id' => $user->id,
                ]);

                $employeeAdvance->rejection()->create([
                    'reason' => $reason,
                    'rejected_by_id' => $user->id,
                ]);
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal menolak kasbon: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

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

        $employeeAdvance->loadMissing('employee.user');
        if ($employeeAdvance->employee?->user_id) {
            $this->pushNotificationService->sendToUser(
                '💰 Kasbon Ditolak',
                "Pengajuan kasbon Anda sebesar {$employeeAdvance->amount_formatted} ditolak dengan alasan: {$reason}.",
                $employeeAdvance->employee->user_id,
                route('admin.finance.employee_advances.index'),
            );
        }
    }

    public function pay(EmployeeAdvance $employeeAdvance, User $user, ?int $payAmount = null): void
    {
        try {
            DB::transaction(function () use ($employeeAdvance, $user, $payAmount): void {
                $employeeAdvance->loadMissing('employee.user.profile');

                $remaining = $employeeAdvance->amount - $employeeAdvance->paid_amount;
                $amountToPay = $payAmount !== null ? min($payAmount, $remaining) : $remaining;
                $isFullPayment = ($employeeAdvance->paid_amount + $amountToPay) >= $employeeAdvance->amount;

                $description = sprintf(
                    '%s kasbon: %s',
                    $isFullPayment ? 'Pelunasan' : 'Pembayaran sebagian kasbon',
                    $employeeAdvance->employeeName,
                );

                $cashTransaction = $this->cashService->recordIncoming(
                    $employeeAdvance,
                    $amountToPay,
                    $description,
                    $user,
                );

                $newPaidAmount = $employeeAdvance->paid_amount + $amountToPay;

                $employeeAdvance->update([
                    'paid_amount' => $newPaidAmount,
                    'repayment_cash_transaction_id' => $isFullPayment ? $cashTransaction->id : $employeeAdvance->repayment_cash_transaction_id,
                    'paid_at' => $isFullPayment ? now() : $employeeAdvance->paid_at,
                    'paid_by_id' => $isFullPayment ? $user->id : $employeeAdvance->paid_by_id,
                    'status' => $isFullPayment ? EmployeeAdvanceStatus::PAID : EmployeeAdvanceStatus::PARTIALLY_PAID,
                ]);

                EmployeeAdvancePayment::create([
                    'employee_advance_id' => $employeeAdvance->id,
                    'paid_by_id' => $user->id,
                    'cash_transaction_id' => $cashTransaction->id,
                    'amount' => $amountToPay,
                    'description' => $description,
                    'paid_at' => now(),
                ]);
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal melunasi kasbon: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

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

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

            return;
        }

        $query->latest();
    }

    public function canSubmit(User $user): bool
    {
        return $user->can(Permission::EMPLOYEE_ADVANCES_CREATE->value)
            && ! $user->can(Permission::EMPLOYEE_ADVANCES_VERIFY->value)
            && $user->employee !== null;
    }

    public function indexPageData(array $tableQuery, User $user, string $status = ''): array
    {
        return [
            'employeeAdvances' => $this->paginateForIndex($tableQuery, $user, $status),
            'summary' => $this->outstandingSummary($user),
            'authEmployeeId' => $user->employee?->id,
            'canSubmit' => $this->canSubmit($user),
        ];
    }
}
