<?php

namespace App\Services;

use App\Models\ProfitCenter;
use App\Models\ProfitCenterSupplierSchedule;
use App\Models\Supplier;
use Illuminate\Support\Carbon;

class NextDeliveryDateCalculatorService extends ServiceBase
{
    public function __construct(
        protected DeliveryCalendarService $deliveryCalendarService,
        protected WorkCalendarService $workCalendarService,
    ) {
    }

    /**
     * Meghatározza a következő szállítási napot egy adott supplier–profitcenter párosnál,
     * a megrendelés leadásának dátuma és időpontja alapján.
     *
     * Ha a suppliernek van szállítási korlátozása (hasDeliveryConstraint), figyelembe veszi:
     *   - orderCutOffTime: leadási határidő (óra), ha előtte adjuk le, a nap beleszámít az átfutásba
     *   - deliveryLeadTime: szállítási átfutási idő (óra → munkanapban számítva)
     *
     * @param  int     $supplierId
     * @param  int     $profitCenterId
     * @param  Carbon  $orderDateTime  A leadás dátuma és időpontja
     * @return Carbon|null             A következő lehetséges szállítási nap
     */
    public function calculate(
        int $supplierId,
        int $profitCenterId,
        Carbon $orderDateTime
    ): ?Carbon {
        $assignment = ProfitCenterSupplierSchedule::where('supplier_id', $supplierId)
            ->where('profit_center_id', $profitCenterId)
            ->with('deliverySchedule')
            ->first();

        if (! $assignment?->deliverySchedule) {
            return null;
        }

        $supplier = Supplier::find($supplierId);
        $pc = ProfitCenter::find($profitCenterId);

        if (! $supplier || ! $pc) {
            return null;
        }

        $leadDays = 0;
        $startDay = $orderDateTime->copy()->startOfDay();

        if ($supplier->hasDeliveryConstraint) {
            $cutOffHour = (int) $supplier->orderCutOffTime;
            $leadDays = (int) ($supplier->deliveryLeadTime / 24);

            // Ha a leadási határidő UTÁN vagyunk, holnap az első számítandó munkanap
            if ($orderDateTime->hour >= $cutOffHour) {
                $startDay->addDay();
            }
        }

        return $this->findNextDeliveryDay($startDay, $supplier, $pc, $leadDays);
    }

    /**
     * Munkanapokban számol előre ($leadDays darabot), majd az első olyan napot adja vissza,
     * amelyik szállítási nap is.
     */
    private function findNextDeliveryDay(
        Carbon $candidate,
        Supplier $supplier,
        ProfitCenter $pc,
        int $leadDays,
    ): ?Carbon {
        $workingDaysCounted = 0;

        // Max. 365 nap iteráció (végtelen ciklus-védelem)
        for ($i = 0; $i < 365; $i++) {
            if ($this->workCalendarService->isWorkDay($candidate)) {
                if ($workingDaysCounted >= $leadDays) {
                    // Átfutási idő letelt – az első szállítási napot keressük
                    if ($this->deliveryCalendarService->isDeliveryDay($pc, $supplier, $candidate)) {
                        return $candidate->copy();
                    }
                } else {
                    $workingDaysCounted++;
                }
            }
            $candidate->addDay();
        }

        return null;
    }
}
