<?php

namespace App\Components\Report;

use App\Models\Dialler\AgentStatus;
use App\Models\Dialler\AgentStatusHistory;
use App\Models\Solicitor\Solicitor;
use Carbon\Carbon;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;

/**
 * Day-by-day agent availability timeline.
 *
 * Reads the interval history (agent_status_histories), clips each interval to
 * the selected window AND to each calendar day it spans (so a session crossing
 * midnight is counted against both days), then derives: per-agent/day status
 * totals, a 24-hour timeline of coloured blocks, utilisation KPIs, and a
 * per-day stacked-chart series.
 */
class AgentAvailabilityReport extends Component
{
    use AuthorizesRequests;

    public ?string $from = null;
    public ?string $to = null;
    public ?int $agentId = null;

    /** Presence statuses we render, in display order, with colour + label. */
    public const STATUSES = [
        'available'      => ['label' => 'Available',      'colour' => '#198754'],
        'busy'           => ['label' => 'On Call',        'colour' => '#dc3545'],
        'wrap_up'        => ['label' => 'Wrap-Up',        'colour' => '#ffc107'],
        'do_not_disturb' => ['label' => 'Do Not Disturb', 'colour' => '#6c757d'],
        'offline'        => ['label' => 'Offline',        'colour' => '#dee2e6'],
    ];

    /** Statuses that count as "online" (the agent was on shift / working). */
    public const ONLINE = ['available', 'busy', 'wrap_up', 'do_not_disturb'];

    /** Hard cap so a huge range can't build an unbounded result. */
    protected const MAX_DAYS = 92;

    public function mount(): void
    {
        $this->authorize('reports.agent-availability.view');

        $this->from ??= now()->subDays(6)->toDateString();
        $this->to   ??= now()->toDateString();
    }

    public function render()
    {
        $solicitorId = app(Solicitor::class)?->id;

        return view('components.report.agent-availability-report', [
            'report'   => $this->buildReport($solicitorId),
            'statuses' => self::STATUSES,
            'agents'   => $this->agentOptions($solicitorId),
        ]);
    }

    /** Human-readable duration, e.g. "3h 05m", "12m", "45s". */
    public function fmt(int $seconds): string
    {
        if ($seconds <= 0) {
            return '—';
        }

        if ($seconds < 60) {
            return $seconds . 's';
        }

        $hours   = intdiv($seconds, 3600);
        $minutes = intdiv($seconds % 3600, 60);

        return $hours > 0
            ? $hours . 'h ' . str_pad((string) $minutes, 2, '0', STR_PAD_LEFT) . 'm'
            : $minutes . 'm';
    }

    /** Agents known to the dialler, for the filter dropdown. */
    protected function agentOptions(?int $solicitorId): array
    {
        if (! $solicitorId) {
            return [];
        }

        return AgentStatus::query()
            ->where('solicitor_id', $solicitorId)
            ->with('agent:id,name_first,name_last')
            ->get()
            ->filter(fn ($s) => $s->agent)
            ->map(fn ($s) => [
                'id'   => $s->agent_id,
                'name' => $this->agentName($s->agent_id, $s->agent),
            ])
            ->sortBy('name')
            ->values()
            ->all();
    }

    protected function buildReport(?int $solicitorId): array
    {
        $empty = ['days' => [], 'agents' => [], 'chart' => ['labels' => [], 'datasets' => []]];

        if (! $solicitorId || ! $this->from || ! $this->to) {
            return $empty;
        }

        $rangeStart = Carbon::parse($this->from)->startOfDay();
        $rangeEnd   = Carbon::parse($this->to)->endOfDay();

        if ($rangeEnd->lessThan($rangeStart)) {
            return $empty;
        }

        if ($rangeStart->diffInDays($rangeEnd) > self::MAX_DAYS) {
            $rangeStart = $rangeEnd->copy()->subDays(self::MAX_DAYS)->startOfDay();
        }

        $now = now();

        // Day keys spanning the range (inclusive).
        $days = [];
        for ($d = $rangeStart->copy(); $d->lessThanOrEqualTo($rangeEnd); $d->addDay()) {
            $days[] = $d->toDateString();
        }
        $dayIndex = array_flip($days);

        $segments = AgentStatusHistory::query()
            ->where('solicitor_id', $solicitorId)
            ->when($this->agentId, fn ($q) => $q->where('agent_id', $this->agentId))
            ->where('started_at', '<=', $rangeEnd)
            ->where(fn ($q) => $q->whereNull('ended_at')->orWhere('ended_at', '>=', $rangeStart))
            ->with('agent:id,name_first,name_last')
            ->orderBy('started_at')
            ->get();

        $agents = [];
        $chart  = ['labels' => array_map(fn ($d) => Carbon::parse($d)->format('D j M'), $days), 'datasets' => []];
        foreach (array_keys(self::STATUSES) as $status) {
            $chart['datasets'][$status] = array_fill(0, count($days), 0);
        }

        foreach ($segments as $seg) {
            // Clip the interval to the report window.
            $segEnd = $seg->ended_at ?? $now;
            $s = $seg->started_at->greaterThan($rangeStart) ? $seg->started_at->copy() : $rangeStart->copy();
            $e = $segEnd->lessThan($rangeEnd) ? $segEnd->copy() : $rangeEnd->copy();

            if ($s->greaterThanOrEqualTo($e)) {
                continue;
            }

            $key = $seg->agent_id;

            if (! isset($agents[$key])) {
                $agents[$key] = [
                    'id'            => $seg->agent_id,
                    'name'          => $this->agentName($seg->agent_id, $seg->agent),
                    'days'          => [],
                    'wrap_up_count' => 0,
                ];
            }

            if ($seg->status === 'wrap_up') {
                $agents[$key]['wrap_up_count']++;
            }

            // Split the clipped interval across the calendar days it spans.
            $cursor = $s->copy();

            while ($cursor->lessThan($e)) {
                $nextDay  = $cursor->copy()->addDay()->startOfDay();
                $sliceEnd = $nextDay->lessThan($e) ? $nextDay : $e;

                $seconds = (int) $cursor->diffInSeconds($sliceEnd);

                if ($seconds > 0) {
                    $dateKey  = $cursor->toDateString();
                    $dayStart = $cursor->copy()->startOfDay();

                    $agents[$key]['days'][$dateKey] ??= ['totals' => [], 'blocks' => []];

                    $agents[$key]['days'][$dateKey]['totals'][$seg->status] =
                        ($agents[$key]['days'][$dateKey]['totals'][$seg->status] ?? 0) + $seconds;

                    $agents[$key]['days'][$dateKey]['blocks'][] = [
                        'status'   => $seg->status,
                        'offset'   => (int) $dayStart->diffInSeconds($cursor),
                        'duration' => $seconds,
                        'start'    => $cursor->format('H:i'),
                        'end'      => $sliceEnd->format('H:i'),
                        'open'     => $seg->ended_at === null,
                    ];

                    if (isset($dayIndex[$dateKey])) {
                        $chart['datasets'][$seg->status][$dayIndex[$dateKey]] += $seconds;
                    }
                }

                $cursor = $nextDay;
            }
        }

        // Per-agent KPIs.
        foreach ($agents as &$agent) {
            $kpi = ['available' => 0, 'busy' => 0, 'wrap_up' => 0, 'do_not_disturb' => 0, 'offline' => 0, 'online' => 0];

            foreach ($agent['days'] as $day) {
                foreach ($day['totals'] as $status => $secs) {
                    $kpi[$status] = ($kpi[$status] ?? 0) + $secs;

                    if (in_array($status, self::ONLINE, true)) {
                        $kpi['online'] += $secs;
                    }
                }
            }

            $kpi['occupied']     = $kpi['busy'] + $kpi['wrap_up'];
            $kpi['occupancyPct'] = $kpi['online'] > 0 ? (int) round($kpi['occupied'] / $kpi['online'] * 100) : 0;
            $kpi['wrap_up_count'] = $agent['wrap_up_count'];
            $kpi['avg_wrap_up']  = $agent['wrap_up_count'] > 0 ? (int) round($kpi['wrap_up'] / $agent['wrap_up_count']) : 0;

            $agent['kpi'] = $kpi;
        }
        unset($agent);

        // Stable display order by agent name.
        uasort($agents, fn ($a, $b) => strcmp($a['name'], $b['name']));

        return ['days' => $days, 'agents' => $agents, 'chart' => $chart];
    }

    protected function agentName(int $agentId, $agent): string
    {
        if (! $agent) {
            return 'Agent #' . $agentId;
        }

        return trim($agent->name_first . ' ' . $agent->name_last) ?: ('Agent #' . $agentId);
    }
}
