<?php

namespace Directus\Services;

use Directus\Database\Schema\SchemaManager;
use Directus\Database\TableGateway\DirectusUsersTableGateway;
use Directus\Exception\UnprocessableEntityException;
use Directus\Mail\Message;
use Directus\Permissions\Acl;
use function Directus\send_mail_with_layout;
use Directus\Util\ArrayUtils;
use Directus\Validator\Validator;
use Zend\Db\Sql\Predicate\In;

class MailService extends AbstractService
{
    public function send(array $data)
    {
        $this->validate($data, [
            'type' => 'string',
            'subject' => 'string',
            'body' => 'required|string',
            'data' => 'array',
            'to' => 'required'
        ]);

        $subject = ArrayUtils::get($data, 'subject');
        $toAddresses = $this->getToAddresses(ArrayUtils::get($data, 'to'));

        send_mail_with_layout(
            'base.twig',
            $data['body'],
            (array) ArrayUtils::get($data, 'data'),
            $data['type'] == 'plain' ? 'text/plain' : 'text/html',
            function (Message $message) use ($data, $toAddresses, $subject) {
                $message->setFrom($this->getFrom());
                $message->setTo($toAddresses);
                $message->setSubject($subject);
            }
        );
    }

    /**
     * Returns the list of 'to' addresses
     *
     * @param $addresses
     *
     * @return array
     */
    protected function getToAddresses($addresses)
    {
        if (!is_array($addresses) || !ArrayUtils::isNumericKeys($addresses)) {
            $addresses = [$addresses];
        }

        $this->validateAddresses($addresses);

        return $this->parseAddresses($addresses);
    }

    /**
     * Validate a list of addresses
     *
     * @param array $addresses
     *
     * @throws UnprocessableEntityException
     */
    protected function validateAddresses(array $addresses)
    {
        $validator = new Validator();
        foreach ($addresses as $address) {
            if (is_array($address) && (!isset($address['name']) || !isset($address['email']))) {
                throw new UnprocessableEntityException(
                    'Addressee if an array, it must have a name and an email attribute'
                );
            }

            if (is_string($addresses)) {
                $violations = $validator->validate($addresses, ['required|email']);
                if ($violations->count() > 0) {
                    throw new UnprocessableEntityException(
                        'Invalid email'
                    );
                }
            }

            if (!is_numeric($address) && !is_string($address) && !is_array($address)) {
                throw new UnprocessableEntityException(
                    'Address must be a string, number or an array'
                );
            }
        }
    }

    /**
     * Parses a list of address into
     *
     * @param array $addresses
     *
     * @return array
     *
     * @throws UnprocessableEntityException
     */
    protected function parseAddresses(array $addresses)
    {
        $ids = [];
        foreach ($addresses as $i => $address) {
            if (is_numeric($address)) {
                $ids[] = $address;
                unset($addresses[$i]);
            } else if (is_array($address)) {
                unset($addresses[$i]);
                $addresses[ArrayUtils::get($address, 'email')] = ArrayUtils::get($address, 'name');
            }
        }

        if ($ids) {
            $users = $this->fetchItems(
                SchemaManager::COLLECTION_USERS,
                ['email', 'first_name', 'last_name'],
                [new In('id', $ids), 'status' => DirectusUsersTableGateway::STATUS_ACTIVE]
            );

            if ($users->count() !== count($ids)) {
                throw new UnprocessableEntityException(
                    'Unable to find some users'
                );
            }

            foreach ($users as $user) {
                $name = [];
                if ($user->first_name) {
                    $name[] = $user->first_name;
                }

                if ($user->last_name) {
                    $name[] = $user->last_name;
                }

                if ($name) {
                    $addresses[$user->email] = implode(' ', $name);
                } else {
                    $addresses[] = $user->email;
                }
            }
        }

        return $addresses;
    }

    /**
     * Returns the authenticated user address
     *
     * @return array
     */
    protected function getFrom()
    {
        /** @var Acl $acl */
        $acl = $this->container->get('acl');

        return [
            $acl->getUserEmail() => $acl->getUserFullName()
        ];
    }
}
