<?php

namespace Concrete\Block\CalendarEvent;

use Concrete\Core\Attribute\Key\CollectionKey;
use Concrete\Core\Attribute\Key\EventKey;
use Concrete\Core\Block\BlockController;
use Concrete\Core\Calendar\Calendar;
use Concrete\Core\Calendar\Calendar\CalendarService;
use Concrete\Core\Calendar\CalendarServiceProvider;
use Concrete\Core\Calendar\Event\Event;
use Concrete\Core\Calendar\Event\EventOccurrence;
use Concrete\Core\Entity\Calendar\CalendarEvent;
use Concrete\Core\Entity\Calendar\CalendarEventVersionOccurrence;
use Concrete\Core\Feature\Features;
use Concrete\Core\Feature\UsesFeatureInterface;
use Concrete\Core\Page\Page;
use Concrete\Core\Url\SeoCanonical;
use Concrete\Core\Utility\Service\Xml;
use Doctrine\ORM\EntityManagerInterface;
use SimpleXMLElement;

defined('C5_EXECUTE') or die('Access Denied.');

class Controller extends BlockController implements UsesFeatureInterface
{
    /**
     * @var string|null
     */
    protected $mode = 'S';

    /**
     * @var string|null
     */
    protected $calendarEventAttributeKeyHandle;

    /**
     * @var int|string|null
     */
    public $calendarID;

    /**
     * @var int|string|null
     */
    protected $eventID;

    /**
     * @var string|null
     */
    public $displayEventAttributes;

    /**
     * @var bool|int|string|null
     */
    public $allowExport;

    /**
     * @var bool|int|string|null
     */
    public $enableLinkToPage;

    /**
     * @var bool|int|string|null
     */
    public $displayEventName;

    /**
     * @var bool|int|string|null
     */
    public $displayEventDate;

    /**
     * @var bool|int|string|null
     */
    public $displayEventDescription;

    /**
     * @var int
     */
    protected $btInterfaceWidth = 550;

    /**
     * @var int
     */
    protected $btInterfaceHeight = 400;

    /**
     * @var string
     */
    protected $btTable = 'btCalendarEvent';

    /**
     * @var bool|null
     */
    protected $btCacheBlockOutput = null;

    /**
     * @var bool
     */
    protected $btCacheBlockOutputForRegisteredUsers = true;

    /**
     * @var bool
     */
    protected $btCacheBlockOutputOnPost = true;

    /**
     * @var int
     */
    protected $btCacheBlockOutputLifetime = 3600;

    /**
     * {@inheritdoc}
     */
    public function getRequiredFeatures(): array
    {
        return [
            Features::CALENDAR,
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockTypeDescription()
    {
        return t('Displays a calendar event on a page.');
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockTypeName()
    {
        return t('Calendar Event');
    }

    /**
     * @return void
     */
    public function add()
    {
        $this->edit();
    }

    /**
     * @return void
     */
    public function edit()
    {
        /** @var \Concrete\Core\Entity\Attribute\Key\EventKey[] $eventKeys */
        /** @phpstan-ignore-next-line */
        $eventKeys = EventKey::getList();
        $calendars = ['' => t('** Choose a Calendar')];
        $calendarEventPageKeys = ['' => t('** Choose an Event')];
        /** @phpstan-ignore-next-line */
        foreach (CollectionKey::getList() as $ak) {
            if ($ak->getAttributeTypeHandle() == 'calendar_event') {
                $calendarEventPageKeys[$ak->getAttributeKeyHandle()] = $ak->getAttributeKeyDisplayName();
            }
        }
        /** @phpstan-ignore-next-line */
        foreach (Calendar::getList() as $calendar) {
            $calendars[$calendar->getID()] = $calendar->getName();
        }

        $displayEventAttributes = [];
        if (isset($this->displayEventAttributes)) {
            $displayEventAttributes = json_decode($this->displayEventAttributes);
        }

        $this->set('calendarEventPageKeys', $calendarEventPageKeys);
        $this->set('eventKeys', $eventKeys);
        $this->set('calendars', $calendars);
        $this->set('displayEventAttributes', $displayEventAttributes);
    }

    /**
     * @param array<string,mixed> $data
     *
     * @return void
     */
    public function save($data)
    {
        $data['calendarID'] = isset($data['calendarID']) ? (int) ($data['calendarID']) : 0;
        $data['eventID'] = isset($data['eventID']) ? (int) ($data['eventID']) : 0;

        $data['allowExport'] = isset($data['allowExport']) && $data['allowExport'] ? 1 : 0;
        $data['displayEventName'] = isset($data['displayEventName']) && $data['displayEventName'] ? 1 : 0;
        $data['displayEventDate'] = isset($data['displayEventDate']) && $data['displayEventDate'] ? 1 : 0;
        $data['displayEventDescription'] = isset($data['displayEventDescription']) && $data['displayEventDescription'] ? 1 : 0;
        $data['enableLinkToPage'] = isset($data['enableLinkToPage']) && $data['enableLinkToPage'] ? 1 : 0;

        $attributes = [];
        if (isset($data['displayEventAttributes']) && is_array($data['displayEventAttributes'])) {
            $attributes = $data['displayEventAttributes'];
        }
        $data['displayEventAttributes'] = json_encode($attributes);
        parent::save($data);
    }

    /**
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     *
     * @return CalendarEventVersionOccurrence|null
     */
    public function getOccurrence()
    {
        $event = $this->getEvent();
        if (is_object($event)) {
            if ($this->request->query->has('occurrenceID')) {
                /** @var CalendarEventVersionOccurrence|null $occurrence */
                /** @phpstan-ignore-next-line */
                $occurrence = EventOccurrence::getByID($this->request->query->get('occurrenceID'));
                if ($occurrence) {
                    if ($occurrence->getEvent() && $occurrence->getEvent()->getID() == $event->getID()) {
                        /** @phpstan-ignore-next-line */
                        if ($event->isApproved()) {
                            return $occurrence;
                        }
                    }

                    /** @phpstan-ignore-next-line */
                    if ($occurrence->getEvent() && Event::isRelatedTo($occurrence->getEvent(), $event)) {
                        // this is a temporary hack. We need to ultimately make this make sure that
                        // the event matches -but sometimes our events don't match because we imported them as series
                        // events and not as  occurrences of one event.
                        // @TODO delete this code when we no longer need it and all related events are migrated into multidate events.
                        /** @phpstan-ignore-next-line */
                        if ($event->isApproved()) {
                            return $occurrence;
                        }
                    }
                }
            }
            $date = $this->app->make('date')->date('Y-m-d', null, 'app');
            $time = $this->app->make('date')->toDateTime($date . ' 00:00:00', 'GMT', 'app')->getTimestamp();
            $list = $event->getOccurrenceList();
            $list->filterByStartTimeAfter($time);
            $list->setItemsPerPage(1);
            $results = $list->getResults();
            if (!$results) {
                $list = $event->getOccurrenceList();
                $list->sortBy('startTime', 'desc');
                $results = $list->getResults();
            }

            return $results[0] ?? null;
        }
        if ($this->request->query->has('occurrenceID') && $this->mode == 'R') {
            // request mode
            /** @phpstan-ignore-next-line */
            $occurrence = EventOccurrence::getByID($this->request->query->get('occurrenceID'));
            if ($occurrence) {
                $event = $occurrence->getEvent();
                if ($event && $event->isApproved()) {
                    return $occurrence;
                }
            }
        }

        return null;
    }

    public function on_start()
    {
        if ($this->request->query->has('occurrenceID') && $this->mode == 'R') {
            /** @var SeoCanonical $seo */
            $seo = $this->app->make(SeoCanonical::class);
            $seo->addIncludedQuerystringParameter('occurrenceID');
        }
    }

    /**
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     *
     * @return void
     */
    public function view()
    {
        $this->set('event', $this->getEvent());

        $displayEventAttributes = [];
        if (isset($this->displayEventAttributes)) {
            $displayEventAttributes = json_decode($this->displayEventAttributes);
        }

        $this->set('displayEventAttributes', $displayEventAttributes);
        $occurrence = $this->getOccurrence();
        $this->set('occurrence', $occurrence);
        $provider = $this->app->make(CalendarServiceProvider::class);
        if ($occurrence) {
            $this->set('event', $occurrence->getEvent());
            $linkFormatter = $provider->getLinkFormatter();
            $eventOccurrenceLink = $linkFormatter->getEventOccurrenceFrontendViewLink($occurrence);
            $this->set('eventOccurrenceLink', $eventOccurrenceLink);
        }
        $formatter = $provider->getDateFormatter();
        $this->set('formatter', $formatter);
    }

    /**
     * {@inheritdoc}
     *
     * @see \Concrete\Core\Block\BlockController::export()
     */
    public function export(SimpleXMLElement $blockNode)
    {
        $xml = $this->app->make(Xml::class);
        parent::export($blockNode);
        $recordNode = $blockNode->xpath('./data[@table="btCalendarEvent"]/record')[0];

        $calendarName = '';
        if ($this->calendarID) {
            $calendar = $this->app->make(CalendarService::class)->getByID($this->calendarID);
            if ($calendar !== null) {
                $calendarName = $calendar->getName();
            }
        }
        unset($recordNode->calendarID);
        $xml->createChildElement($recordNode, 'calendarName', $calendarName);

        $displayEventAttributes = [];
        $attributeIDs = $this->displayEventAttributes ? (array) json_decode($this->displayEventAttributes) : [];
        foreach ($attributeIDs as $attributeID) {
            if (($attributeID = (int) $attributeID) < 1) {
                continue;
            }
            if (!($ak = EventKey::getByID($attributeID))) {
                continue;
            }
            $displayEventAttributes[] = $ak->getAttributeKeyHandle();
        }
        unset($recordNode->displayEventAttributes);
        $xml->createChildElement($recordNode, 'displayEventAttributes', json_encode($displayEventAttributes));

        $serializedEvent = '';
        if ($this->mode !== 'P') {
            $event = $this->getEvent();
            $eventVersion = $event ? $event->getSelectedVersion() : null;
            $eventOccurrence = $eventVersion ? $eventVersion->getOccurrences()->first() : null;
            if ($eventOccurrence) {
                $serializedEvent = json_encode(['startTime' => $eventOccurrence->getOccurrence()->getStart(), 'name' => $eventVersion->getName()]);
            }
        }
        unset($recordNode->eventID);
        $xml->createChildElement($recordNode, 'event', $serializedEvent);
    }

    /**
     * {@inheritdoc}
     *
     * @see \Concrete\Core\Block\BlockController::getImportData()
     */
    protected function getImportData($blockNode, $page)
    {
        $data = parent::getImportData($blockNode, $page);
        $calendar = null;
        if (is_string($calendarName = $data['calendarName'] ?? null) && ($calendarName = trim($calendarName)) !== '') {
            $calendar = $this->app->make(CalendarService::class)->getByName($calendarName);
        }
        $event = null;
        if ($calendar) {
            $serializedEvent = empty($data['event']) ? null : json_decode($data['event'], true);
            if (isset($serializedEvent['startTime']) && isset($serializedEvent['name'])) {
                $qb = $this->app->make(EntityManagerInterface::class)->createQueryBuilder();
                $qb
                    ->setParameter('calendarID', $calendar->getID())
                    ->setParameter('eventName', (string) $serializedEvent['name'])
                    ->setParameter('startTime', (int) $serializedEvent['startTime'])
                    ->select('event')
                    ->from(CalendarEvent::class, 'event')
                    ->innerJoin('event.versions', 'version')
                    ->innerJoin('version.occurrences', 'versionOccurrence')
                    ->innerJoin('versionOccurrence.occurrence', 'occurrence')
                    ->andWhere('event.calendar = :calendarID')
                    ->andWhere('version.evName = :eventName')
                    ->andWhere('occurrence.startTime = :startTime')
                    ->setMaxResults(1)
                ;
                $event = $qb->getQuery()->execute()[0] ?? null;
            }
        }
        $data['calendarID'] = $calendar ? $calendar->getID() : 0;
        $data['eventID'] = $event ? $event->getID() : 0;
        $displayEventAttributes = [];
        if (!empty($data['displayEventAttributes']) && is_array($handles = json_decode($data['displayEventAttributes']))) {
            foreach ($handles as $handle) {
                $ak = EventKey::getByHandle($handle);
                if ($ak) {
                    $displayEventAttributes[] = $ak->getAttributeKeyID();
                }
            }
        }
        $data['displayEventAttributes'] = $displayEventAttributes;

        return $data;
    }

    /**
     * @return CalendarEvent|null
     */
    protected function getEvent()
    {
        $event = null;
        if ($this->mode == 'P') {
            $page = Page::getCurrentPage();
            $event = $page->getAttribute($this->calendarEventAttributeKeyHandle);
        } else {
            if ($this->eventID) {
                /** @phpstan-ignore-next-line */
                $event = Event::getByID($this->eventID);
            }
        }

        return $event;
    }

    public function cacheBlockOutput()
    {
        if ($this->btCacheBlockOutput === null) {
            if ($this->mode !== 'R') {
                $this->btCacheBlockOutput = true;
            } else {
                $this->btCacheBlockOutput = false;
            }
        }

        return $this->btCacheBlockOutput;
    }
}
