<?php

namespace App\Domain\Rent\Services;

use App\Domain\Rent\Support\PeriodCalculator;
use App\Models\Invoice;
use App\Models\InvoiceItem;
use App\Models\Lease;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;

class InvoiceGeneratorService
{
    public function generateForDate(?Carbon $runDate = null): array
    {
        $runDate = $runDate ?: now();

        $leases = Lease::query()
            ->with(['tenant', 'unit'])
            ->where('status', 'active')
            ->get();

        $created = 0;
        $skipped = 0;

        DB::transaction(function () use ($leases, $runDate, &$created, &$skipped) {
            $groups = [];

            foreach ($leases as $lease) {
                // ROLLING period by lease.start_date
                if ($lease->billing_cycle === 'monthly') {
                    [$ps, $pe] = PeriodCalculator::monthlyRolling($runDate, $lease->start_date);
                    $ptype = 'monthly';
                } else {
                    [$ps, $pe] = PeriodCalculator::yearlyRolling($runDate, $lease->start_date);
                    $ptype = 'yearly';
                }

                // Lease harus overlap periode
                if (! $this->leaseOverlapsPeriod($lease, $ps, $pe)) {
                    continue;
                }

                $key = implode('|', [
                    $lease->tenant_id,
                    $ptype,
                    $ps->toDateString(),
                    $pe->toDateString(),
                ]);

                $groups[$key]['tenant_id'] = $lease->tenant_id;
                $groups[$key]['period_type'] = $ptype;
                $groups[$key]['period_start'] = $ps->toDateString();
                $groups[$key]['period_end'] = $pe->toDateString();
                $groups[$key]['leases'][] = $lease;
            }

            foreach ($groups as $g) {
                // cegah duplikasi (abaikan invoice void)
                $exists = Invoice::query()
                    ->where([
                        'tenant_id'    => $g['tenant_id'],
                        'period_type'  => $g['period_type'],
                        'period_start' => $g['period_start'],
                        'period_end'   => $g['period_end'],
                    ])
                    ->where('status', '!=', 'void')
                    ->exists();

                if ($exists) {
                    $skipped++;
                    continue;
                }

                $ps = Carbon::parse($g['period_start'])->startOfDay();
                $pe = Carbon::parse($g['period_end'])->endOfDay();

                // rolling: due date = period_start (anniversary)
                $due = $ps->copy()->startOfDay();

                $invoice = Invoice::create([
                    'tenant_id'    => $g['tenant_id'],
                    'period_type'  => $g['period_type'],
                    'period_start' => $g['period_start'],
                    'period_end'   => $g['period_end'],
                    'due_date'     => $due->toDateString(),
                    'issued_at'    => now(),
                    'status'       => 'unpaid',
                ]);

                foreach ($g['leases'] as $lease) {
                    // Clamp item range ke masa aktif lease (biar tidak menagih sebelum mulai / setelah selesai)
                    [$itemStart, $itemEnd] = $this->clampToLeaseActiveRange($lease, $ps, $pe);

                    $amount = $this->computeLeaseAmountForPeriod($lease, $itemStart, $itemEnd);

                    if ($amount <= 0) {
                        continue;
                    }

                    InvoiceItem::create([
                        'invoice_id'   => $invoice->id,
                        'lease_id'     => $lease->id,
                        'unit_id'      => $lease->unit_id,
                        'description'  => $this->makeDescription($lease, $g['period_type'], $itemStart, $itemEnd),
                        'amount'       => $amount,
                    ]);
                }

                $invoice->recalcTotals();
                $invoice->save();

                $created++;
            }
        });

        return compact('created', 'skipped');
    }

    private function leaseOverlapsPeriod(Lease $lease, Carbon $ps, Carbon $pe): bool
    {
        $leaseStart = $lease->start_date->copy()->startOfDay();
        $leaseEnd   = $lease->end_date ? $lease->end_date->copy()->endOfDay() : null;

        if ($leaseStart->gt($pe)) return false;
        if ($leaseEnd && $leaseEnd->lt($ps)) return false;

        return true;
    }

    /**
     * Ambil range item yang benar-benar aktif untuk lease di periode invoice:
     * itemStart = max(leaseStart, periodStart)
     * itemEnd   = min(leaseEnd, periodEnd) (jika end_date ada)
     */
    private function clampToLeaseActiveRange(Lease $lease, Carbon $ps, Carbon $pe): array
    {
        $leaseStart = $lease->start_date->copy()->startOfDay();
        $leaseEnd   = $lease->end_date ? $lease->end_date->copy()->endOfDay() : null;

        $itemStart = $leaseStart->gt($ps) ? $leaseStart : $ps->copy()->startOfDay();
        $itemEnd   = $leaseEnd && $leaseEnd->lt($pe) ? $leaseEnd : $pe->copy()->endOfDay();

        return [$itemStart, $itemEnd];
    }

    /**
     * Hitung nominal per periode.
     * - Kalau prorate_enabled = false => full price_amount
     * - Kalau true => prorate berdasarkan hari aktif vs total hari periode invoice (RUPIAH BULAT)
     */
    private function computeLeaseAmountForPeriod(Lease $lease, Carbon $itemStart, Carbon $itemEnd): int
    {
        $full = (int) round((float) $lease->price_amount);

        if (! $lease->prorate_enabled) {
            return $full;
        }

        // total hari di periode item (inclusive)
        $daysInPeriod = $itemStart->copy()->startOfDay()->diffInDays($itemEnd->copy()->endOfDay()) + 1;
        if ($daysInPeriod <= 0) return 0;

        // Karena itemStart/itemEnd sudah clamp, activeDays = daysInPeriod
        // (prorate di sini relevan kalau kamu nanti ubah menjadi hitung relatif ke full period invoice,
        // tapi untuk sekarang: tetap prorate di range item, aman.)
        $activeDays = $daysInPeriod;

        // Kalau kamu ingin prorate dibanding FULL PERIOD invoice (ps-pe), ubah di sini.
        // Untuk rolling, biasanya cukup prorate jika lease berakhir di tengah periode:
        // amount = full * (activeDays / fullDaysOfInvoicePeriod)
        // Namun karena item range sudah clamp, gunakan perbandingan "bulan/tahun" rolling:
        // Kita ambil pembagi berdasarkan jenis billing_cycle:
        $fullDays = $lease->billing_cycle === 'yearly'
            ? $lease->start_date->copy()->addYearNoOverflow()->subDay()->diffInDays($lease->start_date) + 1
            : $lease->start_date->copy()->addMonthNoOverflow()->subDay()->diffInDays($lease->start_date) + 1;

        $raw = $full * ($activeDays / max(1, $fullDays));

        return (int) round($raw, 0); // rupiah bulat
    }

    private function makeDescription(Lease $lease, string $ptype, Carbon $itemStart, Carbon $itemEnd): string
    {
        $unit = $lease->unit->code . ' - ' . $lease->unit->name;

        $ps = $itemStart->toDateString();
        $pe = $itemEnd->toDateString();

        if ($ptype === 'monthly') {
            return "Sewa Bulanan {$unit} ({$ps} s/d {$pe})";
        }

        return "Sewa Tahunan {$unit} ({$ps} s/d {$pe})";
    }
    public function generateForTenantForDate(int $tenantId, ?Carbon $runDate = null): array
    {
        $runDate = $runDate ?: now();

        $leases = Lease::query()
            ->with(['tenant','unit'])
            ->where('status','active')
            ->where('tenant_id', $tenantId)
            ->get();

        $created = 0;
        $skipped = 0;

        DB::transaction(function () use ($leases, $runDate, &$created, &$skipped) {
            $groups = [];

            foreach ($leases as $lease) {
                if ($lease->billing_cycle === 'monthly') {
                    [$ps, $pe] = PeriodCalculator::monthlyRolling($runDate, $lease->start_date);
                    $ptype = 'monthly';
                } else {
                    [$ps, $pe] = PeriodCalculator::yearlyRolling($runDate, $lease->start_date);
                    $ptype = 'yearly';
                }

                if (! $this->leaseOverlapsPeriod($lease, $ps, $pe)) {
                    continue;
                }

                $key = implode('|', [
                    $lease->tenant_id,
                    $ptype,
                    $ps->toDateString(),
                    $pe->toDateString(),
                ]);

                $groups[$key]['tenant_id']    = $lease->tenant_id;
                $groups[$key]['period_type']  = $ptype;
                $groups[$key]['period_start'] = $ps->toDateString();
                $groups[$key]['period_end']   = $pe->toDateString();
                $groups[$key]['leases'][]     = $lease;
            }

            foreach ($groups as $g) {
                $exists = Invoice::query()
                    ->where([
                        'tenant_id'    => $g['tenant_id'],
                        'period_type'  => $g['period_type'],
                        'period_start' => $g['period_start'],
                        'period_end'   => $g['period_end'],
                    ])
                    ->where('status','!=','void')
                    ->exists();

                if ($exists) { $skipped++; continue; }

                $ps = Carbon::parse($g['period_start'])->startOfDay();
                $pe = Carbon::parse($g['period_end'])->endOfDay();
                $due = $ps->copy()->startOfDay();

                $invoice = Invoice::create([
                    'tenant_id'    => $g['tenant_id'],
                    'period_type'  => $g['period_type'],
                    'period_start' => $g['period_start'],
                    'period_end'   => $g['period_end'],
                    'due_date'     => $due->toDateString(),
                    'issued_at'    => now(),
                    'status'       => 'unpaid',
                ]);

                foreach ($g['leases'] as $lease) {
                    [$itemStart, $itemEnd] = $this->clampToLeaseActiveRange($lease, $ps, $pe);
                    $amount = $this->computeLeaseAmountForPeriod($lease, $itemStart, $itemEnd);
                    if ($amount <= 0) continue;

                    InvoiceItem::create([
                        'invoice_id'  => $invoice->id,
                        'lease_id'    => $lease->id,
                        'unit_id'     => $lease->unit_id,
                        'description' => $this->makeDescription($lease, $g['period_type'], $itemStart, $itemEnd),
                        'amount'      => $amount,
                    ]);
                }

                $invoice->recalcTotals();
                $invoice->save();

                $created++;
            }
        });

        return compact('created','skipped');
    }

}
