<?php

namespace App\Services\Finance;

use App\Models\InvoiceInstallment;
use App\Models\InvoiceInstallmentPlan;
use App\Models\PaymentInstallmentAllocation;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class InstallmentPlanService
{
    public function list(array $filters): array
    {
        $q = InvoiceInstallmentPlan::query();
        foreach (['invoice_id','parent_id','school_year','status'] as $field) {
            if (!empty($filters[$field])) $q->where($field, $filters[$field]);
        }
        return ['rows' => $q->orderByDesc('id')->paginate((int)($filters['per_page'] ?? 25))];
    }

    public function createForInvoice(int $invoiceId, array $payload, ?int $actorId): array
    {
        return DB::transaction(function () use ($invoiceId, $payload, $actorId) {
            $invoice = DB::table('invoices')->where('id', $invoiceId)->first();
            abort_if(!$invoice, 404, 'Invoice not found.');
            $total = (float) ($payload['total_amount'] ?? ($invoice->balance ?? $invoice->total_amount ?? 0));
            $count = (int) $payload['number_of_installments'];
            $plan = InvoiceInstallmentPlan::query()->create([
                'invoice_id' => $invoiceId,
                'parent_id' => $invoice->parent_id,
                'school_year' => $invoice->school_year ?? null,
                'plan_name' => $payload['plan_name'] ?? ('Invoice #' . $invoiceId . ' installment plan'),
                'total_amount' => $total,
                'number_of_installments' => $count,
                'status' => 'draft',
                'created_by' => $actorId,
            ]);

            $firstDue = Carbon::parse($payload['first_due_date']);
            $interval = (int) ($payload['interval_months'] ?? 1);
            $base = floor(($total / $count) * 100) / 100;
            $allocated = 0.0;
            for ($i = 1; $i <= $count; $i++) {
                $amount = $i === $count ? round($total - $allocated, 2) : $base;
                $allocated += $amount;
                InvoiceInstallment::query()->create([
                    'installment_plan_id' => $plan->id,
                    'invoice_id' => $invoiceId,
                    'sequence' => $i,
                    'due_date' => $firstDue->copy()->addMonths(($i - 1) * $interval)->toDateString(),
                    'amount_due' => $amount,
                    'amount_paid' => 0,
                    'balance' => $amount,
                    'status' => 'pending',
                ]);
            }

            return $this->show($plan->id);
        });
    }

    public function show(int $id): array
    {
        $plan = InvoiceInstallmentPlan::query()->findOrFail($id)->toArray();
        $plan['installments'] = InvoiceInstallment::query()->where('installment_plan_id', $id)->orderBy('sequence')->get()->toArray();
        return $plan;
    }

    public function activate(int $id, ?int $actorId): array
    {
        $plan = InvoiceInstallmentPlan::query()->findOrFail($id);
        $plan->fill(['status' => 'active', 'approved_by' => $actorId])->save();
        InvoiceInstallment::query()->where('installment_plan_id', $id)->where('status', 'pending')->update(['status' => 'due']);
        return $this->show($id);
    }

    public function cancel(int $id): array
    {
        InvoiceInstallmentPlan::query()->whereKey($id)->update(['status' => 'cancelled']);
        InvoiceInstallment::query()->where('installment_plan_id', $id)->whereNotIn('status', ['paid'])->update(['status' => 'cancelled']);
        return $this->show($id);
    }

    public function allocatePayment(int $paymentId, array $payload): array
    {
        return DB::transaction(function () use ($paymentId, $payload) {
            $payment = DB::table('payments')->where('id', $paymentId)->first();
            abort_if(!$payment, 404, 'Payment not found.');
            $allocations = $payload['installments'] ?? null;
            if (!$allocations) {
                $remaining = (float) ($payment->paid_amount ?? 0);
                $installments = InvoiceInstallment::query()->where('invoice_id', $payment->invoice_id)->where('balance', '>', 0)->orderBy('due_date')->lockForUpdate()->get();
                $allocations = [];
                foreach ($installments as $inst) {
                    if ($remaining <= 0) break;
                    $amount = min($remaining, (float) $inst->balance);
                    $allocations[] = ['installment_id' => $inst->id, 'amount' => $amount];
                    $remaining -= $amount;
                }
            }

            $created = [];
            foreach ($allocations as $a) {
                $inst = InvoiceInstallment::query()->lockForUpdate()->findOrFail($a['installment_id']);
                $amount = min((float) $a['amount'], (float) $inst->balance);
                if ($amount <= 0) continue;
                $created[] = PaymentInstallmentAllocation::query()->create([
                    'payment_id' => $paymentId,
                    'invoice_id' => $inst->invoice_id,
                    'installment_id' => $inst->id,
                    'amount' => $amount,
                ])->toArray();
                $inst->amount_paid = (float) $inst->amount_paid + $amount;
                $inst->balance = max(0, (float) $inst->amount_due - (float) $inst->amount_paid);
                $inst->status = $inst->balance <= 0 ? 'paid' : 'partially_paid';
                $inst->paid_at = $inst->balance <= 0 ? now() : null;
                $inst->save();
            }
            return ['allocations' => $created];
        });
    }

    public function due(bool $overdue = false): array
    {
        $q = InvoiceInstallment::query()->where('balance', '>', 0);
        $overdue ? $q->whereDate('due_date', '<', now()->toDateString()) : $q->whereDate('due_date', '>=', now()->toDateString());
        return ['rows' => $q->orderBy('due_date')->get()->toArray()];
    }

    public function markOverdue(): int
    {
        return InvoiceInstallment::query()->where('balance', '>', 0)->whereDate('due_date', '<', now()->toDateString())->whereNotIn('status', ['paid','cancelled','waived'])->update(['status' => 'overdue']);
    }
}
