<?php

declare(strict_types=1);

namespace App\Services\Support;

use App\Models\SlaBusinessHours;
use App\Models\SlaPolicy;
use App\Models\SupportTicket;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;

class SlaService
{
    /**
     * Assign an SLA policy to a ticket based on its department and priority.
     */
    public function assignSla(SupportTicket $ticket): void
    {
        $department = $ticket->departmentRelation;

        if (! $department?->sla_policy_id) {
            return;
        }

        $policy = SlaPolicy::query()->find($department->sla_policy_id);

        if (! $policy) {
            return;
        }

        // Find a policy matching the ticket priority, or fall back to the department's default
        $priorityPolicy = SlaPolicy::query()
            ->where('priority', $ticket->priority)
            ->where('id', $policy->id)
            ->first();

        $effectivePolicy = $priorityPolicy ?? $policy;

        $now = Carbon::now();

        $ticket->update([
            'sla_policy_id' => $effectivePolicy->id,
            'first_response_due_at' => $this->calculateDueTime(
                $now,
                $effectivePolicy->first_response_hours,
                $effectivePolicy->business_hours_only
            ),
            'resolution_due_at' => $this->calculateDueTime(
                $now,
                $effectivePolicy->resolution_hours,
                $effectivePolicy->business_hours_only
            ),
        ]);
    }

    /**
     * Calculate the due time accounting for business hours if required.
     */
    public function calculateDueTime(Carbon $from, int $hours, bool $businessHoursOnly): Carbon
    {
        if (! $businessHoursOnly) {
            return $from->copy()->addHours($hours);
        }

        $businessHoursConfig = SlaBusinessHours::query()
            ->where('is_holiday', false)
            ->orderBy('day_of_week')
            ->get();

        if ($businessHoursConfig->isEmpty()) {
            // No business hours configured, fall back to calendar hours
            return $from->copy()->addHours($hours);
        }

        $remainingMinutes = $hours * 60;
        $current = $from->copy();

        $maxIterations = $hours * 5; // Safety valve
        $iteration = 0;

        while ($remainingMinutes > 0 && $iteration < $maxIterations) {
            $iteration++;
            $dayOfWeek = (int) $current->dayOfWeek;

            $dayHours = $businessHoursConfig->where('day_of_week', $dayOfWeek)->first();

            if (! $dayHours) {
                // Not a business day, skip to next day
                $current->addDay()->startOfDay();

                continue;
            }

            $startTime = Carbon::parse($dayHours->start_time, $current->timezone)->setDate(
                $current->year,
                $current->month,
                $current->day
            );
            $endTime = Carbon::parse($dayHours->end_time, $current->timezone)->setDate(
                $current->year,
                $current->month,
                $current->day
            );

            if ($current->lt($startTime)) {
                $current = $startTime->copy();
            }

            if ($current->gte($endTime)) {
                $current->addDay()->startOfDay();

                continue;
            }

            $availableMinutes = (int) $current->diffInMinutes($endTime);

            if ($availableMinutes >= $remainingMinutes) {
                $current->addMinutes($remainingMinutes);
                $remainingMinutes = 0;
            } else {
                $remainingMinutes -= $availableMinutes;
                $current->addDay()->startOfDay();
            }
        }

        return $current;
    }

    /**
     * Check all open tickets for SLA breaches.
     */
    public function checkBreaches(): int
    {
        $now = Carbon::now();
        $breachCount = 0;

        // Check first response breaches
        $firstResponseBreaches = SupportTicket::query()
            ->whereNotNull('first_response_due_at')
            ->whereNull('first_responded_at')
            ->where('first_response_due_at', '<', $now)
            ->where('sla_first_response_breached', false)
            ->whereIn('status', ['open', 'in_progress', 'waiting'])
            ->get();

        foreach ($firstResponseBreaches as $ticket) {
            $ticket->update(['sla_first_response_breached' => true]);
            Log::warning('SLA first response breached', [
                'ticket_id' => $ticket->id,
                'ticket_reference' => $ticket->ticket_reference,
                'due_at' => $ticket->first_response_due_at->toDateTimeString(),
            ]);
            $breachCount++;
        }

        // Check resolution breaches
        $resolutionBreaches = SupportTicket::query()
            ->whereNotNull('resolution_due_at')
            ->whereNull('resolved_at')
            ->where('resolution_due_at', '<', $now)
            ->where('sla_resolution_breached', false)
            ->whereIn('status', ['open', 'in_progress', 'waiting'])
            ->get();

        foreach ($resolutionBreaches as $ticket) {
            $ticket->update(['sla_resolution_breached' => true]);
            Log::warning('SLA resolution breached', [
                'ticket_id' => $ticket->id,
                'ticket_reference' => $ticket->ticket_reference,
                'due_at' => $ticket->resolution_due_at->toDateTimeString(),
            ]);
            $breachCount++;
        }

        return $breachCount;
    }

    /**
     * Record the first staff response on a ticket.
     */
    public function recordFirstResponse(SupportTicket $ticket): void
    {
        if ($ticket->first_responded_at !== null) {
            return;
        }

        $ticket->update([
            'first_responded_at' => Carbon::now(),
        ]);
    }
}
