<?php

namespace App\Services;

use App\Models\Setting;
use Carbon\Carbon;

class BusinessHoursService
{
    private array $config;
    private string $timezone;

    public function __construct()
    {
        $this->timezone = Setting::get('support_timezone', 'Europe/Istanbul');
        $raw = Setting::get('business_hours', '{"work_days":["monday","tuesday","wednesday","thursday","friday"],"start":"09:00","end":"18:00"}');
        $this->config = is_string($raw) ? json_decode($raw, true) : (is_array($raw) ? $raw : []);
    }

    /**
     * Check if a given timestamp falls within business hours.
     */
    public function isWithinBusinessHours(?Carbon $time = null): bool
    {
        if (! Setting::get('business_hours_enabled', true)) {
            return true;
        }

        $time = ($time ?? Carbon::now())->copy()->setTimezone($this->timezone);
        $dayName = strtolower($time->format('l'));

        $workDays = $this->config['work_days'] ?? [];
        if (! in_array($dayName, $workDays)) {
            return false;
        }

        $start = $this->config['start'] ?? '09:00';
        $end = $this->config['end'] ?? '18:00';

        $timeStr = $time->format('H:i');

        return $timeStr >= $start && $timeStr < $end;
    }

    /**
     * PRC-353 — true when the given (or current) time is OUTSIDE business
     * hours. When business_hours_enabled is off, isWithinBusinessHours()
     * returns true, so this returns false → the outside-hours banner is
     * never shown while the feature is disabled (spec 4.19).
     */
    public function isOutsideHours(?Carbon $time = null): bool
    {
        return ! $this->isWithinBusinessHours($time);
    }

    /**
     * Add N minutes to a starting time, counting only business hours.
     */
    public function addBusinessMinutes(Carbon $from, int $minutes): Carbon
    {
        if (! Setting::get('business_hours_enabled', true)) {
            return $from->copy()->addMinutes($minutes);
        }

        $current = $from->copy()->setTimezone($this->timezone);
        $remaining = $minutes;

        $workDays = $this->config['work_days'] ?? [];
        $startTime = $this->config['start'] ?? '09:00';
        $endTime = $this->config['end'] ?? '18:00';

        [$startHour, $startMin] = array_map('intval', explode(':', $startTime));
        [$endHour, $endMin] = array_map('intval', explode(':', $endTime));

        $dailyMinutes = ($endHour * 60 + $endMin) - ($startHour * 60 + $startMin);

        if ($dailyMinutes <= 0) {
            return $from->copy()->addMinutes($minutes);
        }

        // If current time is outside business hours, advance to next business start
        if (! $this->isWithinBusinessHours($current)) {
            $current = $this->nextBusinessStart($current);
        }

        // Safety limit to prevent infinite loops (max 365 days)
        $maxIterations = 365;
        $iterations = 0;

        while ($remaining > 0 && $iterations < $maxIterations) {
            $iterations++;
            $dayName = strtolower($current->format('l'));

            if (! in_array($dayName, $workDays)) {
                $current->addDay()->setTime($startHour, $startMin, 0);
                continue;
            }

            $dayEnd = $current->copy()->setTime($endHour, $endMin, 0);
            $minutesLeftToday = max(0, (int) $current->diffInMinutes($dayEnd, false));

            if ($remaining <= $minutesLeftToday) {
                $current->addMinutes($remaining);
                $remaining = 0;
            } else {
                $remaining -= $minutesLeftToday;
                $current->addDay()->setTime($startHour, $startMin, 0);
            }
        }

        return $current;
    }

    /**
     * Get the next business hour start from a given time.
     */
    public function nextBusinessStart(Carbon $from): Carbon
    {
        $current = $from->copy()->setTimezone($this->timezone);
        $workDays = $this->config['work_days'] ?? [];
        $startTime = $this->config['start'] ?? '09:00';
        $endTime = $this->config['end'] ?? '18:00';

        [$startHour, $startMin] = array_map('intval', explode(':', $startTime));
        [$endHour, $endMin] = array_map('intval', explode(':', $endTime));

        // If today is a work day and we're before start time, jump to start
        $dayName = strtolower($current->format('l'));
        if (in_array($dayName, $workDays)) {
            $todayStart = $current->copy()->setTime($startHour, $startMin, 0);
            $todayEnd = $current->copy()->setTime($endHour, $endMin, 0);

            if ($current->lt($todayStart)) {
                return $todayStart;
            }

            if ($current->lt($todayEnd)) {
                return $current;
            }
        }

        // Move to next day and find first work day
        $current->addDay()->setTime($startHour, $startMin, 0);

        for ($i = 0; $i < 7; $i++) {
            $dayName = strtolower($current->format('l'));
            if (in_array($dayName, $workDays)) {
                return $current;
            }
            $current->addDay();
        }

        return $from->copy();
    }
}
