<?php

namespace App\Http\Controllers;

use App\Events\ChatMessageSent;
use App\Models\ChatConversation;
use App\Models\ChatMessage;
use App\Models\Job;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;

class ChatController extends Controller
{
    public function index()
    {
        $user = auth()->user();
        $conversations = ChatConversation::with(['participants.role', 'job', 'messages.user'])
            ->whereHas('participants', fn ($q) => $q->whereKey($user->id))
            ->withCount('messages')
            ->latest('updated_at')
            ->get()
            ->map(fn ($c) => $this->conversationArray($c, $user->id));

        $users = User::with('role')->where('id', '!=', $user->id)->where('is_active', true)
            ->orderBy('name')->get(['id', 'name', 'role_id']);
        $jobs = $this->accessibleJobs($user)->get(['id', 'job_number', 'site_suburb']);

        return view('chat.index', compact('conversations', 'users', 'jobs'));
    }

    public function messages(ChatConversation $conversation)
    {
        $this->assertParticipant($conversation);
        return response()->json($conversation->messages()->with('user')->latest()->limit(100)->get()->reverse()->values()
            ->map->toChatArray()->values());
    }

    public function store(Request $request, ChatConversation $conversation)
    {
        $this->assertParticipant($conversation);
        $data = $request->validate([
            'body' => 'nullable|string|max:10000',
            'file' => 'nullable|file|max:10240|mimes:jpg,jpeg,png,gif,webp,pdf,doc,docx,xls,xlsx,txt,zip',
        ]);
        abort_if(blank($data['body'] ?? null) && !$request->hasFile('file'), 422, 'Message cannot be empty.');

        $message = new ChatMessage(['conversation_id' => $conversation->id, 'user_id' => auth()->id(), 'body' => $data['body'] ?? null]);
        if ($request->hasFile('file')) {
            $file = $request->file('file');
            $message->attachment_path = $file->store('chat-files', 'public');
            $message->attachment_name = $file->getClientOriginalName();
            $message->attachment_mime = $file->getMimeType();
            $message->attachment_size = $file->getSize();
        }
        $message->save();
        $conversation->touch();
        broadcast(new ChatMessageSent($message));

        return response()->json($message->load('user')->toChatArray(), 201);
    }

    public function direct(Request $request)
    {
        $data = $request->validate(['user_id' => 'required|exists:users,id']);
        abort_if((int) $data['user_id'] === auth()->id(), 422, 'You cannot message yourself.');
        $ids = [auth()->id(), (int) $data['user_id']];
        $conversation = ChatConversation::where('type', 'direct')
            ->whereHas('participants', fn ($q) => $q->whereIn('user_id', $ids), '=', 2)
            ->whereDoesntHave('participants', fn ($q) => $q->whereNotIn('user_id', $ids))
            ->first();
        if (!$conversation) $conversation = $this->createConversation('direct', null, null, $ids);
        return response()->json($this->conversationArray($conversation->load('participants.role', 'job'), auth()->id()));
    }

    public function group(Request $request)
    {
        abort_unless(auth()->user()->role?->name === 'office_admin', 403);
        $data = $request->validate(['name' => 'required|string|max:120', 'user_ids' => 'required|array|min:1', 'user_ids.*' => 'exists:users,id']);
        $conversation = $this->createConversation('group', $data['name'], null, array_unique(array_merge([auth()->id()], $data['user_ids'])));
        return response()->json($this->conversationArray($conversation->load('participants.role'), auth()->id()), 201);
    }

    public function job(Job $job)
    {
        $user = auth()->user();
        $memberIds = $this->jobMemberIds($job);
        abort_unless(in_array($user->id, $memberIds, true), 403, 'You are not a member of this job.');
        $conversation = ChatConversation::where('type', 'job')->where('job_id', $job->id)->first();
        if (!$conversation) $conversation = $this->createConversation('job', 'Job '.$job->job_number, $job->id, $memberIds);
        else $conversation->participants()->syncWithoutDetaching($memberIds);
        return response()->json($this->conversationArray($conversation->load('participants.role', 'job'), $user->id));
    }

    private function createConversation(string $type, ?string $name, ?int $jobId, array $userIds): ChatConversation
    {
        return DB::transaction(function () use ($type, $name, $jobId, $userIds) {
            $conversation = ChatConversation::create(['type' => $type, 'name' => $name, 'job_id' => $jobId, 'created_by' => auth()->id()]);
            $conversation->participants()->sync($userIds);
            return $conversation;
        });
    }

    private function assertParticipant(ChatConversation $conversation): void
    {
        abort_unless($conversation->participants()->whereKey(auth()->id())->exists(), 403, 'You do not have access to this conversation.');
    }

    private function conversationArray(ChatConversation $c, int $userId): array
    {
        $other = $c->participants->firstWhere('id', '!=', $userId);
        return [
            'id' => $c->id, 'type' => $c->type, 'name' => $c->name ?: ($other?->name ?? 'Conversation'),
            'job' => $c->job ? ['id' => $c->job->id, 'number' => $c->job->job_number] : null,
            'participants' => $c->participants->map(fn ($u) => ['id' => $u->id, 'name' => $u->name, 'role' => $u->role_name, 'initials' => $u->initials]),
            'messages_count' => $c->messages_count ?? $c->messages->count(),
            'last_message' => ($last = $c->messages->sortByDesc('created_at')->first())?->toChatArray(),
        ];
    }

    private function jobMemberIds(Job $job): array
    {
        $ids = collect([$job->consultant_id, $job->manager_id, $job->designer_id, $job->sent_to_manufacture_by])
            ->filter()->map(fn ($id) => (int) $id);
        $ids = $ids->merge($job->tradeUsers()->pluck('users.id'));
        $ids = $ids->merge($job->employeeTaskAssignments()->pluck('user_id'));
        $ids = $ids->merge(User::whereHas('role', fn ($q) => $q->where('name', 'office_admin'))->pluck('id'));
        return $ids->map(fn ($id) => (int) $id)->unique()->values()->all();
    }

    private function accessibleJobs(User $user)
    {
        if ($user->role?->name === 'office_admin') return Job::query()->latest('id');
        $ids = $this->jobMemberIdsForUser($user);
        return Job::whereIn('id', $ids)->latest('id');
    }

    private function jobMemberIdsForUser(User $user): array
    {
        return Job::where(fn ($q) => $q->where('consultant_id', $user->id)->orWhere('manager_id', $user->id)->orWhere('designer_id', $user->id))
            ->pluck('id')->merge($user->assignedJobs()->pluck('crm_jobs.id'))
            ->merge($user->jobEmployeeTaskAssignments()->pluck('job_id'))->unique()->all();
    }
}