<?php

namespace UserBundle\Repository;

use AppBundle\Model\SortingOptions;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\QueryBuilder;
use UserBundle\Entity\Notification\Notification;
use UserBundle\Entity\Recipient\AbstractRecipient;
use UserBundle\Entity\Subscription\AbstractSubscription;
use UserBundle\Entity\User;
use UserBundle\Enum\NotificationTypeEnum;
use UserBundle\Enum\StatusFilterEnum;

/**
 * NotificationRepository
 *
 * This class was generated by the Doctrine ORM. Add your own custom
 * repository methods below.
 */
class NotificationRepository extends EntityRepository
{

    /**
     * @param integer              $id   A Notification entity instance.
     * @param NotificationTypeEnum $type A NotificationTypeEnum instance.
     *
     * @return Notification|null
     */
    public function get($id, NotificationTypeEnum $type = null)
    {
        $expr = $this->_em->getExpressionBuilder();

        $condition = $expr->andX(
            $expr->eq('Notification.id', ':id')
        );

        $parameters = new ArrayCollection([
            new Parameter('id', $id),
        ]);

        if ($type !== null) {
            $condition->add($expr->eq(
                'Notification.notificationType',
                ':type'
            ));
            $parameters[] = new Parameter('type', (string) $type);
        }

        return $this->createQueryBuilder('Notification')
//            ->addSelect('Feed, Chart, Owner, Recipient')
            ->addSelect('Feed, Owner, Recipient')
            ->leftJoin('Notification.feeds', 'Feed')
//            ->leftJoin('Notification.charts', 'Chart')
            ->leftJoin('Notification.owner', 'Owner')
            ->leftJoin('Notification.recipients', 'Recipient')
            ->where($condition)
            ->setParameters($parameters)
            ->getQuery()
            ->getOneOrNullResult();
    }

    /**
     * Get notification instance for sending.
     *
     * @param integer $id A Notification entity id.
     *
     * @return Notification|null
     */
    public function getForSending($id)
    {
        return $this->createQueryBuilder('Notification')
            ->addSelect(
                'partial Feed.{id, name}, Owner, Recipient',
                'partial Schedule.{id}'
            )
            ->leftJoin('Notification.feeds', 'Feed')
            ->leftJoin('Notification.owner', 'Owner')
            ->leftJoin('Notification.recipients', 'Recipient')
            ->leftJoin('Notification.schedules', 'Schedule')
            ->where('Notification.id = :id')
            ->setParameter('id', $id)
            ->getQuery()
            ->getOneOrNullResult();
    }

    /**
     * @return QueryBuilder
     */
    public function getQueryBuilderForSubscription()
    {
        return $this->createQueryBuilder('Notification')
            ->addSelect('partial Owner.{id, email}')
            ->join('Notification.owner', 'Owner');
    }

    /**
     * Get query builder for fetching available notifications for forms.
     *
     * @param User $user A User entity instance, who ask.
     *
     * @return QueryBuilder
     */
    public function getQueryBuilderForForm(User $user)
    {
        $expr = $this->_em->getExpressionBuilder();

        return $this->createQueryBuilder('Notification')
            ->where($expr->orX(
                $expr->eq('Notification.owner', ':user'),
                $expr->andX(
                    $expr->eq('Notification.published', 1),
                    $expr->eq('Notification.billingSubscription', ':subscription')
                )
            ))
            ->setParameter('user', $user->getId())
            ->setParameter('subscription', $user->getBillingSubscription()->getId());
    }

    /**
     * @param AbstractRecipient $recipient      Requested AbstractRecipient
     *                                          entity instance.
     * @param User              $owner          A User entity id.
     * @param SortingOptions    $sortingOptions A SortingOptions instance.
     * @param StatusFilterEnum  $statusFilter   A StatusFilterEnum instance.
     * @param string            $nameFilter     Part of Notification entity name
     *                                          for filtering.
     *
     * @return QueryBuilder
     */
    public function getQueryBuilderForRecipient(
        AbstractRecipient $recipient,
        User $owner,
        SortingOptions $sortingOptions,
        StatusFilterEnum $statusFilter,
        $nameFilter
    ) {
        $expr = $this->_em->getExpressionBuilder();

        $sortField = $sortingOptions->getFieldName();

        $qb = $this->getQueryBuilderForForm($owner)
            ->addSelect(
                'RecipientList',
                'Schedule',
                'Owner'
            )
            ->join('Notification.owner', 'Owner')
            ->leftJoin('Notification.recipients', 'RecipientList')
            ->leftJoin('Notification.schedules', 'Schedule')
            ->orderBy($sortField, $sortingOptions->getSortDirection())
            ->setParameter('recipient', $recipient->getId());

        if ($nameFilter !== '') {
            $qb
                ->andWhere($expr->like('Notification.name', ':name'))
                ->setParameter('name', '%'. $nameFilter .'%');
        }

        switch ($statusFilter->getValue()) {
            //
            // Select notification ids which has association with specified recipient
            // and remove them from results.
            //
            case StatusFilterEnum::NO:
                $subDql = $this->createQueryBuilder('_Notification')
                    ->select('_Notification.id')
                    ->join('_Notification.recipients', '_Recipient', Join::WITH, '_Recipient.id = :recipient')
                    ->getDQL();

                $qb
                    ->addSelect('0 AS subscribed')
                    ->andWhere($expr->notIn('Notification.id', $subDql));
                break;

            //
            // Fetch only subscribed notifications.
            //
            case StatusFilterEnum::YES:
                $qb
                    ->addSelect('1 AS subscribed')
                    ->join('Notification.recipients', 'Recipient', Join::WITH, 'Recipient.id = :recipient');
                break;

            case StatusFilterEnum::ALL:
                $countDQL = $this->createQueryBuilder('_Notification')
                    ->select('COUNT(_Notification.id)')
                    ->join('_Notification.recipients', '_Recipient', Join::WITH, '_Recipient.id = :recipient')
                    ->where('_Notification.id = Notification.id')
                    ->getDQL();

                $qb
                    ->addSelect("({$countDQL}) AS subscribed")
                    ->join('Notification.recipients', 'Recipient');
                break;
        }

        $qb = $this->resolveSortingOptions($qb, $sortingOptions);

        return $qb;
    }

    /**
     * Get query builder for notifications.
     *
     * @param SortingOptions $sortingOptions A SortingOptions instance.
     * @param User           $owner          A User entity instance or null.
     * @param boolean        $onlyPublished  Fetch only published notifications.
     * @param string         $nameFilter     Filter notification by name.
     *
     * @return QueryBuilder
     */
    public function getQueryBuilder(
        SortingOptions $sortingOptions,
        User $owner,
        $onlyPublished = false,
        $nameFilter = null
    ) {
        $expr = $this->_em->getExpressionBuilder();
        $condition = $expr->andX(
            $expr->eq('Notification.billingSubscription', ':subscription'),
            $expr->eq('Notification.owner', ':owner')
        );
        $parameters = new ArrayCollection([
            new Parameter('subscription', $owner->getBillingSubscription()->getId()),
            new Parameter('recipient', $owner->getRecipient()->getId()),
            new Parameter('owner', $owner->getId()),
        ]);

        if ($onlyPublished) {
            $condition->add($expr->eq('Notification.published', 1));
        }

        $qb = $this->createQueryBuilder('Notification')
            ->addSelect(
                'Recipient',
                'Schedule',
                'Owner',
                '(CASE WHEN Recipient.id = :recipient THEN 1 ELSE 0 END) AS subscribed'
            )
            ->join('Notification.owner', 'Owner')
            ->leftJoin('Notification.recipients', 'Recipient')
            ->leftJoin('Notification.schedules', 'Schedule')
            ->where($condition)
            ->setParameters($parameters);

        if ($nameFilter !== null) {
            $qb
                ->andWhere($expr->like('Notification.name', ':name'))
                ->setParameter('name', '%'. $nameFilter .'%');
        }
        return $this->resolveSortingOptions(
            $qb,
            $sortingOptions
        );
    }

    /**
     * @param QueryBuilder   $qb             A QueryBuilder instance.
     * @param SortingOptions $sortingOptions A SortingOptions instance.
     *
     * @return QueryBuilder
     */
    private function resolveSortingOptions(QueryBuilder $qb, SortingOptions $sortingOptions)
    {
        $sortField = $sortingOptions->getFieldName();
        switch ($sortField) {
            case 'published':
            case 'active':
            case 'sourcesCount':
            case 'owner':
            case 'name':
                $sortField = "Notification.{$sortField}";
                break;

            case 'type':
                $sortField = 'Notification.notificationType';
                break;

            default:
                throw new \InvalidArgumentException("Unknown field name '{$sortField}'.");
        }

        return $qb
            ->orderBy($sortField, $sortingOptions->getSortDirection());
    }

    /**
     * Get count notifications for user.
     *
     * @param SortingOptions $sortingOptions A SortingOptions instance.
     *
     * @return \Doctrine\ORM\QueryBuilder
     */
    public function computeUserNotificationsCount(SortingOptions $sortingOptions)
    {
        $sortField = $sortingOptions->getFieldName();

        return $this->createQueryBuilder('Notification')
            ->select(
                'COUNT(Notification.id) as notifications',
                'IDENTITY(Notification.owner) as id',
                'Owner.email as name',
                '\'owner\' as type'
            )
            ->join('Notification.owner', 'Owner')
            ->orderBy($sortField, $sortingOptions->getSortDirection())
            ->groupBy('Notification.owner')
            ->getQuery()
            ->execute();
    }

    /**
     * Get count notifications for recipient.
     *
     * @param SortingOptions $sortingOptions A SortingOptions instance.
     *
     * @return \Doctrine\ORM\QueryBuilder
     */
    public function computeRecipientNotificationsCount(SortingOptions $sortingOptions)
    {
        $sortField = $sortingOptions->getFieldName();
        return $this->createQueryBuilder('Notification')
            ->select(
                'COUNT(Notification.id) as notifications',
                'Recipient.id as id',
                'Recipient.name as name',
                '\'recipient\' as type'
            )
            ->leftJoin('Notification.recipients', 'Recipient')
            ->orderBy($sortField, $sortingOptions->getSortDirection())
            ->groupBy('Recipient.id')
            ->getQuery()
            ->execute();
    }

    /**
     * Get count notifications for feed.
     *
     * @param SortingOptions $sortingOptions A SortingOptions instance.
     *
     * @return \Doctrine\ORM\QueryBuilder
     */
    public function getCountFeedNotifications(SortingOptions $sortingOptions)
    {
        $sortField = $sortingOptions->getFieldName();
        return $this->createQueryBuilder('Notification')
            ->select(
                'COUNT(Notification.id) as notifications',
                'Feed.id as id',
                'Feed.name as name',
                '\'feed\' as type'
            )
            ->leftJoin('Notification.feeds', 'Feed')
            ->orderBy($sortField, $sortingOptions->getSortDirection())
            ->groupBy('Feed.id')
            ->getQuery()
            ->execute();
    }

    /**
     * Get query builder for all notifications.
     *
     * @param SortingOptions       $sortingOptions A SortingOptions instance.
     * @param AbstractSubscription $subscription   A AbstractSubscription instance.
     *
     * @return QueryBuilder
     */
    public function getNotificationsAllQueryBuilder(
        SortingOptions $sortingOptions,
        AbstractSubscription $subscription
    ) {
        return $this->resolveSortingOptions(
            $this->createQueryBuilder('Notification')
                ->addSelect(
                    'Recipient',
                    'Schedule',
                    'Owner'
                )
                ->join('Notification.owner', 'Owner')
                ->leftJoin('Notification.recipients', 'Recipient')
                ->leftJoin('Notification.schedules', 'Schedule')
                ->where('Notification.billingSubscription = :subscription')
                ->setParameter('subscription', $subscription->getId())
                ->groupBy('Notification.id'),
            $sortingOptions
        );
    }

    /**
     * @param SortingOptions $sortingOptions A SortingOptions instance.
     * @param string         $typeFilter     One of available filter.
     * @param string         $filterId       Filter id.
     * @param User           $user           Filter owner.
     *
     * @return QueryBuilder
     */
    public function getQueryBuilderForFilter(
        SortingOptions $sortingOptions,
        $typeFilter,
        $filterId,
        User $user
    ) {

        $sortField = $sortingOptions->getFieldName();
        $qb = $this->createQueryBuilder('Notification')
            ->addSelect(
                'RecipientList',
                'Schedule',
                'Owner'
            )
            ->leftJoin('Notification.owner', 'Owner')
            ->leftJoin('Notification.recipients', 'RecipientList')
            ->leftJoin('Notification.schedules', 'Schedule')
            ->leftJoin('Notification.billingSubscription', 'Subscription', Join::WITH, 'Subscription.masterAccounts =:masterUser')
            ->setParameter('masterUser', $user->getId())
            ->orderBy($sortField, $sortingOptions->getSortDirection());

        switch ($typeFilter) {
            case 'owner':
                $qb->andWhere('Notification.owner =:owner')
                    ->setParameter(':owner', $filterId);
                break;
            case 'recipient':
                $qb->join('Notification.recipients', 'Recipient', Join::WITH, 'Recipient.id = :recipient')
                    ->setParameter('recipient', $filterId);
                break;
            case 'feed':
                $qb->join('Notification.feeds', 'Feed', Join::WITH, 'Feed.id = :feed')
                    ->setParameter('feed', $filterId);
                break;
        }
        $qb = $this->resolveSortingOptions($qb, $sortingOptions);
        return $qb;
    }
}
