<?php

namespace App\Services\Refunds;

use App\Models\Invoice;
use App\Models\Refund;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;

class RefundPayoutService
{
    public function recordPayment(int $refundId, array $payload, int $actorId): array
    {
        $refund = Refund::query()->find($refundId);
        if (!$refund) {
            return ['ok' => false, 'message' => 'Refund not found.'];
        }

        if (!in_array($refund->status, [Refund::STATUS_APPROVED, Refund::STATUS_PARTIAL], true)) {
            return ['ok' => false, 'message' => 'Refund must be Approved or Partial to pay.'];
        }

        $newPaidAmount = (float) ($payload['paid_amount'] ?? 0);
        if ($newPaidAmount <= 0) {
            return ['ok' => false, 'message' => 'Valid paid amount is required.'];
        }

        $oldPaid = (float) ($refund->refund_paid_amount ?? 0);
        $target = (float) ($refund->refund_amount ?? 0);
        $total = $oldPaid + $newPaidAmount;

        if ($total > $target + 0.0001) {
            return ['ok' => false, 'message' => 'Total paid cannot exceed refund amount.'];
        }

        $refundMethod = (string) ($payload['payment_method'] ?? '');
        $checkNumber = $payload['check_number'] ?? null;
        $checkFileName = $refund->check_file;

        if ($refundMethod === 'Check' && ($payload['check_file'] ?? null) instanceof UploadedFile) {
            $file = $payload['check_file'];
            if ($file->isValid()) {
                $checkFileName = $file->hashName();
                $file->storeAs('checks', $checkFileName);
            }
        }

        $newStatus = ($total < $target) ? Refund::STATUS_PARTIAL : Refund::STATUS_PAID;

        return DB::transaction(function () use (
            $refund,
            $total,
            $newStatus,
            $refundMethod,
            $checkNumber,
            $checkFileName,
            $actorId
        ) {
            $assignInvoiceId = $refund->invoice_id ?: $this->assignInvoiceForRefund($refund);

            $refund->update([
                'refund_paid_amount' => $total,
                'status' => $newStatus,
                'refunded_at' => utc_now(),
                'updated_at' => utc_now(),
                'updated_by' => $actorId,
                'refund_method' => $refundMethod,
                'check_nbr' => $checkNumber,
                'check_file' => $checkFileName,
                'invoice_id' => $assignInvoiceId,
            ]);

            return ['ok' => true];
        });
    }

    private function assignInvoiceForRefund(Refund $refund): ?int
    {
        $parentId = (int) ($refund->parent_id ?? 0);
        $schoolYear = (string) ($refund->school_year ?? '');
        if ($parentId <= 0 || $schoolYear === '') {
            return null;
        }

        try {
            $invRows = Invoice::query()
                ->select('id', 'total_amount')
                ->where('parent_id', $parentId)
                ->where('school_year', $schoolYear)
                ->get()
                ->map(fn ($row) => (array) $row)
                ->all();

            if (empty($invRows)) {
                return null;
            }

            $invoiceIds = array_map(static fn ($r) => (int) $r['id'], $invRows);

            $paidBy = $this->sumPaymentsByInvoice($invoiceIds);
            $discBy = $this->sumDiscountsByInvoice($invoiceIds);

            $minRaw = 0.0;
            $minId = null;
            foreach ($invRows as $row) {
                $iid = (int) $row['id'];
                $raw = (float) ($row['total_amount'] ?? 0) - (float) ($discBy[$iid] ?? 0) - (float) ($paidBy[$iid] ?? 0);
                if ($raw < $minRaw) {
                    $minRaw = $raw;
                    $minId = $iid;
                }
            }

            if ($minId !== null) {
                return $minId;
            }

            $latest = Invoice::query()
                ->where('parent_id', $parentId)
                ->where('school_year', $schoolYear)
                ->orderByDesc('created_at')
                ->first();

            return $latest ? (int) $latest->id : null;
        } catch (\Throwable $e) {
            Log::warning('Refund invoice assignment failed.', ['error' => $e->getMessage()]);
            return null;
        }
    }

    private function sumPaymentsByInvoice(array $invoiceIds): array
    {
        $query = DB::table('payments')
            ->select('invoice_id', DB::raw('COALESCE(SUM(paid_amount),0) AS total_paid'))
            ->whereIn('invoice_id', $invoiceIds)
            ->groupBy('invoice_id');

        if (Schema::hasColumn('payments', 'status')) {
            $query->where(function ($q) {
                $q->whereNotIn('status', [
                    'void', 'voided', 'refunded', 'failed', 'chargeback', 'declined', 'reversed', 'canceled', 'cancelled',
                ])->orWhereNull('status');
            });
        }
        if (Schema::hasColumn('payments', 'is_void')) {
            $query->where(function ($q) {
                $q->where('is_void', 0)->orWhereNull('is_void');
            });
        }

        $rows = $query->get();
        $paidBy = [];
        foreach ($rows as $row) {
            $paidBy[(int) $row->invoice_id] = (float) ($row->total_paid ?? 0);
        }

        return $paidBy;
    }

    private function sumDiscountsByInvoice(array $invoiceIds): array
    {
        $rows = DB::table('discount_usages')
            ->select('invoice_id', DB::raw('COALESCE(SUM(discount_amount),0) AS total_disc'))
            ->whereIn('invoice_id', $invoiceIds)
            ->groupBy('invoice_id')
            ->get();

        $discBy = [];
        foreach ($rows as $row) {
            $discBy[(int) $row->invoice_id] = (float) ($row->total_disc ?? 0);
        }

        return $discBy;
    }
}
