<?php

namespace BotMan\Drivers\Web;

use BotMan\BotMan\Drivers\Events\GenericEvent;
use BotMan\BotMan\Drivers\HttpDriver;
use BotMan\BotMan\Interfaces\WebAccess;
use BotMan\BotMan\Messages\Attachments\Audio;
use BotMan\BotMan\Messages\Attachments\File;
use BotMan\BotMan\Messages\Attachments\Image;
use BotMan\BotMan\Messages\Attachments\Video;
use BotMan\BotMan\Messages\Incoming\Answer;
use BotMan\BotMan\Messages\Incoming\IncomingMessage;
use BotMan\BotMan\Messages\Outgoing\OutgoingMessage;
use BotMan\BotMan\Messages\Outgoing\Question;
use BotMan\BotMan\Users\User;
use BotMan\Drivers\Web\Extras\TypingIndicator;
use Illuminate\Support\Collection;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class WebDriver extends HttpDriver
{
    const DRIVER_NAME = 'Web';

    const ATTACHMENT_IMAGE = 'image';
    const ATTACHMENT_AUDIO = 'audio';
    const ATTACHMENT_VIDEO = 'video';
    const ATTACHMENT_FILE = 'file';
    const ATTACHMENT_LOCATION = 'location';

    /** @var OutgoingMessage[] */
    protected $replies = [];

    /** @var int */
    protected $replyStatusCode = 200;

    /** @var string */
    protected $errorMessage = '';

    /** @var array */
    protected $messages = [];

    /** @var array */
    protected $files = [];

    /**
     * @param  Request  $request
     */
    public function buildPayload(Request $request)
    {
        $this->payload = $request->request->all();
        $this->event = Collection::make($this->payload);
        $this->files = Collection::make($request->files->all());
        $this->config = Collection::make($this->config->get('web', []));
    }

    /**
     * @param  IncomingMessage  $matchingMessage
     * @return \BotMan\BotMan\Users\User
     */
    public function getUser(IncomingMessage $matchingMessage)
    {
        return new User($matchingMessage->getSender());
    }

    /**
     * Determine if the request is for this driver.
     *
     * @return bool
     */
    public function matchesRequest()
    {
        return Collection::make($this->config->get('matchingData'))->diffAssoc($this->event)->isEmpty();
    }

    /**
     * @param  IncomingMessage  $matchingMessage
     * @return void
     */
    public function types(IncomingMessage $matchingMessage)
    {
        $this->replies[] = [
            'message' => TypingIndicator::create(),
            'additionalParameters' => [],
        ];
    }

    /**
     * Send a typing indicator and wait for the given amount of seconds.
     *
     * @param  IncomingMessage  $matchingMessage
     * @param  float  $seconds
     * @return mixed
     */
    public function typesAndWaits(IncomingMessage $matchingMessage, float $seconds)
    {
        $this->replies[] = [
            'message' => TypingIndicator::create($seconds),
            'additionalParameters' => [],
        ];
    }

    /**
     * @param  IncomingMessage  $message
     * @return \BotMan\BotMan\Messages\Incoming\Answer
     */
    public function getConversationAnswer(IncomingMessage $message)
    {
        $interactive = $this->event->get('interactive', false);
        if (is_string($interactive)) {
            $interactive = ($interactive !== 'false') && ($interactive !== '0');
        } else {
            $interactive = (bool) $interactive;
        }

        return Answer::create($message->getText())
            ->setValue($this->event->get('value', $message->getText()))
            ->setMessage($message)
            ->setInteractiveReply($interactive);
    }

    /**
     * @return bool
     */
    public function hasMatchingEvent()
    {
        $event = false;

        if ($this->event->has('eventData')) {
            $event = new GenericEvent($this->event->get('eventData'));
            $event->setName($this->event->get('eventName'));
        }

        return $event;
    }

    /**
     * Retrieve the chat message.
     *
     * @return array
     */
    public function getMessages()
    {
        if (empty($this->messages)) {
            $message = $this->event->get('message');
            $userId = $this->event->get('userId');
            $sender = $this->event->get('sender', $userId);

            $incomingMessage = new IncomingMessage($message, $sender, $userId, $this->payload);

            $incomingMessage = $this->addAttachments($incomingMessage);

            $this->messages = [$incomingMessage];
        }

        return $this->messages;
    }

    /**
     * @return bool
     */
    public function isBot()
    {
        return false;
    }

    /**
     * @param  string|Question|OutgoingMessage  $message
     * @param  IncomingMessage  $matchingMessage
     * @param  array  $additionalParameters
     * @return Response
     */
    public function buildServicePayload($message, $matchingMessage, $additionalParameters = [])
    {
        if (! $message instanceof WebAccess && ! $message instanceof OutgoingMessage) {
            $this->errorMessage = 'Unsupported message type.';
            $this->replyStatusCode = 500;
        }

        return [
            'message' => $message,
            'additionalParameters' => $additionalParameters,
        ];
    }

    /**
     * @param  mixed  $payload
     * @return Response
     */
    public function sendPayload($payload)
    {
        $this->replies[] = $payload;
    }

    /**
     * @param $messages
     * @return array
     */
    protected function buildReply($messages)
    {
        $replyData = Collection::make($messages)->transform(function ($replyData) {
            $reply = [];
            $message = $replyData['message'];
            $additionalParameters = $replyData['additionalParameters'];

            if ($message instanceof WebAccess) {
                $reply = $message->toWebDriver();
            } elseif ($message instanceof OutgoingMessage) {
                $attachmentData = (is_null($message->getAttachment())) ? null : $message->getAttachment()->toWebDriver();
                $reply = [
                    'type' => 'text',
                    'text' => $message->getText(),
                    'attachment' => $attachmentData,
                ];
            }
            $reply['additionalParameters'] = $additionalParameters;

            return $reply;
        })->toArray();

        return $replyData;
    }

    /**
     * Send out message response.
     */
    public function messagesHandled()
    {
        $messages = $this->buildReply($this->replies);

        // Reset replies
        $this->replies = [];

        (new Response(json_encode([
            'status' => $this->replyStatusCode,
            'messages' => $messages,
        ]), $this->replyStatusCode, [
            'Content-Type' => 'application/json',
            'Access-Control-Allow-Credentials' => true,
            'Access-Control-Allow-Origin' => '*',
        ]))->send();
    }

    /**
     * @return bool
     */
    public function isConfigured()
    {
        return false;
    }

    /**
     * Low-level method to perform driver specific API requests.
     *
     * @param  string  $endpoint
     * @param  array  $parameters
     * @param  \BotMan\BotMan\Messages\Incoming\IncomingMessage  $matchingMessage
     * @return void
     */
    public function sendRequest($endpoint, array $parameters, IncomingMessage $matchingMessage)
    {
        // Not available with the web driver.
    }

    /**
     * Add potential attachments to the message object.
     *
     * @param  IncomingMessage  $incomingMessage
     * @return IncomingMessage
     */
    protected function addAttachments($incomingMessage)
    {
        $attachment = $this->event->get('attachment');

        if ($attachment === self::ATTACHMENT_IMAGE) {
            $images = $this->files->map(function ($file) {
                if ($file instanceof UploadedFile) {
                    $path = $file->getRealPath();
                } else {
                    $path = $file['tmp_name'];
                }

                return new Image($this->getDataURI($path));
            })->values()->toArray();
            $incomingMessage->setText(Image::PATTERN);
            $incomingMessage->setImages($images);
        } elseif ($attachment === self::ATTACHMENT_AUDIO) {
            $audio = $this->files->map(function ($file) {
                if ($file instanceof UploadedFile) {
                    $path = $file->getRealPath();
                } else {
                    $path = $file['tmp_name'];
                }

                return new Audio($this->getDataURI($path));
            })->values()->toArray();
            $incomingMessage->setText(Audio::PATTERN);
            $incomingMessage->setAudio($audio);
        } elseif ($attachment === self::ATTACHMENT_VIDEO) {
            $videos = $this->files->map(function ($file) {
                if ($file instanceof UploadedFile) {
                    $path = $file->getRealPath();
                } else {
                    $path = $file['tmp_name'];
                }

                return new Video($this->getDataURI($path));
            })->values()->toArray();
            $incomingMessage->setText(Video::PATTERN);
            $incomingMessage->setVideos($videos);
        } elseif ($attachment === self::ATTACHMENT_FILE) {
            $files = $this->files->map(function ($file) {
                if ($file instanceof UploadedFile) {
                    $path = $file->getRealPath();
                } else {
                    $path = $file['tmp_name'];
                }

                return new File($this->getDataURI($path));
            })->values()->toArray();
            $incomingMessage->setText(File::PATTERN);
            $incomingMessage->setFiles($files);
        }

        return $incomingMessage;
    }

    /**
     * @param $file
     * @param  string  $mime
     * @return string
     */
    protected function getDataURI($file, $mime = '')
    {
        return 'data: '.(function_exists('mime_content_type') ? mime_content_type($file) : $mime).';base64,'.base64_encode(file_get_contents($file));
    }
}
