<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\DB;

class ChatRoom extends Model
{
    // FIX #9 — Added SoftDeletes so deleted rooms don't leak messages/broadcasts.
    use HasFactory, SoftDeletes;

    protected $table = 'chat_rooms';
    protected $primaryKey = 'chat_id';
    public $incrementing = true;

    protected $fillable = [
        'chat_type',
        'group_name',
        'group_description',
        'group_avatar_url',
        'created_by',
        'last_message_id',
        'is_active',
    ];

    protected $casts = [
        'is_active'  => 'boolean',
        'created_at' => 'datetime',
        'updated_at' => 'datetime',
    ];

    // -------------------------------------------------------------------------
    // Relationships
    // -------------------------------------------------------------------------

    public function creator(): BelongsTo
    {
        return $this->belongsTo(User::class, 'created_by');
    }

    public function messages(): HasMany
    {
        return $this->hasMany(ChatMessage::class, 'chat_id', 'chat_id');
    }

    public function participants(): HasMany
    {
        return $this->hasMany(ChatParticipant::class, 'chat_id', 'chat_id');
    }

    /**
     * FIX #7 — Added ->using(ChatParticipant::class) so that the BelongsToMany
     * pivot is hydrated as a ChatParticipant model instance (with its casts,
     * scopes, and booted() hooks) instead of a plain stdClass.
     * Without this, casts like is_blocked => boolean silently do nothing when
     * iterating $room->users.
     */
    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class, 'chat_participants', 'chat_id', 'user_id')
                    ->using(ChatParticipant::class)
                    ->withPivot(['role', 'joined_at', 'last_seen', 'is_muted', 'is_blocked']);
    }

    /**
     * FIX #6 — lastMessage() is kept as a belongsTo on the stored FK for
     * eager-loading performance. However, callers that need the truly latest
     * message should use latestMessage() below, which derives it from the
     * messages table directly — immune to stale last_message_id values.
     *
     * You should also add a DB trigger or update last_message_id inside
     * ChatController::sendMessage() after every insert to keep this accurate.
     */
    public function lastMessage(): BelongsTo
    {
        return $this->belongsTo(ChatMessage::class, 'last_message_id', 'message_id');
    }

    /**
     * Always-accurate latest message derived from the messages table.
     * Use this when you cannot guarantee last_message_id is up to date.
     */
    public function latestMessage()
    {
        return $this->hasOne(ChatMessage::class, 'chat_id', 'chat_id')
                    ->latestOfMany('sent_at');
    }

    // -------------------------------------------------------------------------
    // Scopes
    // -------------------------------------------------------------------------

    public function scopeActive($query)
    {
        return $query->where('is_active', true);
    }

    public function scopeForUser($query, int $userId)
    {
        return $query->whereHas('participants', fn ($q) => $q->where('user_id', $userId));
    }

    /**
     * FIX #4 & #5 — The original correlated subquery used last_seen as the
     * unread cutoff, which resets to now() the moment the user opens the chat —
     * making the count always 0 before the page even renders. It also returned
     * null when no participant row existed, causing the > comparison to silently
     * drop all messages.
     *
     * Correct approach: count messages where is_read = false AND sender != $userId.
     * This is already maintained by ChatMessage::markAsRead() and the saving hook,
     * so it is always accurate regardless of last_seen.
     */
    public function scopeWithUnreadCount($query, int $userId)
    {
        return $query->withCount([
            'messages as unread_count' => fn ($q) => $q
                ->where('is_read', false)
                ->where('sender_id', '!=', $userId),
        ]);
    }

    // -------------------------------------------------------------------------
    // Static factory
    // -------------------------------------------------------------------------

    /**
     * FIX #1  — Race condition: wrapped in DB::transaction + lockForUpdate so
     *            two concurrent requests cannot both create the same private room.
     *
     * FIX #2  — Auth::id() removed. Caller must pass $creatorId explicitly so
     *            this works from queues, CLI, and broadcast contexts where the
     *            Auth facade returns null.
     *
     * FIX #3  — Replaced ChatParticipant::insert([]) with two ::create() calls
     *            so all model events, casts, and $fillable guards fire correctly.
     *
     * FIX #8  — Added ->having('participants_count', 2) so group rooms that
     *            happen to contain both users are never mistaken for a private room.
     */
    public static function getOrCreatePrivate(int $user1Id, int $user2Id, int $creatorId): self
    {
        return DB::transaction(function () use ($user1Id, $user2Id, $creatorId) {
            // Lock the matching rows so a concurrent request blocks here
            // instead of racing past the existence check.
            $room = self::query()
                ->where('chat_type', 'private')
                ->whereHas('participants', fn ($q) => $q->where('user_id', $user1Id))
                ->whereHas('participants', fn ($q) => $q->where('user_id', $user2Id))
                ->withCount('participants')
                ->having('participants_count', 2)   // FIX #8: exclude group rooms
                ->lockForUpdate()
                ->first();

            if ($room) {
                return $room;
            }

            // FIX #2: use passed-in $creatorId, not Auth::id()
            $room = self::create([
                'chat_type'  => 'private',
                'is_active'  => true,
                'created_by' => $creatorId,
            ]);

            $now = now();

            // FIX #3: use ChatParticipant::create() so casts and events fire
            ChatParticipant::create([
                'chat_id'   => $room->chat_id,
                'user_id'   => $user1Id,
                'joined_at' => $now,
                'last_seen' => $now,
            ]);

            ChatParticipant::create([
                'chat_id'   => $room->chat_id,
                'user_id'   => $user2Id,
                'joined_at' => $now,
                'last_seen' => $now,
            ]);

            return $room;
        });
    }
}