<?php

namespace PeytzMail;

use PeytzMail\Client;
use PeytzMail\Entity\Events;
use PeytzMail\Entity\FieldEvents;
use PeytzMail\Entity\Mailinglist;
use PeytzMail\Entity\Newsletter;
use PeytzMail\Entity\Recipient;
use PeytzMail\Entity\Subscriber;
use PeytzMail\Entity\TrackingEvents;
use PeytzMail\Entity\TriggerMail;
use PeytzMail\Exception\InvalidArgumentException;
use PeytzMail\Exception\NoResultException;
use PeytzMail\Exception\RuntimeException;

/**
 * @package PeytzMail
 */
class PeytzMail
{
    protected $client;

    /**
     * @param Client $client
     */
    public function __construct(Client $client)
    {
        $this->client = $client;
    }

    /**
     * Get a subscriber by its id.
     *
     * @param  string     $subscriberId
     * @return Subscriber
     */
    public function getSubscriber($subscriberId)
    {
        $path = sprintf('/api/v1/subscribers/%s.json', $subscriberId);
        $content = $this->client->getContent($path, $subscriberId);

        $subscriber = new Subscriber;
        $subscriber->createFromJson($content);

        return $subscriber;
    }

    /**
     * Updates a subscriber by Peytz Mail subscriber id without changing any mailinglists subscriptions.
     *
     * @param  Subscriber               $subscriber
     * @throws InvalidArgumentException
     * @return Subscriber
     */
    public function updateSubscriber(Subscriber $subscriber)
    {
        if (!isset($subscriber->id)) {
            throw new InvalidArgumentException(
                '[Peytz Mail] The "id" property is mandatory for updating a subscriber.'
            );
        }

        $subscriber->prepare();
        $data = array(
            'subscriber' => $subscriber,
        );

        $path = sprintf('/api/v1/subscribers/%s.json', $subscriber->id);
        $content = $this->client->getContent($path, $data, 'PUT');

        $subscriber->createFromJson($content);

        return $subscriber;
    }

    /**
     * Add a subscriber or update if existing.
     *
     * @param  Subscriber               $subscriber
     * @throws InvalidArgumentException
     * @return Subscriber
     */
    public function addSubscriber(Subscriber $subscriber)
    {
        if (!isset($subscriber->email)) {
            throw new InvalidArgumentException(
                '[Peytz Mail] The "email" property is mandatory for adding or updating a subscriber.'
            );
        }

        $subscriber->prepare();
        $data = array(
            'subscriber' => $subscriber,
        );

        $content = $this->client->getContent('/api/v1/subscribers.json', $data, 'POST');

        $subscriber->createFromJson($content);

        return $subscriber;
    }

    /**
     * Add a set of subscribers or updte if existing.
     * Obs.: you cannot add custom properties.
     *
     * @param  Subscriber[]             $subscribers
     * @throws InvalidArgumentException
     * @return Subscriber[]
     */
    public function addSubscribers(array $subscribers)
    {
        foreach ($subscribers as $subscriber) {
            if (!$subscriber instanceof Subscriber) {
                throw new InvalidArgumentException(
                    '[Peytz Mail] At least one subscriber is not a Subscriber entity.'
                );
            }

            if (!isset($subscriber->email)) {
                throw new InvalidArgumentException(
                    '[Peytz Mail] The "email" property is mandatory for adding or updating a subscriber.'
                );
            }

            $subscriber->prepare();
            $data['subscribers'][] = $subscriber;
        }

        $content = $this->client->getContent('/api/v1/subscribers.json', $data, 'POST');
        $content = json_decode($content);

        $subscribers = array();

        foreach ($content->subscribers as $subscriber) {
            $newSubscriber = new Subscriber;
            $newSubscriber->createFromObject($subscriber);

            $subscribers[] = $newSubscriber;
        }

        return $subscribers;
    }

    /**
     * Find subscribers who matches given criteria.
     *
     * @param  array             $criteria The associative key-value array.
     * @throws NoResultException
     * @return Subscriber[]
     */
    public function findSubscribers(array $criteria)
    {
        $path = '/api/v1/subscribers/search.json?';

        foreach ($criteria as $key => $value) {
            $path = sprintf('%scriteria[%s]=%s&', $path, $key, $value);
        }

        $content = $this->client->getContent($path);
        $content = json_decode($content);

        // FIXME: need to implement pagination if it returns a lot of subscribers

        if (empty($content->subscribers)) {
            throw new NoResultException('[Peytz Mail] No subscribers match your criteria.');
        }

        foreach ($content->subscribers as $subscriber) {
            $newSubscriber = new Subscriber;
            $newSubscriber->createFromObject($subscriber);

            $subscribers[] = $newSubscriber;
        }

        return $subscribers;
    }

    /**
     * Get paginated list subscriber events of a mailinglist.
     * Change parameter is a kind of a filter.
     * Sorting is an associative array (remember from_id override period_start & period_end).
     *
     * @param  string                   $mailinglistId
     * @param  array                    $changes
     * @param  array                    $sortings
     * @param  integer                  $page
     * @throws InvalidArgumentException
     * @return Events
     */
    public function getSubscriberEvents($mailinglistId, array $changes = array(), array $sortings = array(), $page = 1)
    {
        $path = sprintf('/api/v1/subscriber_events/mailinglists/%s.json?page=%d&', $mailinglistId, $page);

        foreach ($changes as $change) {
            if (!in_array($change, array('subscribe', 'unsubscribe', 'click', 'open'))) {
                throw new InvalidArgumentException('[Peytz Mail] Change parameter should be subscribe, unsubscribe, click or open.');
            }

            $path = sprintf('%schanges[]=%s&', $path, $change);
        }

        foreach ($sortings as $type => $value) {
            if (!in_array($type, array('from_id', 'period_start', 'period_end'))) {
                throw new InvalidArgumentException('[Peytz Mail] Sorting parameter should be from_id, period_start and/or period_end.');
            }
        }

        $content = $this->client->getContent(sprintf('%s%s', $path, http_build_query($sortings)));

        $events = new Events;
        $events->createFromJson($content);

        return $events;
    }

    /**
     * Get paginated list of events related to subscriber events.
     * Change parameter is a kind of a filter.
     * Sorting is an associative array (remember from_id override period_start & period_end).
     *
     * @param  array                    $changes
     * @param  array                    $sortings
     * @param  integer                  $page
     * @throws InvalidArgumentException
     * @return Events
     */
    public function getSubscriberFieldEvents(array $changes = array(), array $sortings = array(), $page = 1)
    {
        $path = sprintf('/api/v1/subscriber_events.json?page=%d&', $page);

        foreach ($changes as $change) {
            if (!in_array($change, array('update', 'create'))) {
                throw new InvalidArgumentException('[Peytz Mail] Change parameter should be update or create.');
            }

            $path = sprintf('%schanges[]=%s&', $path, $change);
        }

        foreach ($sortings as $type => $value) {
            if (!in_array($type, array('from_id', 'period_start', 'period_end'))) {
                throw new InvalidArgumentException('[Peytz Mail] Sorting parameter should be from_id, period_start and/or period_end.');
            }
        }

        $content = $this->client->getContent(sprintf('%s%s', $path, http_build_query($sortings)));

        $events = new FieldEvents;
        $events->createFromJson($content);

        return $events;
    }

    /**
     * Gets a paginated list of subscriber tracking events.
     * Event parameter can only be subscribes or unsubscribes.
     * Types is kind of a filter og the this Event name.
     * Sorting is an associative array (remember from_id override period_start & period_end).
     *
     * @param  string                   $mailinglistId
     * @param  string                   $event
     * @param  array                    $types
     * @param  array                    $sortings
     * @param  integer                  $page
     * @throws InvalidArgumentException
     * @return TrackingEvents
     */
    public function getSubscriberTrackingEvents($mailinglistId, $event, array $types = array(), array $sortings = array(), $page = 1)
    {
        $path = sprintf('/api/v1/tracks/%s.json?mailinglist_id=%s&page=%d&', $event, $mailinglistId, $page);

        switch ($event) {
            case 'unsubscribes':
                $validTypes = array('backend', 'blacklist', 'complaint', 'frontend', 'soft', 'hard', 'profile', 'api');
                break;
            case 'subscribes':
                $validTypes = array('backend', 'confirm', 'frontend', 'profile', 'api');
                break;
            default:
                throw new InvalidArgumentException('[Peytz Mail] Event parameter should be either unsubscribes or subscribes.');
        }

        foreach ($types as $type) {
            if (!in_array($type, $validTypes)) {
                throw new InvalidArgumentException(
                    sprintf('[Peytz Mail] Types parameter should be %s.', implode(', ', $validTypes))
                );
            }

            $path = sprintf('%stypes[]=%s&', $path, $type);
        }

        foreach ($sortings as $type => $value) {
            if (!in_array($type, array('from_id', 'period_start', 'period_end'))) {
                throw new InvalidArgumentException('[Peytz Mail] Sorting parameter should be from_id, period_start and/or period_end.');
            }
        }

        $content = $this->client->getContent(sprintf('%s%s', $path, http_build_query($sortings)));

        $trackingEvents = new TrackingEvents;
        $trackingEvents->createFromJson($content, $event);

        return $trackingEvents;
    }

    /**
     * Gets a paginated list of user event tracking events.
     * Action parameter can only be clicks or opens.
     * NewsletterIds is an array where to look after those tracking events.
     * Sorting is an associative array (remember from_id override period_start & period_end).
     *
     * @param  string                   $mailinglistId
     * @param  string                   $event
     * @param  array                    $newsletterIds
     * @param  array                    $sortings
     * @param  integer                  $page
     * @throws InvalidArgumentException
     * @return TrackingEvents
     */
    public function getUserEventTrackingEvents($mailinglistId, $event, array $newsletterIds = array(), array $sortings = array(), $page = 1)
    {
        if (!in_array($event, array('clicks', 'opens'))) {
            throw new InvalidArgumentException('[Peytz Mail] Event parameter should be either clicks or opens.');
        }

        $path = sprintf('/api/v1/tracks/%s.json?mailinglist_id=%s&page=%d&', $event, $mailinglistId, $page);

        foreach ($newsletterIds as $newsletterId) {
            $path = sprintf('%snewsletter_ids[]=%s&', $path, $newsletterId);
        }

        foreach ($sortings as $type => $value) {
            if (!in_array($type, array('from_id', 'period_start', 'period_end'))) {
                throw new InvalidArgumentException('[Peytz Mail] Sorting parameter should be from_id, period_start and/or period_end.');
            }
        }

        $content = $this->client->getContent(sprintf('%s%s', $path, http_build_query($sortings)));

        $trackingEvents = new TrackingEvents;
        $trackingEvents->createFromJson($content, $event);

        return $trackingEvents;
    }

    /**
     * Create a new mailing list.
     * Need the hightest level of privileges to use this method.
     *
     * @param  Mailinglist $mailinglist
     * @return Mailinglist
     */
    public function createMailinglist(Mailinglist $mailinglist)
    {
        if (!property_exists($mailinglist, 'title')
        || !property_exists($mailinglist, 'defaultTemplate')
        || !property_exists($mailinglist, 'sendConfirmationMail')
        || !property_exists($mailinglist, 'sendWelcomeMail')
        ) {
            throw new InvalidArgumentException(
                '[Peytz Mail] title, defaultTemplate, sendConfirmationMail, sendWelcomeMail properties are mandatory.'
            );
        }

        $mailinglist->prepare();
        $content = $this->client->getContent('/api/v1/mailinglists.json', $mailinglist, 'POST');

        $mailinglist = new Mailinglist;
        $mailinglist->createFromJson($content);

        return $mailinglist;
    }

    /**
     * Get a mailing list by its id.
     *
     * @param  string      $mailinglistId
     * @return Mailinglist
     */
    public function getMailinglist($mailinglistId)
    {
        $path = sprintf('/api/v1/mailinglists/%s.json', $mailinglistId);
        $content = $this->client->getContent($path, $mailinglistId);

        $mailinglist = new Mailinglist;
        $mailinglist->createFromJson($content);

        return $mailinglist;
    }

    /**
     * Get customer's mailing lists.
     *
     * @return Mailinglist[]
     */
    public function getMailinglists()
    {
        $content = $this->client->getContent('/api/v1/mailinglists.json');
        $content = json_decode($content);

        if (empty($content->mailinglists)) {
            throw new NoResultException('[Peytz Mail] This customer has not mailing lists.');
        }

        foreach ($content->mailinglists as $mailinglist) {
            $newMailinglist = new Mailinglist;
            $newMailinglist->createFromObject($mailinglist);

            $mailinglists[] = $newMailinglist;
        }

        return $mailinglists;
    }

    /**
     * Subscribe to one or many mailing lists listed by its ids.
     *
     * @param  Subscriber       $subscriber     The Subscriber to subscribe.
     * @param  array            $mailinglistIds List of mailing list ids.
     * @param  array            $extra          Optional extra parameters.
     * @throws RuntimeException                 If the result is not "ok", "created" or "accepted".
     * @return \stdClass
     */
    public function subscribeToMailinglists(Subscriber $subscriber, array $mailinglistIds, array $extra = array())
    {
        $subscriber->prepare();
        $data = array(
            'subscribe' => array(
                'subscriber'      => $subscriber,
                'mailinglist_ids' => $mailinglistIds,
            )
        );

        $data['subscribe'] = array_merge($data['subscribe'], $extra);

        $content = $this->client->getContent('/api/v1/mailinglists/subscribe.json', $data, 'POST');
        $content = json_decode($content);

        if (!in_array($content->result, array('ok', 'created', 'accepted',))) {
            throw new RuntimeException(sprintf('[Peytz Mail] %s', $content->message));
        }

        return $content;
    }

    /**
     * Unsubscribe to one or many mailing lists listed by its ids.
     *
     * @param  Subscriber       $subscriber     The Subscriber to unsubscribe.
     * @param  array            $mailinglistIds List of mailing list ids.
     * @throws RuntimeException                 If the result is not "ok".
     * @return \stdClass
     */
    public function unsuscribeFromMailinglists(Subscriber $subscriber, array $mailinglistIds)
    {
        $data = array(
            'unsubscribe' => array(
                'mailinglist_ids' => $mailinglistIds,
                'email'           => $subscriber->email,
            )
        );

        $content = $this->client->getContent('/api/v1/mailinglists/unsubscribe.json', $data, 'POST');
        $content = json_decode($content);

        if ('ok' !== $content->result) {
            throw new RuntimeException(sprintf('[Peytz Mail] %s', $content->message));
        }

        return $content;
    }

    /**
     * Unsubscribe to a specific mailing list.
     *
     * @param  Subscriber       $subscriber  The Subscriber to unsubscribe.
     * @param  Mailinglist      $mailinglist The Mailing list to unsubscribe from.
     * @throws RuntimeException              If the result is not "ok".
     * @return \stdClass
     */
    public function unsuscribeFromMailinglist(Subscriber $subscriber, Mailinglist $mailinglist)
    {
        $path = sprintf('/api/v1/mailinglists/%s/subscribers/%s.json', $mailinglist->id, $subscriber->id);

        $content = $this->client->getContent($path, null, 'DELETE');
        $content = json_decode($content);

        if ('ok' !== $content->result) {
            throw new RuntimeException(sprintf('[Peytz Mail] %s', $content->message));
        }

        return $content;
    }

    /**
     * Get subscription history of a mailing list.
     * Not implemented yet - coming soon.
     *
     * @param  Mailinglist      $mailinglist
     * @param  array            $events
     * @throws RuntimeException
     * @return string
     */
    public function subscriptionHistory(Mailinglist $mailinglist, array $events)
    {
        throw new RuntimeException('[Peytz Mail] Not implemented yet.');

        $path = sprintf('/api/v1/permissions/history.json?mailinglist_id=%s&', $mailinglist->id);

        foreach ($events as $event) {
            $path = sprintf('%sevents[]=%s&', $path, $event);
        }

        return $this->client->getContent($path);
    }

    /**
     * Get statistics about a mailing list.
     * Not implemented yet - coming soon.
     *
     * @param  Mailinglist      $mailinglist
     * @throws RuntimeException
     * @return string
     */
    public function getStatistics(Mailinglist $mailinglist)
    {
        throw new RuntimeException('[Peytz Mail] Not implemented yet.');

        $path = sprintf('/api/v1/mailinglists/%s/statistics.json', $mailinglist->id);

        return $this->client->getContent($path);
    }

    /**
     * Get a trigger mail by its id.
     *
     * @param  string      $triggerMailId
     * @return TriggerMail
     */
    public function getTriggerMail($triggerMailId)
    {
        $path = sprintf('/api/v1/trigger_mails/%s.json', $triggerMailId);
        $content = $this->client->getContent($path);

        $triggerMail = new TriggerMail;
        $triggerMail->createFromJson($content);

        return $triggerMail;
    }

    /**
     * Create and send a trigger mail to a list of recipients.
     *
     * @param  TriggerMail   $triggerMail
     * @param  Recipient[]   $recipients
     * @return TriggerMail[]
     */
    public function createAndSendTriggerMail(TriggerMail $triggerMail, array $recipients)
    {
        $triggerMail->prepare();
        $data['trigger_mail'] = $triggerMail;
        $data['trigger_mail']->recipients = array();

        foreach ($recipients as $recipient) {
            if (!$recipient instanceof Recipient) {
                throw new InvalidArgumentException(
                    '[Peytz Mail] At least one recipent is not a Recipient entity.'
                );
            }

            $recipient->prepare();
            $data['trigger_mail']->recipients[] = $recipient;
        }

        $content = $this->client->getContent('/api/v1/trigger_mails.json', $data, 'POST');
        $content = json_decode($content);

        $triggerMails = array();
        foreach ($content->trigger_mails as $triggerMail) {
            $thisTriggerMail = new TriggerMail;
            $thisTriggerMail->createFromObject($triggerMail);

            $triggerMails[] = $thisTriggerMail;
        }

        return $triggerMails;
    }

    /**
     * Finds a newsletter and shows its informations and status.
     *
     * @param  Mailinglist $mailinglist
     * @param  string      $newsletterId
     * @return Newsletter
     */
    public function getNewsletter(Mailinglist $mailinglist, $newsletterId)
    {
        $path = sprintf('/api/v1/mailinglists/%s/newsletters/%s.json', $mailinglist->id, $newsletterId);
        $content = $this->client->getContent($path);

        $newsletter = new Newsletter;
        $newsletter->createFromJson($content);

        return $newsletter;
    }

    /**
     * Creates and sends a newsletter without testing it.
     *
     * @param  Newsletter  $newsletter
     * @param  Mailinglist $mailinglist
     * @return boolean
     */
    public function createAndSendNewsletter(Newsletter $newsletter, Mailinglist $mailinglist)
    {
        $newsletter->prepare();
        $data['newsletter'] = $newsletter;

        $url = sprintf('/api/v1/mailinglists/%s/newsletters/create_and_send.json', $mailinglist->id);
        $content = $this->client->getContent($url, $data, 'POST');
        $newsletter->createFromJson($content);

        return true;
    }
}
