<?php

namespace App\Modules\Bookings\repositories;

use App\Modules\Bookings\Task;
use App\Modules\Bookings\TaskRepositoryInterface;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use App\Models\Status;
use App\Modules\Clients\Client;
use App\Modules\Clients\Customer;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pagination\Paginator;
use App\Models\User;

class TaskRepositoryEloquent implements TaskRepositoryInterface
{
    /**
     * A reference to the task model.
     *
     * @var App\Modules\Bookings\Task
     */
    protected $model;

    /**
     * The maximum number of results the query should return.
     *
     * @var integer
     */
    protected $limit;

    /**
     * The results should be retrieve using simplePaginate
     * or the default lenghtaware paginator.
     *
     * @var boolean
     */
    protected $simple_paginate = false;

    /**
     * Constructor function where the dependency injection
     * magic happens.
     *
     * @param App\Modules\Bookings\Task $model
     */
    public function __construct(\App\Modules\Bookings\Task $model)
    {
        $this->model = $model;
    }

    /**
     * Returns an empty paginator object,
     * with no items in the collection.
     *
     * @param  int $limit
     *
     * @return Illuminate\Pagination\Paginator
     */
    public function getEmptyPaginator($limit = 15)
    {
        return new Paginator([], 0, $limit);
    }

    /**
     * Limit the number of results.
     *
     * @param  integer $limit
     * @return self
     */
    public function limit($limit)
    {
        $this->limit = $limit;
        return $this;
    }

    /**
     * Returns the number of results it should retrieve, aka the limit.
     *
     * @return integer | null
     */
    public function getLimit()
    {
        return request()->get('limit');
    }

    /**
     * Set the results to be retrieved using
     * simplePaginate.
     *
     * @return self
     */
    public function simplePaginate()
    {
        $this->simple_paginate = true;
        return $this;
    }

    /**
     * Set the results to be retrieved using
     * LengthAwarePaginator.
     *
     * @return self
     */
    public function lengthPaginate()
    {
        $this->simple_paginate = false;
        return $this;
    }

    /**
     * Get all the open QA tasks a user has access to that are assigned to no one.
     *
     * @return \Illuminate\Pagination\Paginator
     */
    public function getOpenQATasks()
    {
        $user = User::findOrFail(getAuthUserId());
        $types = [];

        if ($user->hasPermission('read_qa_repairs')) {
            $types[] = 'repair';
        }
        if ($user->hasPermission('read_qa_support')) {
            $types[] = 'support';
        }
        if ($user->hasPermission('read_qa_onsite')) {
            $types[] = 'onsite';
        }

        // Return an empty paginator object if the user
        // has no permissions to access any of the
        // types
        if (count($types) == 0) {
            return $this->getEmptyPaginator();
        }

        // Build up the query and return the results
        // as a paginated class
        $query = $this->model->newQuery()
                    ->select('tasks.*', DB::raw('tmp.type as original_type, tmp.created_by as original_creator_id, tmp.labour as original_labour, FORMAT(SUM(partstmp.price * partstmp.quantity), 2) part_amount_ex_vat, FORMAT(SUM((partstmp.price * partstmp.quantity)*1.2), 2) part_amount_inc_vat'))
                    ->join('tasks as tmp', function ($q) {
                        $q->on('tasks.qa_id', '=', 'tmp.id')->whereNull('tmp.deleted_at');
                    })
                    ->leftJoin('parts as partstmp', function ($q) {
                        $q->on('tasks.qa_id', '=', 'partstmp.related_id');
                    })
                    ->whereNotNull('tasks.qa_id')
                    ->where('tasks.type', 'qa')
                    ->where('tasks.completed', 0)
                    ->whereNull('tasks.assigned_to')
                    ->whereIn('tmp.type', $types)
                    ->groupBy('tasks.id')
                    ->orderByRaw('FORMAT(SUM((partstmp.price * partstmp.quantity)*1.2), 2) DESC, original_labour DESC');

        $this->beforeQuery($query);
        $collection = $query->paginate(request()->get('limit'));
        $this->afterQuery($collection);

        return $collection;
    }

    /**
     * Get all the open QA tasks a user has access to.
     *
     * @return \Illuminate\Pagination\Paginator
     */
    public function getNeedsCallTasks()
    {
        $user = User::findOrFail(getAuthUserId());
        $types = [];

        if ($user->hasPermission('read_qa_repairs')) {
            $types[] = 'repair';
        }
        if ($user->hasPermission('read_qa_support')) {
            $types[] = 'support';
        }
        if ($user->hasPermission('read_qa_onsite')) {
            $types[] = 'onsite';
        }

        // Return an empty paginator object if the user
        // has no permissions to access any of the
        // types
        if (count($types) == 0) {
            return $this->getEmptyPaginator();
        }

        // Build up the query and return the results
        // as a paginated class
        $query = $this->model->newQuery()
                    ->select('tasks.*', DB::raw('tmp.type as original_type, tmp.created_by as original_creator_id'))
                    ->join('tasks as tmp', function ($q) {
                        $q->on('tasks.qa_id', '=', 'tmp.id')->whereNull('tmp.deleted_at');
                    })
                    ->whereNotNull('tasks.qa_id')
                    ->where('tasks.type', 'qa')
                    ->where('tasks.completed', 1)
                    ->where('tasks.needs_calling', true)
                    ->whereIn('tmp.type', $types);

        $this->beforeQuery($query);
        $collection = $query->paginate(request()->get('limit'));
        $this->afterQuery($collection);

        return $collection;
    }

    /**
     * Get all tasks awaiting pickup/collection from customers for the logged-in user.
     * These are tasks that need to be picked up from the customer.
     *
     * @return \Illuminate\Pagination\Paginator
     */
    public function getCollectionsQueue()
    {
        $userId = request()->get('user_id', getAuthUserId());

        $query = $this->model->newQuery()
                    ->where('needs_pickup', 1)
                    ->where('pickup_by', $userId)
                    ->whereNull('picked_up_at')
                    ->whereIn('type', ['repair', 'support', 'onsite'])
                    ->orderBy('created_at', 'asc');

        $this->beforeQuery($query);
        $collection = $query->paginate(request()->get('limit'));
        $this->afterQuery($collection);

        return $collection;
    }

    /**
     * Get all tasks that need delivery back to customers for the logged-in user.
     * These are tasks marked as needing delivery but not yet delivered.
     *
     * @return \Illuminate\Pagination\Paginator
     */
    public function getDeliveriesQueue()
    {
        $userId = request()->get('user_id', getAuthUserId());

        $query = $this->model->newQuery()
                    ->where('needs_delivery', 1)
                    ->where('delivered_by', $userId)
                    ->where('delivered', 0)
                    ->whereIn('type', ['repair', 'support', 'onsite'])
                    ->orderBy('created_at', 'asc');

        $this->beforeQuery($query);
        $collection = $query->paginate(request()->get('limit'));
        $this->afterQuery($collection);

        return $collection;
    }

    /**
     * Get all the untouched repairs prior to a given date.
     *
     * @param  string $date
     *
     * @return \Illuminate\Pagination\Paginator
     */
    public function getUntouchedTasks($date)
    {
        $query = $this->model->newQuery()
                    ->with('user')
                    ->whereNotNull('assigned_to')
                    ->where('completed', 0)
                    ->where('updated_at', '<', $date)
                    ->whereDoesntHave('comments', function ($q) use ($date) {
                            return $q->where('created_at', '>', $date);
                        });

        if (request()->get('type')) {
            $query->where('type', request()->get('type'));
        }

        return $query->paginate(request()->get('limit'));
    }

    /**
     * Get all the open tasks for the given user.
     *
     * @param  integer $user_id
     * @param  array   $columns
     * @return \Illuminate\Pagination\LengthAwarePaginator
     */
    public function getUsersOpenTasks($user_id, $columns = ['*'])
    {
        $query = $this->model->newQuery()
                             ->where('assigned_to', '=', $user_id)
                             ->where('status_id', '!=', Status::TASKS_QUALITY_CONTROL)
                             ->open()
                             ->setEagerLoads([])
                             // ->with('creator')
                             ->leftJoin('severities', 'severities.id', '=', 'tasks.severity_id')
                             ->orderBy('severities.priority', 'desc')
                             ->orderBy('tasks.id', 'asc');

        $results = $this->getResults($query, $columns);
        $this->setAppends($results, []);

        return $results;
    }

    /**
     * Get the unassigned tasks.
     *
     * @param  array  $columns
     * @return \Illuminate\Pagination\LengthAwarePaginator
     */
    public function getUnassignedTasks($columns = ['*'])
    {
        $query = $this->model->newQuery()
                             ->whereNull('assigned_to')
                             ->where('type', '!=', 'qa')
                             ->where('type', '!=', 'follow')
                             ->setEagerLoads([])
                             // ->with('creator')
                             ->open()
                             ->leftJoin('severities', 'severities.id', '=', 'tasks.severity_id')
                             ->orderBy('severities.priority', 'desc')
                             ->orderBy('tasks.id', 'asc');

        $results = $this->getResults($query, $columns);
        $this->setAppends($results, []);

        return $results;
    }

    /**
     * Get all the subscribed tasks for the given user.
     *
     * @param  integer $user_id
     * @param  array   $columns
     * @return \Illuminate\Pagination\LengthAwarePaginator
     */
    public function getUsersSubscribedTasks($user_id, $columns = ['*'])
    {
        $query = $this->model->newQuery()
                             ->open()
                             ->setEagerLoads([])
                             // ->with('creator')
                             ->whereHas('subscribers', function ($q) use ($user_id) {
                                 $q->where('user_id', $user_id);
                             })
                             ->leftJoin('severities', 'severities.id', '=', 'tasks.severity_id')
                             ->orderBy('severities.priority', 'desc')
                             ->orderBy('tasks.id', 'asc');

        $results = $this->getResults($query, $columns);
        $this->setAppends($results, []);

        return $results;
    }

    /**
     * Get all the delivery tasks for the given user.
     *
     * @param  integer $user_id
     * @param  array   $columns
     * @return \Illuminate\Pagination\LengthAwarePaginator
     */
    public function getDeliveryTasksForUser($user_id, $columns = ['*'])
    {
        $query = $this->model->newQuery()
                             ->repairs()
                             ->where('needs_delivery', '=', 1)
                             ->where('delivered', '=', 0)
                             ->setEagerLoads([])
                             ->with('delivery_user', 'user')
                             ->where('delivered_by', '=', $user_id);

        $results = $this->getResults($query, $columns);
        $this->setAppends($results, []);

        return $results;
    }

    /**
     * Fetch the results from the given query.
     *
     * @param  \Illuminate\Database\Eloquent\Builder $query
     * @param  array  $columns
     * @return \Illuminate\Pagination\LengthAwarePaginator
     */
    protected function getResults(Builder $query, array $columns = ['*'])
    {
        if ($this->simple_paginate) {
            return $query->simplePaginate($this->getLimit(), $columns);
        }

        return $query->paginate($this->getLimit(), $columns);
    }

    /**
     * Set the appends for each model in the collection.
     *
     * @param \Illuminate\Database\Eloquent\Collection $results
     * @param array  $appends
     * @return \Illuminate\Database\Eloquent\Collection
     */
    protected function setAppends(&$results, array $appends)
    {
        $results->each(function($result) use ($appends) {
            $result->setAppends($appends);
        });
    }

    /**
     * Parse additional conditions passed during the request.
     *
     * @param  \Illuminate\Database\Eloquent\Builder &$query
     *
     * @return null
     */
    protected function beforeQuery(&$query)
    {
        if (request()->get('creator')) {
            $query->with('creator');
        }

        if (request()->get('user')) {
            $query->with('user');
        }
    }

    /**
     * Parse the customer data, after the query has been executed
     * and lazy load all the customer details.
     *
     * @param  \Illuminate\Pagination\Paginator &$collection
     *
     * @return null
     */
    protected function afterQuery(&$collection)
    {
        // Eager load clients and customers for the tasks in the collection
        if (request()->get('client') && request()->get('client') != 'false') {
            $clients = Client::with('contract')->whereIn('id', $collection->where('client_type', 'client')->pluck('client_id'))->get();
            $customers = Customer::whereIn('id', $collection->where('client_type', 'customer')->pluck('client_id'))->get();
        }

        // Eager load the original creators for the tasks in the collection
        if (request()->get('original_creator') && request()->get('original_creator') != 'false') {
            $original_creators = User::whereIn('id', $collection->pluck('original_creator_id'))->get();
        }

        foreach ($collection as &$task) {

            // Match the clients and customers with each task
            if (isset($clients) && isset($customers)) {
                if ($task->client_type == 'client') {
                    $task->client = $clients->first(function ($client, $key) use ($task) {
                        return $client->id == $task->client_id;
                    });
                }
                else {
                    $task->client = $customers->first(function ($client, $key) use ($task) {
                        return $client->id == $task->client_id;
                    });
                }
            }

            // Match the original creator with the task
            if (isset($original_creators)) {
                $task->original_creator = $original_creators->first(function ($user, $key) use ($task) {
                    return $user->id == $task->original_creator_id;
                });
            }
        }
    }
}
