<?php

namespace App\Services;

use App\Models\NumberSequence;
use App\Support\PropertyDate;
use Illuminate\Support\Facades\DB;

class DocumentNumberService
{
    public function __construct(
        private readonly SettingsService $settings,
    ) {}

    public function next(string $type): string
    {
        return match ($type) {
            'quotation' => $this->nextDaily('quotation', 'quo'),
            'invoice' => $this->nextDaily('invoice', 'inv'),
            default => $this->nextYearly($type),
        };
    }

    /**
     * Invoice number when converting an accepted quotation:
     * inv-{quotation_number} e.g. inv-quo20260715-0001
     */
    public function invoiceFromQuotation(string $quotationNumber): string
    {
        $quotationNumber = trim($quotationNumber);

        if (str_starts_with(strtolower($quotationNumber), 'inv-')) {
            return $quotationNumber;
        }

        return 'inv-'.$quotationNumber;
    }

    /**
     * Quotation number when creating a quotation from an invoice:
     * quo-{invoice_number} e.g. quo-inv20260715-0001
     */
    public function quotationFromInvoice(string $invoiceNumber): string
    {
        $invoiceNumber = trim($invoiceNumber);

        if (str_starts_with(strtolower($invoiceNumber), 'quo-')) {
            return $invoiceNumber;
        }

        return 'quo-'.$invoiceNumber;
    }

    /**
     * Daily sequence: {prefix}YYYYMMDD-XXXX (resets each calendar day in property timezone).
     */
    public function nextDaily(string $type, string $literalPrefix): string
    {
        return DB::transaction(function () use ($type, $literalPrefix): string {
            $dayKey = PropertyDate::now()->format('Ymd');
            $period = (int) $dayKey;
            $padding = 4;

            $nextNumber = $this->allocate($type, $period);

            return sprintf(
                '%s%s-%s',
                strtolower($literalPrefix),
                $dayKey,
                str_pad((string) $nextNumber, $padding, '0', STR_PAD_LEFT),
            );
        });
    }

    /**
     * Yearly sequence: PREFIX-YYYY-####### (bookings, payments, etc.).
     */
    public function nextYearly(string $type): string
    {
        return DB::transaction(function () use ($type): string {
            $year = (int) PropertyDate::now()->format('Y');
            $prefix = $this->prefixFor($type);
            $padding = max(1, (int) $this->settings->get('number_padding', 6));

            $nextNumber = $this->allocate($type, $year);

            return sprintf(
                '%s-%d-%s',
                $prefix,
                $year,
                str_pad((string) $nextNumber, $padding, '0', STR_PAD_LEFT),
            );
        });
    }

    private function allocate(string $type, int $periodYearOrDay): int
    {
        $sequence = NumberSequence::query()
            ->where('type', $type)
            ->where('year', $periodYearOrDay)
            ->lockForUpdate()
            ->first();

        if ($sequence === null) {
            $sequence = NumberSequence::query()->create([
                'type' => $type,
                'year' => $periodYearOrDay,
                'last_number' => 0,
            ]);

            $sequence = NumberSequence::query()
                ->whereKey($sequence->id)
                ->lockForUpdate()
                ->firstOrFail();
        }

        $nextNumber = $sequence->last_number + 1;
        $sequence->update(['last_number' => $nextNumber]);

        return $nextNumber;
    }

    private function prefixFor(string $type): string
    {
        return match ($type) {
            'booking' => (string) $this->settings->get('booking_prefix', 'BKG'),
            'quotation' => (string) $this->settings->get('quotation_prefix', 'quo'),
            'invoice' => (string) $this->settings->get('invoice_prefix', 'inv'),
            'payment' => (string) $this->settings->get('payment_prefix', 'RCT'),
            default => strtoupper($type),
        };
    }
}
