<?php

namespace App\Http\Controllers\Api;

use App\Events\TaskStatusChanged;
use App\Http\Controllers\Controller;
use App\Http\Resources\TaskResource;
use App\Models\Project;
use App\Models\ProjectStage;
use App\Models\Task;
use App\Support\CrmModuleManager;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;

class TaskController extends Controller
{
    public function __construct()
    {
        $this->authorizeResource(Task::class, 'task');
    }

    public function index(Request $request)
    {
        $search = trim((string) $request->input('q', ''));
        $status = $request->input('status');
        $priority = $request->input('priority');
        $assigneeId = $request->integer('assignee_id');
        $creatorId = $request->integer('creator_id');
        $projectId = $request->integer('project_id');
        $parentId = $request->integer('parent_id');
        $deadline = $request->input('deadline');
        $mine = $request->boolean('mine');

        $tasks = Task::query()
            ->with(['assignee', 'creator', 'deal', 'company', 'contact', 'project', 'projectStage', 'parent'])
            ->withCount(['subtasks', 'doneSubtasks'])
            ->when($search !== '', function (Builder $query) use ($search): void {
                $query->where(function (Builder $sub) use ($search): void {
                    $sub->where('title', 'like', "%{$search}%")
                        ->orWhere('description', 'like', "%{$search}%")
                        ->orWhereHas('project', fn (Builder $project) => $project->where('name', 'like', "%{$search}%"))
                        ->orWhereHas('deal', fn (Builder $deal) => $deal->where('title', 'like', "%{$search}%"))
                        ->orWhereHas('parent', fn (Builder $parent) => $parent->where('title', 'like', "%{$search}%"));
                });
            })
            ->when($status, fn (Builder $query) => $query->where('status', $status))
            ->when($priority, fn (Builder $query) => $query->where('priority', $priority))
            ->when($assigneeId, fn (Builder $query) => $query->where('assignee_id', $assigneeId))
            ->when($creatorId, fn (Builder $query) => $query->where('creator_id', $creatorId))
            ->when($projectId, fn (Builder $query) => $query->where('project_id', $projectId))
            ->when($parentId, fn (Builder $query) => $query->where('parent_id', $parentId))
            ->when($deadline === 'overdue', function (Builder $query): void {
                $query->whereIn('status', ['todo', 'in_progress', 'review'])
                    ->whereNotNull('due_at')
                    ->where('due_at', '<', now());
            })
            ->when($deadline === 'today', fn (Builder $query) => $query->whereDate('due_at', now()->toDateString()))
            ->when($deadline === 'week', fn (Builder $query) => $query->whereBetween('due_at', [now()->startOfDay(), now()->copy()->addDays(7)->endOfDay()]))
            ->when($mine, function (Builder $query) use ($request): void {
                $query->where(function (Builder $sub) use ($request): void {
                    $sub->where('creator_id', $request->user()->id)
                        ->orWhere('assignee_id', $request->user()->id);
                });
            })
            ->orderByRaw('due_at is null')
            ->orderBy('due_at')
            ->orderBy('sort_order')
            ->latest('id')
            ->paginate(30)
            ->withQueryString();

        return TaskResource::collection($tasks);
    }

    public function store(Request $request, CrmModuleManager $moduleManager): TaskResource
    {
        $payload = $this->validatedData($request);
        $payload['creator_id'] = $request->user()->id;
        $payload = $moduleManager->applyPayloadHooks('tasks.store', $payload, [
            'hook' => 'tasks.store',
            'user_id' => $request->user()->id,
            'source' => 'api',
        ], array_keys($payload));
        $payload['completed_at'] = ($payload['status'] ?? null) === 'done'
            ? ($payload['completed_at'] ?? now())
            : null;

        $task = Task::create($payload);

        $this->refreshProjectProgress($task);
        $this->syncParentStatusById($task->parent_id);

        return TaskResource::make($task->load(['assignee', 'creator', 'deal', 'company', 'contact', 'project', 'projectStage', 'parent'])->loadCount(['subtasks', 'doneSubtasks']));
    }

    public function show(Task $task): TaskResource
    {
        return TaskResource::make($task->load(['assignee', 'creator', 'deal', 'company', 'contact', 'project', 'projectStage', 'parent', 'subtasks.assignee', 'subtasks.creator'])->loadCount(['subtasks', 'doneSubtasks']));
    }

    public function update(Request $request, Task $task, CrmModuleManager $moduleManager): TaskResource
    {
        $previousProjectId = $task->project_id;
        $previousParentId = $task->parent_id;

        $payload = $this->validatedData($request, $task);
        $payload = $moduleManager->applyPayloadHooks('tasks.update', $payload, [
            'hook' => 'tasks.update',
            'user_id' => $request->user()->id,
            'task_id' => $task->id,
            'source' => 'api',
        ], array_keys($payload));
        $payload['completed_at'] = ($payload['status'] ?? null) === 'done'
            ? ($payload['completed_at'] ?? now())
            : null;

        $task->update($payload);

        $this->refreshProjectProgress($task);
        if ($previousProjectId && $previousProjectId !== $task->project_id) {
            Project::query()->find($previousProjectId)?->recalculateProgress();
        }

        if ($previousParentId !== $task->parent_id) {
            $this->syncParentStatusById($previousParentId);
        }
        $this->syncParentStatusById($task->parent_id);

        return TaskResource::make($task->load(['assignee', 'creator', 'deal', 'company', 'contact', 'project', 'projectStage', 'parent'])->loadCount(['subtasks', 'doneSubtasks']));
    }

    public function destroy(Task $task)
    {
        $projectId = $task->project_id;
        $parentId = $task->parent_id;

        $task->delete();

        if ($projectId) {
            Project::query()->find($projectId)?->recalculateProgress();
        }

        $this->syncParentStatusById($parentId);

        return response()->noContent();
    }

    public function complete(Task $task): TaskResource
    {
        $this->authorize('complete', $task);

        $doneStageId = null;
        if ($task->project_id) {
            $doneStageId = ProjectStage::query()
                ->where('project_id', $task->project_id)
                ->where('is_done', true)
                ->value('id');
        }

        $task->update([
            'status' => 'done',
            'project_stage_id' => $doneStageId ?? $task->project_stage_id,
            'completed_at' => now(),
        ]);

        $task->refresh()->load(['assignee', 'creator', 'parent']);
        $task->loadCount(['subtasks', 'doneSubtasks']);

        $this->refreshProjectProgress($task);
        $this->syncParentStatusById($task->parent_id);
        $this->broadcastToOthers(new TaskStatusChanged($task));

        return TaskResource::make($task->load(['deal', 'company', 'contact', 'project', 'projectStage']));
    }

    public function updateStatus(Request $request, Task $task): TaskResource
    {
        $this->authorize('update', $task);

        $validated = $request->validate([
            'status' => ['required', Rule::in(['todo', 'in_progress', 'review', 'done'])],
        ]);

        $status = (string) $validated['status'];
        $projectStageId = $task->project_stage_id;

        if ($task->project_id) {
            if ($status === 'done') {
                $doneStageId = ProjectStage::query()
                    ->where('project_id', $task->project_id)
                    ->where('is_done', true)
                    ->value('id');

                if ($doneStageId) {
                    $projectStageId = $doneStageId;
                }
            } elseif ($task->projectStage?->is_done) {
                $projectStageId = ProjectStage::query()
                    ->where('project_id', $task->project_id)
                    ->where('is_done', false)
                    ->orderBy('sort_order')
                    ->value('id');
            }
        }

        $task->update([
            'status' => $status,
            'project_stage_id' => $projectStageId,
            'completed_at' => $status === 'done' ? ($task->completed_at ?? now()) : null,
        ]);

        $task->refresh()->load(['assignee', 'creator', 'parent', 'deal', 'company', 'contact', 'project', 'projectStage']);
        $task->loadCount(['subtasks', 'doneSubtasks']);

        $this->refreshProjectProgress($task);
        $this->syncParentStatusById($task->parent_id);
        $this->broadcastToOthers(new TaskStatusChanged($task));

        return TaskResource::make($task);
    }

    /**
     * @return array<string, mixed>
     */
    private function validatedData(Request $request, ?Task $task = null): array
    {
        $validated = $request->validate([
            'title' => ['required', 'string', 'max:255'],
            'description' => ['nullable', 'string'],
            'deal_id' => ['nullable', 'exists:deals,id'],
            'company_id' => ['nullable', 'exists:companies,id'],
            'contact_id' => ['nullable', 'exists:contacts,id'],
            'project_id' => ['nullable', 'exists:projects,id'],
            'project_stage_id' => ['nullable', 'exists:project_stages,id'],
            'parent_id' => ['nullable', 'exists:tasks,id'],
            'assignee_id' => ['nullable', 'exists:users,id'],
            'status' => ['required', Rule::in(['todo', 'in_progress', 'review', 'done'])],
            'priority' => ['required', Rule::in(['low', 'medium', 'high', 'urgent'])],
            'estimated_hours' => ['nullable', 'numeric', 'min:0'],
            'tracked_hours' => ['nullable', 'numeric', 'min:0'],
            'sort_order' => ['nullable', 'integer', 'min:0'],
            'starts_at' => ['nullable', 'date'],
            'due_at' => ['nullable', 'date', 'after_or_equal:starts_at'],
            'reminder_at' => ['nullable', 'date'],
        ]);

        $parentId = $validated['parent_id'] ?? null;
        $projectId = $validated['project_id'] ?? null;
        $projectStageId = $validated['project_stage_id'] ?? null;

        if ($parentId) {
            $parent = Task::query()->findOrFail((int) $parentId);

            if ($task && $parent->id === $task->id) {
                throw ValidationException::withMessages([
                    'parent_id' => 'A task cannot be a subtask of itself.',
                ]);
            }

            if ($task && $this->isDescendantOfTask($parent, $task->id)) {
                throw ValidationException::withMessages([
                    'parent_id' => 'You cannot select a child task as a parent task.',
                ]);
            }

            if (! $projectId && $parent->project_id) {
                $projectId = $parent->project_id;
                $validated['project_id'] = $projectId;
            }

            if ($parent->project_id && (int) $projectId !== (int) $parent->project_id) {
                throw ValidationException::withMessages([
                    'project_id' => 'The subtask must belong to the same project as the parent task.',
                ]);
            }
        }

        if ($projectStageId && ! $projectId) {
            throw ValidationException::withMessages([
                'project_stage_id' => 'To select a stage, first select a project.',
            ]);
        }

        if ($projectId && $projectStageId) {
            $stage = ProjectStage::query()->findOrFail((int) $projectStageId);

            if ($stage->project_id !== (int) $projectId) {
                throw ValidationException::withMessages([
                    'project_stage_id' => 'The stage does not belong to the selected project.',
                ]);
            }

            if ($stage->is_done) {
                $validated['status'] = 'done';
            }
        }

        if (! $projectId) {
            $validated['project_stage_id'] = null;
        }

        $validated['project_id'] = $projectId;
        $validated['estimated_hours'] = (float) ($validated['estimated_hours'] ?? 0);
        $validated['tracked_hours'] = (float) ($validated['tracked_hours'] ?? 0);
        $validated['sort_order'] = (int) ($validated['sort_order'] ?? 0);
        $validated['completed_at'] = $validated['status'] === 'done'
            ? ($task?->completed_at ?? now())
            : null;

        return $validated;
    }

    private function refreshProjectProgress(Task $task): void
    {
        if ($task->project_id) {
            $task->project?->recalculateProgress();
        }
    }

    private function syncParentStatusById(?int $parentId): void
    {
        if (! $parentId) {
            return;
        }

        $parent = Task::query()->find($parentId);

        if (! $parent) {
            return;
        }

        $total = $parent->subtasks()->count();

        if ($total === 0) {
            return;
        }

        $done = $parent->subtasks()->where('status', 'done')->count();

        if ($done === $total && $parent->status !== 'done') {
            $parent->update([
                'status' => 'done',
                'completed_at' => $parent->completed_at ?? now(),
            ]);

            $this->refreshProjectProgress($parent);
            $this->broadcastToOthers(new TaskStatusChanged($parent));

            return;
        }

        if ($done < $total && $parent->status === 'done') {
            $parent->update([
                'status' => 'in_progress',
                'completed_at' => null,
            ]);

            $this->refreshProjectProgress($parent);
            $this->broadcastToOthers(new TaskStatusChanged($parent));
        }
    }

    private function isDescendantOfTask(Task $candidateParent, int $taskId): bool
    {
        $visited = [];
        $current = $candidateParent;

        while ($current) {
            if ($current->id === $taskId) {
                return true;
            }

            if (in_array($current->id, $visited, true) || ! $current->parent_id) {
                return false;
            }

            $visited[] = $current->id;
            $current = Task::query()->find($current->parent_id);
        }

        return false;
    }
}
