<?php

namespace App\Queue;

use Illuminate\Contracts\Queue\Queue as QueueContract;
use Illuminate\Contracts\Queue\QueueableCollection;
use Illuminate\Contracts\Queue\QueueableEntity;
use MongoDB\Client;
use Illuminate\Queue\Jobs\Job;

use Illuminate\Container\Container;
use Illuminate\Contracts\Container\Container as ContainerContract;

class MongoDatabaseQueue implements QueueContract
{
    protected $collection;
    protected $defaultQueue;
    protected $connectionName;

    public function setContainer(ContainerContract $container)
    {
        $this->container = $container;
        return $this;
    }

    public function getContainer()
    {
        return $this->container;
    }

    public function getConnectionName()
    {
        return $this->connectionName;
    }

    public function setConnectionName($name)
    {
        $this->connectionName = $name;

        return $this;
    }

    public function __construct(Client $mongoClient, string $collection, string $defaultQueue)
    {
        $this->collection = $mongoClient
            ->selectDatabase(config('database.connections.mongodb.database'))
            ->selectCollection($collection);
        $this->defaultQueue = $defaultQueue;
    }

    public function size($queue = null)
    {
        return $this->collection->countDocuments(['queue' => $queue ?? $this->defaultQueue]);
    }

    public function push($job, $data = '', $queue = null)
    {
        return $this->pushToDatabase($queue ?? $this->defaultQueue, $job, $data);
    }

    public function pushOn($queue, $job, $data = '')
    {
        return $this->push($job, $data, $queue);
    }

    public function later($delay, $job, $data = '', $queue = null)
    {
        return $this->pushToDatabase(
            $queue ?? $this->defaultQueue,
            $job,
            $data,
            now()->addSeconds($this->secondsUntil($delay))
        );
    }

    public function pushRaw($payload, $queue = null, array $options = [])
    {
        $queue = $this->getQueue($queue);

        $this->collection->insertOne([
            'queue' => $queue,
            'payload' => $payload,
            'attempts' => 0,
            'reserved' => false,
            'reserved_at' => null,
            'available_at' => now()->timestamp,
            'created_at' => now()->timestamp,
        ]);

        return true;
    }

    public function laterOn($queue, $delay, $job, $data = '')
    {
        return $this->later($delay, $job, $data, $queue);
    }

    public function bulk($jobs, $data = '', $queue = null)
    {
        foreach ($jobs as $job) {
            $this->push($job, $data, $queue);
        }
    }

    public function pop($queue = null)
    {
        // You’ll need to implement popping logic for processing a job
        // Leave this unimplemented if you're not dispatching jobs via queue:work yet
    }

    protected function pushToDatabase($queue, $job, $data, $availableAt = null)
    {
        return $this->collection->insertOne([
            'queue' => $queue,
            'job' => serialize($job),
            'data' => $data,
            'available_at' => $availableAt ?? now(),
            'created_at' => now(),
        ]);
    }

    protected function secondsUntil($delay)
    {
        return $delay instanceof \DateTimeInterface ? $delay->getTimestamp() - time() : $delay;
    }
}