<?php

namespace App\Http\Controllers;

use App\Enums\DeclarationStatus;
use App\Enums\WorkspaceUserRole;
use App\Models\Declaration;
use App\Models\Workspace;
use App\Models\WorkspaceUser;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Inertia\Inertia;
use Inertia\Response;
use Spatie\Activitylog\Models\Activity;

class DashboardController extends Controller
{
    /**
     * Display the command center dashboard with KPI cards and urgent declarations.
     */
    public function __invoke(Request $request): Response
    {
        $user = $request->user();
        $workspaceId = $request->session()->get('current_workspace_id');
        $workspace = $workspaceId ? Workspace::query()->find($workspaceId) : null;

        if (! $workspace || ! $user) {
            return Inertia::render('Dashboard', [
                'stats' => null,
                'statCards' => [],
                'declarations' => [],
                'alerts' => [],
                'activities' => [],
                'workspaceName' => null,
                'roleLabel' => null,
                'isWorker' => false,
                'canNudge' => false,
                'declarationsUrl' => null,
                'clientsUrl' => null,
                'viewAllAlertsUrl' => null,
            ]);
        }

        /** @var WorkspaceUser|null $workspaceUser */
        $workspaceUser = $workspace->users()
            ->where('users.id', $user->id)
            ->first()
            ?->pivot;

        if (! $workspaceUser) {
            abort(404);
        }

        $isWorker = $workspaceUser->role->is(WorkspaceUserRole::Worker);
        $cacheKey = "dashboard:{$workspace->id}:{$user->id}";

        $dashboardData = Cache::remember($cacheKey, 300, function () use ($workspace, $user, $workspaceUser) {
            $baseQuery = fn () => $workspace->declarations()
                ->active()
                ->whereNotIn('status', [DeclarationStatus::Termine, DeclarationStatus::MiseEnDemeure, DeclarationStatus::Ferme])
                ->forUser($user, $workspaceUser);

            $overdue = $baseQuery()
                ->where('due_date', '<', now()->startOfDay())
                ->count();

            $dueThisWeek = $baseQuery()
                ->whereBetween('due_date', [now()->startOfDay(), now()->addDays(7)->endOfDay()])
                ->count();

            $enAttenteClient = $baseQuery()
                ->where('status', DeclarationStatus::EnAttenteClient)
                ->count();

            $enCours = $baseQuery()
                ->where('status', DeclarationStatus::EnCours)
                ->count();

            $alerts = $this->buildAlerts($baseQuery);
            $activities = $this->buildActivityFeed($workspace, $user, $workspaceUser);

            return [
                'overdue' => $overdue,
                'dueThisWeek' => $dueThisWeek,
                'enAttenteClient' => $enAttenteClient,
                'enCours' => $enCours,
                'alerts' => $alerts,
                'activities' => $activities,
            ];
        });

        $urgentDeclarations = $workspace->declarations()
            ->active()
            ->whereNotIn('status', [DeclarationStatus::Termine, DeclarationStatus::MiseEnDemeure, DeclarationStatus::Ferme])
            ->forUser($user, $workspaceUser)
            ->with('client:id,company_name', 'assignee:id,name')
            ->orderByRaw('CASE WHEN due_date IS NULL THEN 1 ELSE 0 END, due_date ASC')
            ->limit(15)
            ->get()
            ->map(fn (Declaration $d) => [
                'id' => $d->id,
                'title' => $d->title,
                'type' => $d->type->value,
                'typeLabel' => $this->typeLabels()[$d->type->value] ?? $d->type->value,
                'clientName' => $d->client?->company_name ?? 'Client supprimé',
                'assigneeName' => $d->assignee?->name,
                'status' => $d->status->value,
                'statusLabel' => DeclarationStatus::labels()[$d->status->value] ?? $d->status->value,
                'dueDate' => $d->due_date?->format('Y-m-d'),
                'showUrl' => route('declarations.show', $d),
                'nudgeUrl' => ! $isWorker ? route('declarations.nudge', $d) : null,
            ])
            ->all();

        $roleLabel = $this->roleLabels()[$workspaceUser->role->value] ?? $workspaceUser->role->value;

        $assigneeParam = $isWorker ? ['assignee' => $user->id] : [];

        $statCards = [
            [
                'label' => 'En retard',
                'count' => $dashboardData['overdue'],
                'status' => 'danger',
                'href' => route('declarations.index', array_merge(['overdue' => 1], $assigneeParam)),
            ],
            [
                'label' => 'Cette semaine',
                'count' => $dashboardData['dueThisWeek'],
                'status' => 'warning',
                'href' => route('declarations.index', array_merge(['due_this_week' => 1], $assigneeParam)),
            ],
            [
                'label' => 'En attente client',
                'count' => $dashboardData['enAttenteClient'],
                'status' => 'info',
                'href' => route('declarations.index', array_merge(['status' => DeclarationStatus::EnAttenteClient], $assigneeParam)),
            ],
            [
                'label' => 'En cours',
                'count' => $dashboardData['enCours'],
                'status' => 'success',
                'href' => route('declarations.index', array_merge(['status' => DeclarationStatus::EnCours], $assigneeParam)),
            ],
        ];

        return Inertia::render('Dashboard', [
            'stats' => $dashboardData,
            'statCards' => $statCards,
            'declarations' => $urgentDeclarations,
            'alerts' => $dashboardData['alerts'],
            'activities' => $dashboardData['activities'],
            'workspaceName' => $workspace->name,
            'roleLabel' => $roleLabel,
            'isWorker' => $isWorker,
            'canNudge' => ! $isWorker,
            'declarationsUrl' => route('declarations.index'),
            'clientsUrl' => route('clients.index'),
            'viewAllAlertsUrl' => route('declarations.index', ['filter' => 'alerts']),
        ]);
    }

    /**
     * Build priority alerts from declarations, sorted by severity and urgency.
     *
     * @param  \Closure  $baseQuery  A closure that returns a fresh query builder with workspace/user scoping applied
     * @return array<int, array<string, mixed>>
     */
    private function buildAlerts(\Closure $baseQuery): array
    {
        $typeLabels = $this->typeLabels();
        $today = now()->startOfDay();

        // Critical: overdue declarations (past deadline), excluding en_attente_client (handled by info)
        $critical = $baseQuery()
            ->whereNotNull('due_date')
            ->where('due_date', '<', $today)
            ->where('status', '!=', DeclarationStatus::EnAttenteClient)
            ->with('client:id,company_name')
            ->orderBy('due_date', 'asc')
            ->limit(20)
            ->get()
            ->map(fn (Declaration $d) => [
                'id' => $d->id,
                'severity' => 'critical',
                'clientName' => $d->client?->company_name ?? 'Client supprimé',
                'declarationType' => $d->type->value,
                'typeLabel' => $typeLabels[$d->type->value] ?? $d->type->value,
                'daysValue' => (int) abs($today->diffInDays($d->due_date)),
                'daysLabel' => abs($today->diffInDays($d->due_date)) <= 1 ? 'jour en retard' : 'jours en retard',
                'showUrl' => route('declarations.show', $d),
            ]);

        // Warning: approaching deadlines (due within 3 days)
        $warning = $baseQuery()
            ->whereNotNull('due_date')
            ->whereBetween('due_date', [$today, now()->addDays(3)->endOfDay()])
            ->with('client:id,company_name')
            ->orderBy('due_date', 'asc')
            ->limit(20)
            ->get()
            ->map(fn (Declaration $d) => [
                'id' => $d->id,
                'severity' => 'warning',
                'clientName' => $d->client?->company_name ?? 'Client supprimé',
                'declarationType' => $d->type->value,
                'typeLabel' => $typeLabels[$d->type->value] ?? $d->type->value,
                'daysValue' => (int) $today->diffInDays($d->due_date),
                'daysLabel' => $today->diffInDays($d->due_date) <= 1 ? 'jour restant' : 'jours restants',
                'showUrl' => route('declarations.show', $d),
            ]);

        // Info: waiting on client for >3 days
        $info = $baseQuery()
            ->where('status', DeclarationStatus::EnAttenteClient)
            ->where('updated_at', '<', now()->subDays(3))
            ->with('client:id,company_name')
            ->orderBy('updated_at', 'asc')
            ->limit(20)
            ->get()
            ->map(fn (Declaration $d) => [
                'id' => $d->id,
                'severity' => 'info',
                'clientName' => $d->client?->company_name ?? 'Client supprimé',
                'declarationType' => $d->type->value,
                'typeLabel' => $typeLabels[$d->type->value] ?? $d->type->value,
                'daysValue' => (int) abs($today->diffInDays($d->updated_at)),
                'daysLabel' => abs($today->diffInDays($d->updated_at)) <= 1 ? 'jour en attente' : 'jours en attente',
                'showUrl' => route('declarations.show', $d),
            ]);

        return $critical->concat($warning)->concat($info)->take(20)->values()->toArray();
    }

    /**
     * Build the activity feed for the dashboard.
     *
     * @return array<int, array<string, mixed>>
     */
    private function buildActivityFeed(Workspace $workspace, \App\Models\User $user, WorkspaceUser $workspaceUser): array
    {
        $isWorker = $workspaceUser->role->is(WorkspaceUserRole::Worker);

        if ($isWorker) {
            $activities = Activity::query()
                ->where('subject_type', 'App\\Models\\Declaration')
                ->whereIn('subject_id',
                    $workspace->declarations()
                        ->where('assigned_to', $user->id)
                        ->select('id')
                )
                ->with('causer:id,name')
                ->latest()
                ->limit(20)
                ->get();
        } else {
            $activities = Activity::query()
                ->where(function ($query) use ($workspace) {
                    $query->where(function ($q) use ($workspace) {
                        $q->where('subject_type', 'App\\Models\\Declaration')
                            ->whereIn('subject_id', $workspace->declarations()->select('id'));
                    })
                        ->orWhere(function ($q) use ($workspace) {
                            $q->where('subject_type', 'App\\Models\\Client')
                                ->whereIn('subject_id', $workspace->clients()->select('id'));
                        })
                        ->orWhere(function ($q) use ($workspace) {
                            $q->whereIn('subject_type', ['App\\Models\\WorkspaceUser', 'App\\Models\\TeamInvitation'])
                                ->whereIn('causer_id', $workspace->users()->select('users.id'));
                        });
                })
                ->with('causer:id,name')
                ->latest()
                ->limit(20)
                ->get();
        }

        $typeLabels = $this->typeLabels();

        // Batch-load declarations (with trashed) and their clients to avoid N+1
        $declarationSubjectIds = $activities
            ->where('subject_type', 'App\\Models\\Declaration')
            ->pluck('subject_id')
            ->unique()
            ->filter()
            ->values();

        $declarationsMap = $declarationSubjectIds->isNotEmpty()
            ? Declaration::query()
                ->withTrashed()
                ->with('client:id,company_name')
                ->whereIn('id', $declarationSubjectIds)
                ->get()
                ->keyBy('id')
            : collect();

        // Batch-load clients referenced as subjects
        $clientSubjectIds = $activities
            ->where('subject_type', 'App\\Models\\Client')
            ->pluck('subject_id')
            ->unique()
            ->filter()
            ->values();

        $existingClientIds = $clientSubjectIds->isNotEmpty()
            ? \App\Models\Client::query()
                ->whereIn('id', $clientSubjectIds)
                ->pluck('id')
                ->flip()
            : collect();

        // Batch-load reassigned user names
        $reassignedUserIds = $activities
            ->filter(fn (Activity $a) => $a->subject_type === 'App\\Models\\Declaration' && $a->event === 'updated')
            ->map(fn (Activity $a) => $a->properties->get('attributes', [])['assigned_to'] ?? null)
            ->filter()
            ->unique()
            ->values();

        $reassignedUsersMap = $reassignedUserIds->isNotEmpty()
            ? \App\Models\User::query()
                ->whereIn('id', $reassignedUserIds)
                ->pluck('name', 'id')
            : collect();

        return $activities->map(function (Activity $activity) use ($typeLabels, $declarationsMap, $existingClientIds, $reassignedUsersMap) {
            $actorName = $activity->causer?->name ?? 'Système';
            $names = explode(' ', trim($actorName));
            $actorInitials = count($names) >= 2
                ? strtoupper(mb_substr($names[0], 0, 1).mb_substr(end($names), 0, 1))
                : strtoupper(mb_substr($actorName, 0, 1));

            return [
                'id' => $activity->id,
                'actorName' => $actorName,
                'actorInitials' => $actorInitials,
                'description' => $this->formatActivityDescription($activity, $typeLabels, $declarationsMap, $reassignedUsersMap),
                'targetUrl' => $this->resolveActivityTargetUrl($activity, $declarationsMap, $existingClientIds),
                'targetLabel' => $activity->subject_type
                    ? class_basename($activity->subject_type)
                    : null,
                'timestamp' => $activity->created_at->toISOString(),
                'eventType' => $this->resolveEventType($activity),
            ];
        })->values()->toArray();
    }

    /**
     * Format a Spatie activity record into a human-readable French description.
     *
     * @param  array<string, string>  $typeLabels
     * @param  \Illuminate\Support\Collection  $declarationsMap  Pre-loaded declarations keyed by ID
     * @param  \Illuminate\Support\Collection  $reassignedUsersMap  Pre-loaded user names keyed by ID
     */
    private function formatActivityDescription(Activity $activity, array $typeLabels, $declarationsMap, $reassignedUsersMap): string
    {
        $actorName = $activity->causer?->name ?? 'Système';
        $subjectType = $activity->subject_type ? class_basename($activity->subject_type) : null;
        $event = $activity->event;
        $properties = $activity->properties;
        $old = $properties->get('old', []);
        $attributes = $properties->get('attributes', []);

        if ($subjectType === 'Declaration') {
            $typeValue = $attributes['type'] ?? $old['type'] ?? null;
            $typeLabel = $typeValue ? ($typeLabels[$typeValue] ?? $typeValue) : 'déclaration';

            $declaration = $declarationsMap->get($activity->subject_id);
            $clientName = $declaration?->client?->company_name ?? 'client supprimé';

            if ($event === 'created') {
                return "{$actorName} a créé la déclaration {$typeLabel} pour {$clientName}";
            }

            if ($event === 'updated') {
                if (isset($attributes['status']) && isset($old['status'])) {
                    $statusLabels = DeclarationStatus::labels();
                    $newStatus = $statusLabels[$attributes['status']] ?? $attributes['status'];

                    return "{$actorName} a changé le statut de {$typeLabel} ({$clientName}) en {$newStatus}";
                }

                if (isset($attributes['assigned_to']) && isset($old['assigned_to'])) {
                    $newAssignee = $reassignedUsersMap->get($attributes['assigned_to'], 'inconnu');

                    return "{$actorName} a réassigné {$typeLabel} ({$clientName}) à {$newAssignee}";
                }

                return "{$actorName} a modifié la déclaration {$typeLabel} ({$clientName})";
            }

            if ($event === 'deleted') {
                return "{$actorName} a supprimé la déclaration {$typeLabel} ({$clientName})";
            }
        }

        if ($subjectType === 'Client') {
            $clientName = $attributes['company_name'] ?? $old['company_name'] ?? 'client';
            if ($event === 'created') {
                return "{$actorName} a créé le client {$clientName}";
            }

            return "{$actorName} a modifié le client {$clientName}";
        }

        if ($subjectType === 'WorkspaceUser' || $subjectType === 'TeamInvitation') {
            if ($event === 'updated' && isset($attributes['role'])) {
                return "{$actorName} a changé le rôle d'un membre";
            }

            return "{$actorName} a modifié l'équipe";
        }

        return "{$actorName} a modifié {$subjectType}";
    }

    /**
     * Resolve the target URL for an activity entry.
     *
     * @param  \Illuminate\Support\Collection  $declarationsMap  Pre-loaded declarations (with trashed) keyed by ID
     * @param  \Illuminate\Support\Collection  $existingClientIds  Pre-loaded existing (non-deleted) client IDs
     */
    private function resolveActivityTargetUrl(Activity $activity, $declarationsMap, $existingClientIds): ?string
    {
        $subjectType = $activity->subject_type ? class_basename($activity->subject_type) : null;
        $subjectId = $activity->subject_id;

        if (! $subjectType || ! $subjectId) {
            return null;
        }

        if ($subjectType === 'Declaration') {
            $declaration = $declarationsMap->get($subjectId);

            if ($declaration && ! $declaration->trashed()) {
                return route('declarations.show', $subjectId);
            }

            return null;
        }

        if ($subjectType === 'Client') {
            if ($existingClientIds->has($subjectId)) {
                return route('clients.show', $subjectId);
            }

            return null;
        }

        if ($subjectType === 'WorkspaceUser' || $subjectType === 'TeamInvitation') {
            return route('team.index');
        }

        return null;
    }

    /**
     * Resolve the event type hint for frontend icon selection.
     */
    private function resolveEventType(Activity $activity): string
    {
        $subjectType = $activity->subject_type ? class_basename($activity->subject_type) : null;
        $event = $activity->event;
        $attributes = $activity->properties->get('attributes', []);

        if ($subjectType === 'Declaration') {
            if ($event === 'created') {
                return 'declaration_created';
            }
            if (isset($attributes['status'])) {
                return 'status_change';
            }
            if (isset($attributes['assigned_to'])) {
                return 'reassignment';
            }

            return 'declaration_updated';
        }

        if ($subjectType === 'Client') {
            return 'client_updated';
        }

        if ($subjectType === 'WorkspaceUser' || $subjectType === 'TeamInvitation') {
            return 'role_change';
        }

        return 'default';
    }

    /**
     * Get declaration type labels.
     *
     * @return array<string, string>
     */
    protected function typeLabels(): array
    {
        return [
            'vat' => 'TVA',
            'vat_monthly' => 'TVA mensuelle',
            'vat_quarterly' => 'TVA trimestrielle',
            'corporate_tax' => 'IS',
            'income_tax' => 'IR',
            'cnss' => 'CNSS',
            'annual_balance' => 'Bilan',
            'other' => 'Autre',
        ];
    }

    /**
     * Get workspace user role labels.
     *
     * @return array<string, string>
     */
    protected function roleLabels(): array
    {
        return [
            WorkspaceUserRole::Owner => 'Propriétaire',
            WorkspaceUserRole::Manager => 'Manager',
            WorkspaceUserRole::Worker => 'Collaborateur',
        ];
    }
}
