<?php

namespace App\Console\Commands;

use App\Album;
use App\Facade\UserConfig;
use App\Photo;
use App\QueueItem;
use App\Services\PhotoService;
use App\Services\RabbitMQService;
use App\User;
use App\UserActivity;
use Illuminate\Console\Command;

class ProcessQueueCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'bt-queue:process';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Processes items in the processing queue.';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        if (!UserConfig::isImageProcessingQueueEnabled())
        {
            $this->output->error('The image processing queue is not enabled');
        }

        $rabbitmq = new RabbitMQService();

        $this->output->writeln('Monitoring queue');

        $rabbitmq->waitOnQueue([$this, 'processQueueItem']);
    }

    /**
     * Processes a single item from the queue.
     *
     * @param $msg
     * @return void
     */
    public function processQueueItem($msg)
    {
        $queueItemID = intval($msg->body);

        $this->output->writeln(sprintf('Processing queue item %d', $queueItemID));

        /** @var QueueItem $queueItem */
        $queueItem = QueueItem::where('id', $queueItemID)->first();
        if (is_null($queueItem))
        {
            $this->output->writeln('Queue item does not exist; skipping');
            $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
            return;
        }

        try
        {
            switch (strtolower($queueItem->action_type))
            {
                case 'photo.analyse':
                    $this->processPhotoAnalyseMessage($queueItem);
                    break;

                case 'photo.bulk_action.change_album':
                case 'photo.bulk_action.delete':
                case 'photo.bulk_action.flip_both':
                case 'photo.bulk_action.flip_horizontal':
                case 'photo.bulk_action.flip_vertical':
                case 'photo.bulk_action.refresh_thumbnails':
                case 'photo.bulk_action.rotate_left':
                case 'photo.bulk_action.rotate_right':
                    $this->processPhotoBulkActionMessage($queueItem);
                    break;

                default:
                    $this->output->writeln(sprintf('Action %s is not recognised, skipping', $queueItem->action_type));
                    return;
            }

            $queueItem->completed_at = new \DateTime();
            $queueItem->save();
        }
        catch (\Exception $ex)
        {
            $this->output->error($ex->getMessage());
            $queueItem->error_message = $ex->getMessage();
        }
        finally
        {
            $queueItem->completed_at = new \DateTime();
            $queueItem->save();

            $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
        }
    }

    private function createActivityRecord(Photo $photo, $type, $userID, $activityDateTime = null)
    {
        if (is_null($activityDateTime))
        {
            $activityDateTime = new \DateTime();
        }

        $userActivity = new UserActivity();
        $userActivity->user_id = $userID;
        $userActivity->activity_at = $activityDateTime;
        $userActivity->type = $type;
        $userActivity->photo_id = $photo->id;
        $userActivity->save();
    }

    private function processPhotoAnalyseMessage(QueueItem $queueItem)
    {
        $this->output->writeln(sprintf('Analysing photo ID %l (batch: %s)', $queueItem->photo_id, $queueItem->batch_reference));

        /** @var Photo $photo */
        $photo = $queueItem->photo;
        if (is_null($photo))
        {
            $this->output->writeln('Photo does not exist; skipping');
            return;
        }

        /* IF CHANGING THIS LOGIC, ALSO CHECK PhotoController::analyse */
        $photoService = new PhotoService($photo);
        $photoService->analyse($queueItem->batch_reference);

        // Log an activity record for the user's feed (remove an existing one as the date may have changed)
        $this->removeExistingActivityRecords($photo, 'photo.taken');

        if (!is_null($photo->taken_at))
        {
            // Log an activity record for the user's feed
            $this->createActivityRecord($photo, 'photo.taken', $queueItem->user_id, $photo->taken_at);
        }

        $queueItem->is_successful = true;
    }

    private function processPhotoBulkActionMessage(QueueItem $queueItem)
    {
        $action = str_replace('photo.bulk_action.', '', $queueItem->action_type);
        $this->output->writeln(sprintf('Apply action \'%s\' to photo ID %l (batch: %s)', $action, $queueItem->photo_id, $queueItem->batch_reference));

        /** @var Photo $photo */
        $photo = $queueItem->photo;
        if (is_null($photo))
        {
            $this->output->writeln('Photo does not exist; skipping');
            return;
        }

        /** @var User $user */
        $user = $queueItem->user;
        if (is_null($user))
        {
            $this->output->writeln('User does not exist; skipping');
            return;
        }

        $photoService = new PhotoService($photo);

        switch (strtolower($action))
        {
            /* This needs a bit more work - we need to also store the new album ID in the queue_items table */
            case 'change_album':
                if ($user->can('change-metadata', $photo))
                {
                    $newAlbum = Album::where('id', intval($queueItem->new_album_id))->first();
                    if (is_null($newAlbum) || !$user->can('upload-photos', $newAlbum))
                    {
                        $this->output->writeln('Target album does not exist or user does not have permission.');
                        return;
                    }

                    $this->output->writeln(sprintf('Moving photo to album \'%s\'', $newAlbum->name));
                    $photoService->changeAlbum($newAlbum);
                }
                break;

            case 'delete':
                if ($user->can('delete', $photo))
                {
                    $photoService->delete();
                }
                break;

            case 'flip_both':
                if ($user->can('manipulate', $photo))
                {
                    $photoService->flip(true, true);
                }
                break;

            case 'flip_horizontal':
                if ($user->can('manipulate', $photo))
                {
                    $photoService->flip(true, false);
                }
                break;

            case 'flip_vertical':
                if ($user->can('manipulate', $photo))
                {
                    $photoService->flip(false, true);
                }
                break;

            case 'refresh_thumbnails':
                if ($user->can('change-metadata', $photo))
                {
                    $photoService->regenerateThumbnails();
                }
                break;

            case 'rotate_left':
                if ($user->can('manipulate', $photo))
                {
                    $photoService->rotate(90);
                }
                break;

            case 'rotate_right':
                if ($user->can('manipulate', $photo))
                {
                    $photoService->rotate(270);
                }
                break;

            default:
                $this->output->writeln(sprintf('Action \'%s\' not recognised; skipping'));
                return;
        }
    }

    private function removeExistingActivityRecords(Photo $photo, $type)
    {
        $existingFeedRecords = UserActivity::where([
            'user_id' => $photo->user_id,
            'photo_id' => $photo->id,
            'type' => $type
        ])->get();

        foreach ($existingFeedRecords as $existingFeedRecord)
        {
            $existingFeedRecord->delete();
        }
    }
}