<?php

namespace App\Repository;

use AnthonyMartin\GeoLocation\GeoLocation;
use App\Entity\ActivityAttendee;
use App\Entity\Location;
use App\Entity\Member;
use App\Entity\Preference;
use DateTime;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Exception;
use Pagerfanta\Doctrine\ORM\QueryAdapter;
use Pagerfanta\Pagerfanta;

class ActivityRepository extends EntityRepository
{
    /**
     * Returns a Pagerfanta object encapsulating the matching paginated activities.
     *
     * @param int $page
     * @param int $items
     *
     * @return Pagerfanta
     */
    public function findLatest($page = 1, $items = 10)
    {
        $paginator = new Pagerfanta(new QueryAdapter($this->queryLatest(), false));
        $paginator->setMaxPerPage($items);
        $paginator->setCurrentPage($page);

        return $paginator;
    }

    /**
     * @return Query
     */
    public function queryLatest()
    {
        return $this->getEntityManager()
            ->createQuery('
                SELECT a
                FROM Activity a
                WHERE a.status <> 1
                ORDER BY a.id DESC
            ');
    }

    /**
     * Returns a Pagerfanta object encapsulating the matching paginated activities.
     *
     * Only lists activities which do have only banned admins.
     *
     * @param int $page
     * @param int $items
     *
     * @return Pagerfanta
     */
    public function findProblematicActivities($page = 1, $items = 10)
    {
        $paginator = new Pagerfanta(new QueryAdapter($this->queryProblematicActivities(), false));
        $paginator->setMaxPerPage($items);
        $paginator->setCurrentPage($page);

        return $paginator;
    }

    public function queryProblematicActivities(): Query
    {
        return $this->createQueryBuilder('a')
            ->join(ActivityAttendee::class, 'aa', Join::WITH, 'aa.activity = a and aa.organizer = 1')
            ->join(Member::class, 'm', Join::WITH, 'aa.attendee = m')
            ->where("m.status = 'Banned'")
            ->orWhere('DATEDIFF(a.ends, a.starts) > 1')
            ->orderBy('a.id', 'desc')
            ->getQuery();
    }

    /**
     * Get all activities around a given location.
     *
     * @param int $limit
     *
     * @throws Exception
     *
     * @return array
     *
     * @SuppressWarnings("PHPMD.StaticAccess")
     */
    public function findUpcomingAroundLocation(Member $member, $online, $limit = 5)
    {
        if (null === $member->getCity()) {
            return [];
        }

        $qb = $this->getUpcomingAroundLocationQueryBuilder($member, $online);

        $query = $qb
            ->setMaxResults($limit)
            ->getQuery()
        ;

        return $query->getResult();
    }

    /**
     * Get all activities around a given location.
     */
    public function getUpcomingAroundLocationCount(Member $member, $online): int
    {
        if (null === $member->getCity()) {
            return 0;
        }

        $qb = $this->getUpcomingAroundLocationQueryBuilder($member, $online);
        $qb
            ->select('count(a.id) AS cnt')
        ;

        $unreadCount = $qb->getQuery()->getSingleScalarResult();

        return (int) $unreadCount;
    }

    private function getActivitiesRadius(Member $member)
    {
        $preferenceRepository = $this->getEntityManager()->getRepository(Preference::class);
        /** @var Preference $preference */
        $preference = $preferenceRepository->findOneBy(['codename' => Preference::ACTIVITIES_NEAR_ME_RADIUS]);
        $memberPreference = $member->getMemberPreference($preference);

        $distance = 20;
        if ($preference) {
            $distance = (int) $memberPreference->getValue();
        }

        return $distance;
    }

    /**
     * @throws Exception
     */
    private function getUpcomingAroundLocationQueryBuilder(Member $member, $online): ?QueryBuilder
    {
        $location = $member->getCity();
        $distance = $this->getActivitiesRadius($member);

        // Fetch latitude and longitude of member's location
        $latitude = $location->getLatitude();
        $longitude = $location->getLongitude();

        $edison = GeoLocation::fromDegrees($latitude, $longitude);
        $coordinates = $edison->boundingCoordinates($distance, 'km');

        $qb = $this->createQueryBuilder('a');
        $qb
            ->leftJoin(
                Location::class,
                'l',
                Join::WITH,
                'a.location = l.geonameId '
                . 'AND l.latitude BETWEEN :lat_e AND :lat_w AND l.longitude BETWEEN :long_s AND :long_n'
            )
            ->setParameter('lat_e', $coordinates[0]->getLatitudeInDegrees())
            ->setParameter('lat_w', $coordinates[1]->getLatitudeInDegrees())
            ->setParameter('long_s', $coordinates[0]->getLongitudeInDegrees())
            ->setParameter('long_n', $coordinates[1]->getLongitudeInDegrees())
            ->where('(a.ends >= :now AND a.ends <= :three_months) OR (a.starts >= :now AND a.starts <= :three_months)')
            ->setParameter('now', new DateTime())
            ->setParameter('three_months', (new DateTime())->modify('+3 months'))
            ->andWhere($qb->expr()->neq('a.status', 1)) // Do not show activities that were cancelled
            ->orderBy('a.starts', 'asc');

        if ($online) {
            $qb->andWhere($qb->expr()->orX(
                $qb->expr()->eq('a.online', 1),
                $qb->expr()->isNotNull('l.geonameId')
            ));
        } else {
            $qb
                ->andWhere($qb->expr()->orX(
                    $qb->expr()->isNull('a.online'),
                    $qb->expr()->eq('a.online', 0),
                ))
                ->andWhere($qb->expr()->isNotNull('l.geonameId'))
            ;
        }

        return $qb;
    }
}
