<?php

namespace App\Livewire\Works;

use App\Enums\WorkAssignmentStatus;
use App\Enums\WorkInternalStatus;
use App\Enums\WorkStatus;
use App\Livewire\Concerns\HasCatalogToast;
use App\Livewire\Forms\WorkCoordinatorForm;
use App\Livewire\Works\Concerns\HasCoordinatorSearchablePickers;
use App\Models\Project;
use App\Models\Site;
use App\Models\User;
use App\Models\Work;
use App\Models\WorkAssignment;
use App\Models\WorkType;
use App\Services\Works\WorkAssignmentService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
use Livewire\WithPagination;
use RuntimeException;
use Throwable;

#[Layout('layouts.app', ['title' => 'Asignaciones', 'header' => 'Asignaciones del día'])]
#[Title('Asignaciones')]
class CoordinatorAssignmentsIndex extends Component
{
    use HasCatalogToast;
    use HasCoordinatorSearchablePickers;
    use WithPagination;

    public string $activeTab = 'assign';

    /** Subpestaña dentro de Asignar: `lote` (Lotes por asignar) o `quitar` (Trabajos de hoy / retirar del día). */
    public string $assignSubTab = 'lote';

    /** @var list<int|string> */
    public array $selectedWorkIds = [];

    /** @var list<int|string> */
    public array $selectedUnassignWorkIds = [];

    /** Solo se usa `technician_id` para el buscador de técnico (mismo patrón que la ficha OT). */
    public WorkCoordinatorForm $form;

    public ?int $filterAssignProjectId = null;

    public ?int $filterAssignWorkTypeId = null;

    public bool $showWorkPreviewModal = false;

    public ?int $previewWorkId = null;

    public bool $showAssignConfirmModal = false;

    public bool $showHelpModal = false;

    public bool $showUnassignConfirmModal = false;

    public ?string $historyDateFrom = null;

    public ?string $historyDateTo = null;

    public ?int $historyProjectId = null;

    public ?int $historyWorkTypeId = null;

    public ?int $historySiteId = null;

    public string $historyAssignmentStatus = '';

    public int $historyPerPage = 25;

    public function mount(): void
    {
        $user = auth()->user();
        if ($user === null || ! $user->canManageWorksCatalog()) {
            abort(403);
        }

        $this->historyDateTo = now()->toDateString();
        $this->historyDateFrom = now()->toDateString();
        $this->syncCoordinatorPickerLabelsFromForm();
    }

    public function updatedActiveTab(): void
    {
        $this->resetPage(pageName: 'history');
    }

    public function updatedAssignSubTab(): void
    {
        $this->selectedWorkIds = [];
        $this->selectedUnassignWorkIds = [];
    }

    public function updatedFilterAssignProjectId(): void
    {
        $this->filterAssignWorkTypeId = null;
        unset($this->worksPendingToday, $this->worksAssignedToday, $this->assignFilterWorkTypes);
    }

    public function updatedFilterAssignWorkTypeId(): void
    {
        unset($this->worksPendingToday, $this->worksAssignedToday);
    }

    public function updatedHistoryProjectId(): void
    {
        $this->historyWorkTypeId = null;
        $this->historySiteId = null;
        unset($this->historyFilterWorkTypes, $this->historyFilterSites);
        $this->resetPage(pageName: 'history');
    }

    public function updatedHistoryDateFrom(): void
    {
        $this->resetPage(pageName: 'history');
    }

    public function updatedHistoryDateTo(): void
    {
        $this->resetPage(pageName: 'history');
    }

    public function updatedHistorySiteId(): void
    {
        $this->resetPage(pageName: 'history');
    }

    public function updatedHistoryWorkTypeId(): void
    {
        $this->resetPage(pageName: 'history');
    }

    public function updatedHistoryAssignmentStatus(): void
    {
        $this->resetPage(pageName: 'history');
    }

    public function updatedHistoryPerPage(): void
    {
        $this->resetPage(pageName: 'history');
    }

    #[Computed]
    public function withinOperationsWindow(): bool
    {
        return app(WorkAssignmentService::class)->isWithinOperationsTimeWindow();
    }

    #[Computed]
    public function todayHasOpenAssignments(): bool
    {
        $ids = app(WorkAssignmentService::class)->openAssignedWorkIdsForDate(now());

        return $ids !== [];
    }

    #[Computed]
    public function todayHeading(): string
    {
        return now()->locale('es')->isoFormat('dddd, D [de] MMMM [de] YYYY');
    }

    #[Computed]
    public function todayDayNumber(): string
    {
        return now()->locale('es')->format('j');
    }

    #[Computed]
    public function todayMonthYear(): string
    {
        return now()->locale('es')->isoFormat('MMMM [de] YYYY');
    }

    #[Computed]
    public function todayWeekday(): string
    {
        return now()->locale('es')->isoFormat('dddd');
    }

    #[Computed]
    public function todayIso(): string
    {
        return now()->toDateString();
    }

    #[Computed]
    public function projectsForFilters(): Collection
    {
        return Project::query()
            ->active()
            ->orderBy('name')
            ->get(['id', 'name', 'color']);
    }

    /** Tipos de trabajo del proyecto elegido (pestaña Asignar). */
    #[Computed]
    public function assignFilterWorkTypes(): Collection
    {
        if ($this->filterAssignProjectId === null || $this->filterAssignProjectId <= 0) {
            return collect();
        }

        return WorkType::query()
            ->active()
            ->where('project_id', $this->filterAssignProjectId)
            ->orderBy('name')
            ->get(['id', 'name']);
    }

    /** Tipos de trabajo según proyecto (historial). */
    #[Computed]
    public function historyFilterWorkTypes(): Collection
    {
        if ($this->historyProjectId === null || $this->historyProjectId <= 0) {
            return collect();
        }

        return WorkType::query()
            ->active()
            ->where('project_id', $this->historyProjectId)
            ->orderBy('name')
            ->get(['id', 'name', 'project_id']);
    }

    /** Sitios del proyecto seleccionado (historial). */
    #[Computed]
    public function historyFilterSites(): Collection
    {
        if ($this->historyProjectId === null || $this->historyProjectId <= 0) {
            return collect();
        }

        return Site::query()
            ->where('project_id', $this->historyProjectId)
            ->orderBy('code')
            ->limit(500)
            ->get(['id', 'code', 'name', 'project_id']);
    }

    /**
     * OT elegibles sin asignación abierta para hoy (pendiente u observada).
     *
     * @return Collection<int, Work>
     */
    #[Computed]
    public function worksPendingToday(): Collection
    {
        $activeIds = app(WorkAssignmentService::class)->openAssignedWorkIdsForDate(now());

        return Work::query()
            ->with([
                'site:id,code,name,project_id,type',
                'site.project:id,name,color',
                'workType:id,name',
                'technician:id,name,paternal_surname,maternal_surname',
            ])
            ->whereIn('internal_status', [
                WorkInternalStatus::PENDIENTE->value,
                WorkInternalStatus::OBSERVADO->value,
            ])
            ->where('work_status', '!=', WorkStatus::CANCELADO->value)
            ->when($activeIds !== [], fn ($q) => $q->whereNotIn('works.id', $activeIds))
            ->when($this->filterAssignProjectId !== null && $this->filterAssignProjectId > 0, function ($q): void {
                $q->whereHas('site', fn ($s) => $s->where('project_id', $this->filterAssignProjectId));
            })
            ->when($this->filterAssignWorkTypeId !== null && $this->filterAssignWorkTypeId > 0, fn ($q) => $q->where('work_type_id', $this->filterAssignWorkTypeId))
            ->orderBy('ot')
            ->limit(500)
            ->get();
    }

    /**
     * OT con asignación abierta hoy (corregir técnico o retirar del lote).
     *
     * @return Collection<int, Work>
     */
    #[Computed]
    public function worksAssignedToday(): Collection
    {
        $activeIds = app(WorkAssignmentService::class)->openAssignedWorkIdsForDate(now());
        if ($activeIds === []) {
            return collect();
        }

        return Work::query()
            ->with([
                'site:id,code,name,project_id,type',
                'site.project:id,name,color',
                'workType:id,name',
                'technician:id,name,paternal_surname,maternal_surname',
                'assignments' => fn ($q) => $q
                    ->with(['technician:id,name,paternal_surname,maternal_surname', 'coordinator:id,name,paternal_surname,maternal_surname'])
                    ->orderByDesc('recorded_at')
                    ->orderByDesc('id')
                    ->limit(1),
            ])
            ->where('work_status', '!=', WorkStatus::CANCELADO->value)
            ->whereIn('works.id', $activeIds)
            ->when($this->filterAssignProjectId !== null && $this->filterAssignProjectId > 0, function ($q): void {
                $q->whereHas('site', fn ($s) => $s->where('project_id', $this->filterAssignProjectId));
            })
            ->when($this->filterAssignWorkTypeId !== null && $this->filterAssignWorkTypeId > 0, fn ($q) => $q->where('work_type_id', $this->filterAssignWorkTypeId))
            ->orderBy('ot')
            ->limit(500)
            ->get();
    }

    /** @return LengthAwarePaginator<int, WorkAssignment> */
    public function getAssignmentHistoryProperty(): LengthAwarePaginator
    {
        $from = $this->normalizeHistoryDate($this->historyDateFrom);
        $to = $this->normalizeHistoryDate($this->historyDateTo);

        if ($from === null) {
            $from = now()->startOfDay();
        } else {
            $from = $from->startOfDay();
        }
        if ($to === null) {
            $to = now()->endOfDay();
        } else {
            $to = $to->endOfDay();
        }
        if ($from->gt($to)) {
            [$from, $to] = [$to->copy()->startOfDay(), $from->copy()->endOfDay()];
        }

        return WorkAssignment::query()
            ->with([
                'work' => fn ($q) => $q->with([
                    'site:id,code,name,project_id',
                    'site.project:id,name,color',
                    'workType:id,name',
                ]),
                'technician:id,name,paternal_surname,maternal_surname',
                'coordinator:id,name,paternal_surname,maternal_surname',
            ])
            ->whereBetween('recorded_at', [$from, $to])
            ->when($this->historyProjectId !== null && $this->historyProjectId > 0, function ($q): void {
                $q->whereHas('work.site', fn ($s) => $s->where('project_id', $this->historyProjectId));
            })
            ->when($this->historyWorkTypeId !== null && $this->historyWorkTypeId > 0, function ($q): void {
                $q->whereHas('work', fn ($w) => $w->where('work_type_id', $this->historyWorkTypeId));
            })
            ->when($this->historySiteId !== null && $this->historySiteId > 0, function ($q): void {
                $q->whereHas('work', fn ($w) => $w->where('site_id', $this->historySiteId));
            })
            ->when($this->historyAssignmentStatus !== '', function ($q): void {
                if ($this->historyAssignmentStatus === WorkAssignmentStatus::FINALIZADO->value) {
                    $q->whereIn('status', [WorkAssignmentStatus::FINALIZADO->value, 'CONCLUIDO']);
                } else {
                    $q->where('status', $this->historyAssignmentStatus);
                }
            })
            ->orderByDesc('recorded_at')
            ->orderByDesc('id')
            ->paginate($this->historyPerPage, ['*'], 'history');
    }

    private function normalizeHistoryDate(?string $value): ?Carbon
    {
        if ($value === null || trim($value) === '') {
            return null;
        }

        try {
            return Carbon::parse($value);
        } catch (Throwable) {
            return null;
        }
    }

    public function getAssignConfirmTechnicianLabelProperty(): string
    {
        if ($this->form->technician_id === null) {
            return '—';
        }

        $u = User::query()->select(['id', 'name', 'paternal_surname', 'maternal_surname'])->find($this->form->technician_id);

        return $u?->fullName() ?? '—';
    }

    public function getBulkUnassignOtsProperty(): Collection
    {
        $ids = array_values(array_unique(array_map('intval', $this->selectedUnassignWorkIds)));
        if ($ids === []) {
            return collect();
        }

        return Work::query()->whereIn('id', $ids)->orderBy('ot')->pluck('ot');
    }

    public function getPreviewWorkProperty(): ?Work
    {
        if ($this->previewWorkId === null) {
            return null;
        }

        return Work::query()
            ->with([
                'site:id,code,name,project_id,type',
                'site.project:id,name,color',
                'workType:id,name',
                'technician:id,name,paternal_surname,maternal_surname',
                'coordinator:id,name,paternal_surname,maternal_surname',
                'noc:id,name,paternal_surname,maternal_surname',
            ])
            ->find($this->previewWorkId);
    }

    public function openWorkPreview(int $workId): void
    {
        $this->previewWorkId = $workId;
        $this->showWorkPreviewModal = true;
    }

    public function closeWorkPreview(): void
    {
        $this->showWorkPreviewModal = false;
        $this->previewWorkId = null;
    }

    public function requestAssignConfirm(): void
    {
        if (! $this->withinOperationsWindow) {
            $this->showToastMessage('Fuera del horario de coordinación ('.config('work_coordination.operations_start').'–'.config('work_coordination.operations_end').').', 'error');

            return;
        }

        $workIdsKey = $this->assignSubTab === 'lote' ? 'selectedWorkIds' : 'selectedUnassignWorkIds';
        $messages = [
            'form.technician_id.required' => 'Seleccione un técnico.',
            $workIdsKey.'.required' => $this->assignSubTab === 'lote'
                ? 'Marque al menos una OT sin asignar hoy.'
                : 'Marque al menos una OT asignada hoy.',
        ];

        $this->validate([
            'form.technician_id' => ['required', 'integer', 'exists:users,id'],
            $workIdsKey => ['required', 'array', 'min:1'],
        ], $messages);

        $this->showAssignConfirmModal = true;
    }

    public function cancelAssignConfirm(): void
    {
        $this->showAssignConfirmModal = false;
    }

    public function confirmAssignSelection(): void
    {
        if (! $this->withinOperationsWindow) {
            $this->showAssignConfirmModal = false;
            $this->showToastMessage('Fuera del horario de coordinación ('.config('work_coordination.operations_start').'–'.config('work_coordination.operations_end').').', 'error');

            return;
        }

        try {
            $workIds = $this->assignSubTab === 'lote'
                ? $this->selectedWorkIds
                : $this->selectedUnassignWorkIds;

            app(WorkAssignmentService::class)->assignWorksForDate(
                now(),
                $workIds,
                (int) $this->form->technician_id,
                auth()->user(),
            );
            $this->selectedWorkIds = [];
            $this->selectedUnassignWorkIds = [];
            $this->showAssignConfirmModal = false;
            $this->clearAssignCaches();
            $this->showToastMessage($this->assignSubTab === 'lote' ? 'Asignación registrada.' : 'Reasignación registrada.', 'success');
        } catch (RuntimeException $e) {
            $this->showToastMessage($e->getMessage(), 'error');
        } catch (Throwable $e) {
            report($e);
            $this->showToastMessage('No se pudo asignar.', 'error');
        }
    }

    public function openHelpModal(): void
    {
        $this->showHelpModal = true;
    }

    public function closeHelpModal(): void
    {
        $this->showHelpModal = false;
    }

    public function requestBulkUnassignConfirm(): void
    {
        if (! $this->withinOperationsWindow) {
            return;
        }

        $this->validate([
            'selectedUnassignWorkIds' => ['required', 'array', 'min:1'],
        ], [
            'selectedUnassignWorkIds.required' => 'Marque al menos una OT asignada hoy.',
        ]);

        $this->showUnassignConfirmModal = true;
    }

    public function cancelUnassignConfirm(): void
    {
        $this->showUnassignConfirmModal = false;
    }

    public function confirmBulkUnassignFromToday(): void
    {
        if (! $this->withinOperationsWindow) {
            $this->cancelUnassignConfirm();

            return;
        }

        try {
            app(WorkAssignmentService::class)->unassignWorksFromOpenDay($this->selectedUnassignWorkIds, now());
            $this->selectedUnassignWorkIds = [];
            $this->showUnassignConfirmModal = false;
            $this->clearAssignCaches();
            $this->showToastMessage('Se retiraron las asignaciones de hoy en las OT seleccionadas.', 'success');
        } catch (RuntimeException $e) {
            $this->showToastMessage($e->getMessage(), 'error');
        } catch (Throwable $e) {
            report($e);
            $this->showToastMessage('No se pudo completar la operación.', 'error');
        }
    }

    public function resetHistoryFilters(): void
    {
        $this->historyDateTo = now()->toDateString();
        $this->historyDateFrom = now()->toDateString();
        $this->historyProjectId = null;
        $this->historySiteId = null;
        $this->historyWorkTypeId = null;
        $this->historyAssignmentStatus = '';
        unset($this->assignmentHistory, $this->historyFilterWorkTypes, $this->historyFilterSites);
        $this->resetPage(pageName: 'history');
    }

    protected function clearAssignCaches(): void
    {
        unset(
            $this->worksPendingToday,
            $this->worksAssignedToday,
            $this->todayHasOpenAssignments,
            $this->withinOperationsWindow,
            $this->assignmentHistory,
            $this->previewWork,
            $this->assignFilterWorkTypes,
            $this->historyFilterWorkTypes,
            $this->historyFilterSites,
        );
    }

    public function render(): View
    {
        return view('livewire.works.coordinator-assignments-index', [
            'assignmentStatusCases' => WorkAssignmentStatus::cases(),
        ]);
    }
}
