<?php

namespace Mtc\Plugins\MembersMessaging\Models;

use App\Casts\OptionalEncrypted;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Log;
use Mtc\Core\Admin\User;
use Mtc\Core\AdminUser;
use Mtc\Modules\Members\Models\Member;
use Mtc\Shop\Order;

class Thread extends Model
{
    use SoftDeletes;

    protected $table = 'messenger_threads';

    protected $fillable = [
        'id',
        'member_id',
        'admin_id',
        'order_id',
        'title',
        'url',
        'status',
        'assigned',
        'category',
        'response_time'
    ];

    protected $casts = [
        'title' => OptionalEncrypted::class,
    ];

    protected $dates = ['created_at', 'updated_at', 'deleted_at'];

    const array THREAD_STATUSES = [
        0 => 'All',
        1 => 'Open',
        2 => 'Closed',
        3 => 'Starred',
        4 => 'Archived',
    ];

    const array THREAD_CATEGORIES = [
        1 => 'General Question',
        2 => 'Doctor Query',
        3 => 'Order Query',
    ];

    public static function boot(): void
    {
        parent::boot();

        self::created(function ($thread) {
            $admin_id = User::getAdminUser() ?? 0;

            $notify_new_owner = !$thread->assigned();

            if (!$thread->admin_id) {

                $thread->admin_id = AdminUser::where('role', 7)
                    ->first()
                    ->id ?? 0;

                $thread->save();

                $thread->markMemberMessageAsUnread();

                $admin_user = User::find($admin_id);
                if ($notify_new_owner && $admin_user) {

                    $email_body = template('emails/new_message.twig', [
                        'member' => $admin_user,
                        'thread' => $thread,
                        'thread_url' => SITE_URL . '/plugins/MembersMessaging/admin/view_member_thread.php?url=' . $thread->url,
                    ]);

                    $email_to = DEV_MODE ?
                        DEV_EMAIL :
                        $admin_user->email;

                    email(
                        $email_to,
                        "You have been assigned a new message thread on " . config('app.name'),
                        $email_body,
                        ['dev_copy' => DEV_MODE]
                    );
                } else if (empty($admin_user)) {
                    Log::error('No admin user found to notify.');
                }
            } else if (empty($admin_id)) {
                Log::error('No admin user found to assign thread to.');
            }
        });
    }

    /**
     * Messages relationship.
     *
     * @return HasMany
     */
    public function messages()
    {
        return $this->hasMany(Message::class, 'thread_id');
    }

    /**
     * Admin/User relationship.
     *
     * @return BelongsTo
     */
    public function admin(): BelongsTo
    {
        return $this->belongsTo(AdminUser::class, 'admin_id', 'id');
    }

    /**
     * Member relationship.
     *
     * @return BelongsTo
     */
    public function member(): BelongsTo
    {
        return $this->belongsTo(Member::class, 'member_id', 'id');
    }

    /**
     * Order relationship.
     *
     * @return hasOne
     */
    public function order(): HasOne
    {
        return $this->hasOne(Order::class, 'id', 'order_id');
    }

    public function otherOrders() {
        return $this->member
            ? $this->member->orders()->where('id', '!=', $this->order_id)->get()
            : collect();
    }

    public function participants()
    {
        $adminIds = $this->messages()->pluck('admin_id')->filter()->unique();
        $memberIds = $this->messages()->pluck('member_id')->filter()->unique();
        
        $admins = AdminUser::whereIn('id', $adminIds)->get();
        $members = Member::whereIn('id', $memberIds)->get();
        
        return $admins->concat($members);
    }

    /**
     * @return string
     */
    public function getCategoryNameAttribute()
    {
        return self::THREAD_CATEGORIES[$this->category] ?? 'Unknown';
    }

    public function getStatusNameAttribute()
    {
        return self::THREAD_STATUSES[$this->status] ?? 'Unknown';
    }

    /**
     *
     */
    public function getThreadAdminName()
    {
        return $this->admin->name ?? null;
    }

    /**
     *
     */
    public function getThreadMemberName()
    {
        return $this->member->getFullnameAttribute();
    }

    /**
     * @return ?Message
     */
    public function getLatestMessageAttribute(): ?Message
    {
        return $this->messages()
            ->where('private', '0')
            ->latest()
            ->first();
    }

    /**
     * @return Collection
     */
    public function public_messages(): Collection
    {
        return $this->messages()->where('private', '0')->get();
    }

    /**
     * Relationship with the latest message placed in this thread
     *
     * @return BelongsTo
     */
    public function latestMessage()
    {
        return $this->hasOne(Message::class, 'thread_id')
            ->where('private', 0)
            ->latestOfMany('created_at');
    }

    /**
     * @return mixed
     */
    public static function getAllLatest()
    {
        return self::latest('updated_at');
    }

    /**
     * @return int|void
     */
    public function markMemberMessageAsRead()
    {
        // Check for if the message is read by admin
        $admin_id = User::getAdminUser();

        // Don't mark as read
        if ($admin_id) {
            return;
        }

        return $this->messages()
            ->WhereNotNull('read_at')
            ->where(function ($query) {
                $query->where('admin_id', 0)->orWhere('private' . 1);
            })
            ->update(['read_at' => now()]);
    }


    /**
     * @return User|null
     */
    public function getAssignedAdmin(): ?User
    {
        return $this->admin()->first()->id;
    }

    /**
     * @param User $admin_user
     * @return Collection
     */
    public static function getAllAssignedToAdmin($admin_id): Collection
    {
        return self::query()
            ->where('admin_id', $admin_id)
            ->get();
    }

    private static function adminCanSeeAllThreads($admin_id): bool
    {
        $admin = AdminUser::find($admin_id);

        if (!$admin) {
            return false;
        }

        $seeAllRoles = [7, 8, 9];

        return in_array($admin->role, $seeAllRoles);
    }
    
    public function scopeForAdminRole($query)
    {
        $adminRole = AdminUser::getCurrent()->role;

        // MTC Admin
        if(!$adminRole) {
            return $query;
        }

        // If the admin has a role that can see all threads, skip filtering
        if (self::adminCanSeeAllThreads($adminRole)) {
            return $query;
        }

        // Otherwise, filter threads by admin role
        return $query->whereHas('admin', function ($q) use ($adminRole) {
            $q->where('role', $adminRole);
        });
    }

    public static function scopeWithMessagesForUrl($query, $url)
    {
        return $query->withTrashed()
            ->with(['messages.admin_user', 'messages.member'])
            ->whereUrl($url)
            ->orderBy('updated_at', 'desc');
    }

    /**
     * @param $member_id
     * @param $admin_id
     * @return Collection
     */
    public static function scopeForUser($admin_id = 0): Collection
    {
        return self::getAllLatest()
            ->orWhere('admin_id', $admin_id)
            ->get();
    }

    public static function scopeForCurrentAdmin($query)
    {
        $admin = AdminUser::getCurrent();

        if(self::adminCanSeeAllThreads($admin->id)) {
            return $query;
        }

        return $query->where('admin_id', $admin->id);
    }

    public function scopeWithParticipants($query) {
        return $query->with(['messages.admin_user', 'messages.member']);
    }
    
    public static function scopeForMember($member_id = 0): Collection
    {
        return self::getAllLatest()
            ->where('member_id', $member_id)
            ->get();
    }

    public function scopeByPriority($query)
    {
        return $query
            ->orderByRaw("status = 3 DESC") // starred first
            ->orderByRaw("status = 2 ASC")  // closed last
            ->orderByDesc('updated_at');    // newest in each group
    }

    public function isClosedOrArchived(): bool
    {
        return in_array($this->status, [
            array_search('Closed', self::THREAD_STATUSES),
            array_search('Archived', self::THREAD_STATUSES),
        ], true);
    }
    
    /**
     * @param $member_id
     * @return Member
     */
    public function getMemberFromThread($member_id = 0): Member
    {
        return $this->where('member_id', $member_id)->first();
    }

    /**
     * @return bool
     */
    public function assigned()
    {
        return !empty($this->admin_id);
    }

    public function calculateResponseTime(): ?int
    {
        $latest = $this->messages()
            ->orderBy('created_at', 'desc')
            ->pluck('created_at')
            ->first();

        return Carbon::now()->diffInSeconds($latest);
    }

    public function otherThreads()
    {
        return self::where('member_id', $this->member_id)
            ->where('id', '!=', $this->id)
            ->get();
    }
    
    public function newUnread($admin_pov = false)
    {
        return $this
            ->messages()
            ->whereNull('read_at')
            ->where('admin_id', $admin_pov ? 0 : '!=', 0)
            ->when(!$admin_pov, function ($query) {
                $query->where('private', 0);
            })->get();
    }

    /**
     * @param $admin_pov
     * @return bool
     */
    public function hasUnread($admin_pov = false): bool
    {
        return $this->newUnread($admin_pov)->count() > 0;
    }

    public function getUnreadStatusString(bool $admin_pov = false): string
    {
        return $this->hasUnread($admin_pov) ? 'unread' : 'read';
    }

    /**
     * @return bool
     */
    public function hasMemberUnread(): bool
    {
        return $this->hasUnread(false);
    }

 
    /**
     * @param $member_id
     * @return Collection
     */
    public function getMemberUnreadMessages($member_id = 0): Collection
    {
        return $this
            ->where('member_id', $member_id)
            ->firstOrFail()
            ->messages()
            ->whereNull('read_at')
            ->get();
    }

    /**
     * @param $member_id
     * @return int
     */
    public function getMemberUnreadMessagesCount($member_id = 0): int
    {
        return $this->getMemberUnreadMessages($member_id)->count();
    }

    public function scopeWithUnreadMessages($query, $admin_pov = false)
    {
        return $query->whereHas('messages', function ($q) use ($admin_pov) {
            $q->whereNull('read_at');
            
            if ($admin_pov) {
                $q->where('admin_id', 0);
            } else {
                $q->where('admin_id', '!=', 0)
                    ->where('private', 0);
            }
        });
    }

    /**
     * @return int
     */
    public function markMemberMessageAsUnread(): int
    {
        return $this->messages()
            ->WhereNotNull('read_at')
            ->where(function ($query) {
                $query->where('admin_id', 0)->orWhere('private', 1);
            })
            ->update(['read_at' => null]);
    }
}
