<?php

namespace Modules\Announcement\Entities;

use App\Traits\QueryCacheable;
use Deligoez\LaravelModelHashId\Traits\HasHashId;
use Deligoez\LaravelModelHashId\Traits\HasHashIdRouting;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Kreait\Firebase\Messaging\CloudMessage;
use Kreait\Firebase\Messaging\Notification;
use Kreait\Laravel\Firebase\Facades\Firebase;
use Modules\Announcement\Database\Factories\DeviceFactory;
use Yajra\DataTables\Facades\DataTables;

class Device extends Model
{
    use HasFactory;
    use HasHashId;
    use HasHashIdRouting;
    use QueryCacheable;

    /**
     * Specify the amount of time to cache queries.
     * Do not specify or set it to null to disable caching.
     *
     * @var int|\DateTime
     */
    public $cacheFor = 3600;

    /**
     * The tags for the query cache. Can be useful
     * if flushing cache for specific tags only.
     *
     * @var null|array
     */
    public $cacheTags = ['devices'];

    /**
     * Invalidate the cache automatically
     * upon update in the database.
     *
     * @var bool
     */
    protected static $flushCacheOnUpdate = true;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'info',
        'token',
        'fcm_topic',
        'fcm_token',
        'ip_address',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        //
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'info' => 'array',
        'updated_at' => 'datetime:Y-m-d H:i:s',
        'created_at' => 'datetime:Y-m-d H:i:s',
    ];

    /**
     * The accessors to append to the model's array form.
     *
     * @var array
     */
    protected $appends = [
        //
    ];

    /**
     * Create a new factory instance for the model.
     */
    protected static function newFactory()
    {
        return DeviceFactory::new();
    }

    public function announcements()
    {
        return $this->belongsToMany(Announcement::class, 'announcement_device')->withTimestamps()->withPivot('has_read');
    }

    public function unreadAnnouncements()
    {
        return $this->announcements()->wherePivot('has_read', 0);
    }

    public function readAnnouncements()
    {
        return $this->announcements()->wherePivot('has_read', 1);
    }

    public function scopeFilters($query, Request $request)
    {
        $query->when($request->has('range'), function ($query) use ($request) {
            $date_range = explode(' to ', $request->range);

            if (count($date_range) == 2) {
                $from = new Carbon($date_range[0]);
                $to = new Carbon($date_range[1]);

                $query->whereBetween('created_at', [$from->startOfDay(), $to->endOfDay()]);
            } else if (count($date_range) == 1) {
                $query->whereDate('created_at', $date_range[0]);
            }

            return $query;
        });
    }

    public static function makeDataTable($source, $trash = false)
    {
        return DataTables::of($source)
            ->addColumn('actions', function (self $row) use ($trash) {
                $actions = ['row' => $row];

                if ($trash) {
                    $actions['restore_route'] = 'device.restore';
                } else {
                    $actions['update_route'] = 'device.edit';
                    $actions['delete_route'] = 'device.destroy';
                }

                return view('admin.datatables.actions', $actions);
            })
            // to fixed yajra weird timezone return
            ->editColumn('created_at', fn($row) => $row->created_at)
            ->editColumn('updated_at', fn($row) => $row->updated_at)
            ->editColumn('deleted_at', fn($row) => $row->deleted_at)
            ->make(true);
    }

    public static function subscribeTopicFCM()
    {
        $topic = config('firebase.topic');

        $devices = self::where(fn ($q) => $q->whereNot('fcm_topic', $topic)->orWhereNull('fcm_topic'))->whereNotNull('fcm_token');

        if ($devices->count()) {
            try {
                Firebase::messaging()->subscribeToTopic($topic, $devices->pluck('fcm_token')->toArray());

                $devices->update(['fcm_topic' => $topic]);
            } catch (\Exception $e) {
                report($e);
            }
        }
    }

    public static function notifyToTopicFCM(?string $title = null, ?string $body = null, bool $throw = false)
    {
        $topic = config('firebase.topic');

        if ($title && $topic) {
            try {
                $notify = Notification::create($title, $body);
                $message = CloudMessage::new()->toTopic($topic)->withNotification($notify);

                Firebase::messaging()->send($message);
            } catch (\Exception $e) {
                report($e);

                if ($throw) {
                    throw $e;
                }
            }
        }

        throw_if($throw && is_null($title), __('Missing a required notification title.'));
        // throw_if($throw && is_null($body), __('Missing a notification message.'));
        throw_if($throw && is_null($topic), __('Missing a default topic/channel for push notification.'));
    }

    public static function notifyViaMulticast(?string $title = null, ?string $body = null, bool $throw = false)
    {
        $topic = config('firebase.topic');

        if ($title) {
            try {
                $devices = self::whereNotNull('fcm_token')->where('fcm_topic', $topic);

                if ($devices->clone()->count()) {
                    $notify = Notification::create($title, $body);
                    $message = CloudMessage::new()->withNotification($notify)->withData([]);

                    Firebase::messaging()->sendMulticast($message, $devices->clone()->pluck('fcm_token')->unique()->values()->toArray());
                }
            } catch (\Exception $e) {
                report($e);

                if ($throw) {
                    throw $e;
                }
            }
        }

        throw_if($throw && is_null($title), __('Missing a required notification title.'));
        // throw_if($throw && is_null($body), __('Missing a notification message.'));
        throw_if($throw && is_null($topic), __('Missing a default topic/channel for push notification.'));
    }
}
