<?php

namespace UserBundle\Repository;

use AppBundle\Model\SortingOptions;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\QueryBuilder;
use UserBundle\Entity\Recipient\PersonRecipient;
use UserBundle\Entity\User;
use UserBundle\Enum\StatusFilterEnum;
use UserBundle\Utils\AdditionalConditions;

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

    const DEFAULT_LIMIT = 30;

    /**
     * Get person recipient by id.
     *
     * @param integer $id   A GroupRecipient entity id.
     * @param integer $user A User entity id.
     *
     * @return PersonRecipient|null
     */
    public function getForUser($id, $user)
    {
        return $this->createQueryBuilder('Person')
            ->where('Person.id = :id AND Person.owner = :user')
            ->setParameter('id', $id)
            ->setParameter('user', $user)
            ->getQuery()
            ->getOneOrNullResult();
    }

    /**
     * @param User $user A User entity instance.
     *
     * @return QueryBuilder
     */
    public function getAvailableForUser(User $user)
    {
        return $this->createQueryBuilder('Person')
            ->where('Person.owner = :user')
            ->setParameter('user', $user->getId());
    }

    /**
     * Get recipient by id.
     *
     * @param integer $id A person recipient entity id.
     *
     * @return PersonRecipient|null
     */
    public function get($id)
    {
        return $this->createQueryBuilder('Person')
            ->addSelect('Group')
            ->join('Person.groups', 'Group')
            ->where('Person.id = :id')
            ->setParameter('id', $id)
            ->getQuery()
            ->getOneOrNullResult();
    }

    /**
     * @param integer        $user           A User entity id.
     * @param SortingOptions $sortingOptions A SortingOptions instance.
     * @param string         $filter         Filter person recipients by name or
     *                                       email.
     *
     * @return QueryBuilder
     */
    public function getQueryBuilderForUser(
        $user,
        SortingOptions $sortingOptions,
        $filter = ''
    ) {
        $sortField = $this->resolveSortField($sortingOptions);
        $expr = $this->_em->getExpressionBuilder();

        $condition = $expr->andX($expr->eq('Person.owner', ':user'));
        $parameters = new ArrayCollection([ new Parameter('user', $user) ]);

        if ($filter !== '') {
            $condition->add($expr->orX(
                $expr->like('Person.firstName', ':filter'),
                $expr->like('Person.lastName', ':filter'),
                $expr->like('Person.email', ':filter')
            ));
            $parameters[] = new Parameter('filter', '%' . $filter . '%');
        }

        return $this->createQueryBuilder('Person')
            ->addSelect('RecipientGroup')
            ->leftJoin('Person.groups', 'RecipientGroup')
            ->where($condition)
            ->setParameters($parameters)
            ->orderBy($sortField, $sortingOptions->getSortDirection());
    }

    /**
     * @param integer              $user                 A User entity id.
     * @param integer              $group                A GroupRecipient entity
     *                                                   id.
     * @param StatusFilterEnum     $statusFilter         A StatusFilterEnum instance.
     * @param SortingOptions       $sortingOptions       A SortingOptions instance.
     * @param string               $filter               Filter person recipients
     *                                                   by name or email.
     * @param AdditionalConditions $additionalConditions A AdditionalConditions
     *                                                   instance.
     *
     * @return QueryBuilder
     */
    public function getQueryBuilderForGroup(
        $user,
        $group,
        StatusFilterEnum $statusFilter,
        SortingOptions $sortingOptions,
        $filter,
        AdditionalConditions $additionalConditions
    ) {
        $sortField = $this->resolveSortField($sortingOptions);
        $expr = $this->_em->getExpressionBuilder();

        $condition = $expr->andX($expr->eq('Person.owner', ':user'));
        $parameters = new ArrayCollection([
            new Parameter('user', $user),
            new Parameter('group', $group),
        ]);
        $parameters = $additionalConditions->addToParameters($parameters);

        if ($filter !== '') {
            $condition->add($expr->orX(
                $expr->like('Person.firstName', ':filter'),
                $expr->like('Person.lastName', ':filter'),
                $expr->like('Person.email', ':filter')
            ));
            $parameters[] = new Parameter('filter', '%' . $filter . '%');
        }

        $qb = $this->createQueryBuilder('Person');

        switch ($statusFilter->getValue()) {
            //
            // Show only recipients which not enrolled in group.
            //
            case StatusFilterEnum::NO:
                //
                // Select recipients which have association with specified group
                // and remove them from results.
                //
                $subCondition = $expr->andX(
                    $expr->eq('_Grp.id', ':group'),
                    $expr->eq('_Person.owner', ':user')
                );
                $subCondition = $additionalConditions->addToConditions($subCondition, '_Person');

                $subDql = $this->createQueryBuilder('_Person')
                    ->select('_Person.id')
                    ->leftJoin('_Person.groups', '_Grp')
                    ->where($subCondition)
                    ->getDQL();

                $condition->add($expr->notIn('Person', $subDql));
                $qb->addSelect('0 AS enrolled');
                break;

            //
            // Show only recipients which enrolled in groups.
            //
            case StatusFilterEnum::YES:
                $condition->add($expr->eq('RecipientGroup.id', ':group'));
                $condition = $additionalConditions->addToConditions($condition, 'Person');

                $qb->addSelect('1 AS enrolled');
                break;


            //
            // If we not apply filters we should check which recipient is enrolled
            // in specified group.
            //
            case StatusFilterEnum::ALL:
                $countCondition = $expr->andX(
                    $expr->eq('_Grp.id', ':group'),
                    $expr->eq('_Person.id', 'Person.id')
                );

                $countCondition = $additionalConditions->addToConditions($countCondition, 'Person');

                $countDQL = $this->createQueryBuilder('_Person')
                    ->select('COUNT(_Person.id)')
                    ->join('_Person.groups', '_Grp')
                    ->where($countCondition)
                    ->getDQL();

                $qb->addSelect("(CASE WHEN ({$countDQL}) > 0 THEN 1 ELSE 0 END) AS enrolled");
                break;
        }

        return $qb
            ->addSelect('RecipientGroup')
            ->leftJoin('Person.groups', 'RecipientGroup')
            ->where($condition)
            ->setParameters($parameters)
            ->orderBy($sortField, $sortingOptions->getSortDirection());
    }

    /**
     * @param integer $group A GroupRecipient entity id.
     *
     * @return string[] Fetched emails.
     */
    public function getEmailsByGroup($group)
    {
        return array_map(function (array $row) {
            return $row['email'];
        }, $this->createQueryBuilder('Person')
            ->select('Person.email')
            ->join('Person.groups', 'Grp')
            ->where('Grp.id = :group')
            ->setParameter('group', $group)
            ->getQuery()
            ->getArrayResult());
    }

    /**
     * @param SortingOptions $sortingOptions A SortingOptions instance.
     *
     * @return string
     */
    private function resolveSortField(SortingOptions $sortingOptions)
    {
        $sortField = $sortingOptions->getFieldName();
        switch ($sortField) {
            case 'active':
            case 'email':
            case 'name':
                $sortField = "Person.{$sortField}";
                break;

            case 'creationDate':
                $sortField = 'Person.createdAt';
                break;

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

        return $sortField;
    }
}
