<?php

namespace App\Components\Dashboard;

use App\Models\LeadSource\LeadSource;
use App\Models\Solicitor\Solicitor;
use App\Services\Dashboard\LeadPerformanceMetrics;
use Illuminate\Support\Carbon;
use Illuminate\Validation\ValidationException;
use Livewire\Component;

class LeadPerformanceDashboard extends Component
{
    public string $filterMode = 'month';

    public string $month = '';

    public string $startDate = '';

    public string $endDate = '';

    public string $leadSourceId = '';

    public string $heatmapWeekStart = '';

    public array $leadSourceOptions = [];

    public array $dashboard = [];

    public function mount(): void
    {
        $this->month = now()->format('Y-m');
        $this->setDatesFromMonth();
        $this->loadLeadSourceOptions();
        $this->resetHeatmapWeek();
        $this->loadDashboard();
    }

    public function applyMonth(): void
    {
        $this->validate([
            'month' => ['required', 'date_format:Y-m'],
        ]);

        $this->filterMode = 'month';
        $this->setDatesFromMonth();
        $this->resetHeatmapWeek();
        $this->loadDashboard(true);
    }

    public function applyCustomRange(): void
    {
        $this->validate([
            'startDate' => ['required', 'date'],
            'endDate' => ['required', 'date', 'after_or_equal:startDate'],
        ]);

        if (Carbon::parse($this->startDate)->diffInDays(Carbon::parse($this->endDate)) > 731) {
            throw ValidationException::withMessages([
                'endDate' => 'Choose a reporting range of two years or less.',
            ]);
        }

        $this->filterMode = 'custom';
        $this->resetHeatmapWeek();
        $this->loadDashboard(true);
    }

    public function updatedLeadSourceId(): void
    {
        if ($this->leadSourceId !== '') {
            $solicitorId = app(Solicitor::class)?->id;

            abort_unless(
                $solicitorId
                && LeadSource::query()
                    ->where('solicitor_id', $solicitorId)
                    ->whereKey((int) $this->leadSourceId)
                    ->exists(),
                404,
            );
        }

        $this->loadDashboard(true);
    }

    public function previousHeatmapWeek(): void
    {
        $firstWeek = Carbon::parse($this->startDate)->startOfWeek();
        $currentWeek = Carbon::parse($this->heatmapWeekStart)->startOfWeek();

        if ($currentWeek->gt($firstWeek)) {
            $this->heatmapWeekStart = $currentWeek->subWeek()->toDateString();
            $this->loadDashboard(true);
        }
    }

    public function nextHeatmapWeek(): void
    {
        $lastWeek = Carbon::parse($this->endDate)->startOfWeek();
        $currentWeek = Carbon::parse($this->heatmapWeekStart)->startOfWeek();

        if ($currentWeek->lt($lastWeek)) {
            $this->heatmapWeekStart = $currentWeek->addWeek()->toDateString();
            $this->loadDashboard(true);
        }
    }

    public function render()
    {
        return view('components.dashboard.lead-performance-dashboard');
    }

    private function setDatesFromMonth(): void
    {
        $date = Carbon::createFromFormat('Y-m', $this->month)->startOfMonth();
        $this->startDate = $date->toDateString();
        $this->endDate = $date->copy()->endOfMonth()->toDateString();
    }

    private function loadLeadSourceOptions(): void
    {
        $solicitorId = app(Solicitor::class)?->id;
        abort_unless($solicitorId, 404);

        $this->leadSourceOptions = LeadSource::query()
            ->where('solicitor_id', $solicitorId)
            ->orderBy('display_name')
            ->get(['id', 'display_name'])
            ->map(fn (LeadSource $source) => [
                'id' => $source->id,
                'name' => $source->display_name,
            ])
            ->all();
    }

    private function resetHeatmapWeek(): void
    {
        $from = Carbon::parse($this->startDate)->startOfDay();
        $to = Carbon::parse($this->endDate)->endOfDay();
        $preferredDate = now()->betweenIncluded($from, $to) ? now() : $from;

        $this->heatmapWeekStart = $preferredDate->copy()->startOfWeek()->toDateString();
    }

    private function loadDashboard(bool $dispatchUpdate = false): void
    {
        $solicitorId = app(Solicitor::class)?->id;
        abort_unless($solicitorId, 404);

        $from = Carbon::parse($this->startDate)->startOfDay();
        $to = Carbon::parse($this->endDate)->endOfDay();
        $weekStart = Carbon::parse($this->heatmapWeekStart)->startOfWeek();
        $weekEnd = $weekStart->copy()->endOfWeek();
        $heatmapFrom = $weekStart->lt($from) ? $from->copy() : $weekStart->copy();
        $heatmapTo = $weekEnd->gt($to) ? $to->copy() : $weekEnd->copy();
        $firstWeek = $from->copy()->startOfWeek();
        $lastWeek = $to->copy()->startOfWeek();
        $weekNumber = intdiv((int) $firstWeek->diffInDays($weekStart), 7) + 1;

        $this->dashboard = (new LeadPerformanceMetrics(
            $solicitorId,
            $this->leadSourceId === '' ? null : (int) $this->leadSourceId,
        ))->report($from, $to, $heatmapFrom, $heatmapTo);

        $this->dashboard['heatmapWeekLabel'] = $this->filterMode === 'month'
            ? "Week {$weekNumber} of {$from->format('F Y')}"
            : "Week {$weekNumber} of selected range";
        $this->dashboard['heatmapWeekDates'] = $heatmapFrom->format('j M Y')
            .' - '.$heatmapTo->format('j M Y');
        $this->dashboard['canPreviousHeatmapWeek'] = $weekStart->gt($firstWeek);
        $this->dashboard['canNextHeatmapWeek'] = $weekStart->lt($lastWeek);
        $this->dashboard['heatmapDates'] = collect(range(0, 6))
            ->map(function (int $offset) use ($weekStart, $from, $to) {
                $date = $weekStart->copy()->addDays($offset);

                return $date->betweenIncluded($from, $to) ? $date->format('j M') : null;
            })
            ->all();

        if ($dispatchUpdate) {
            $this->dispatch(
                'lead-performance-updated',
                leadVolume: $this->dashboard['leadVolume'],
            );
        }
    }
}
