<?php

namespace common\manager;

use common\components\Helper;
use common\models\Card;
use common\models\CardEventRegistrationForm;
use common\models\Customer;
use common\models\Event;
use common\models\EventRegistration;
use common\models\Ticket;
use customerapi\models\available\EventInterval;
use customerapi\models\registrations\EventRegistrationAvailable;
use customerapi\models\details\EventRegistrationView;
use Exception;
use Yii;
use yii\base\BaseObject;
use yii\db\ActiveRecord;
use yii\db\Query;
use yii\web\BadRequestHttpException;
use yii\web\NotFoundHttpException;
use yii\web\ServerErrorHttpException;

/**
 * Created by IntelliJ IDEA.
 * User: rocho
 * Date: 2018.12.17.
 * Time: 6:12
 */
class EventRegistrationManager extends BaseObject
{
    const CARD_NOT_FOUND = 1;
    const CUSTOMER_NOT_FOUND = 2;
    const TICKET_NOT_FOUND = 3;
    const NO_FREE_SEATS = 4;
    const EVENT_TYPE_NOT_FOUND = 5;
    const TICKET_INSUFFICIENT = 6;
    const UNKNOWN_ERROR = 7;
    const MAX_SEAT_COUNT_EXCEEDED = 8;
    const EVENT_UNAVAILABLE = 9;
    const ALREADY_REGISTERED = 10;
    const EVENT_START_DATE_IN_PAST = 11;

    const EVENT_NOT_FOUND = 12;
    const ALREADY_CANCELLED = 13;
    const ALREADY_DELETED = 14;
    const CANCEL_TIME_LIMIT_REACHED = 15;

    public static $STATES = [
        self::CARD_NOT_FOUND => "Kártya nem található",
        self::CUSTOMER_NOT_FOUND => "Vendég nem található",
        self::TICKET_NOT_FOUND => "Bérlet nem található",
        self::NO_FREE_SEATS => "Nincs szabad hely",
        self::EVENT_TYPE_NOT_FOUND => "Esemény típus nem található",
        self::TICKET_INSUFFICIENT => "Bérlet nem található",
        self::UNKNOWN_ERROR => "Ismeretlen hiba",
        self::MAX_SEAT_COUNT_EXCEEDED => "Nincs szabad hely",
        self::EVENT_UNAVAILABLE => "Esemény nem elérhető",
        self::ALREADY_REGISTERED => "Már regisztrálva van",
        self::EVENT_START_DATE_IN_PAST => "Az esemény már elkezdődött",

        self::EVENT_NOT_FOUND => "Esemény tnem található",
        self::ALREADY_CANCELLED => "Ez a regisztráció már lemndásra került",
        self::ALREADY_DELETED => "Ez a regisztráció már törlésre került",
        self::CANCEL_TIME_LIMIT_REACHED => "Ez a regisztráció már nem mondható le",
    ];

    /**
     * @param CardEventRegistrationForm $cardEventForm
     * @throws Exception
     */
    public function registerCard($cardEventForm)
    {
        $db = Yii::$app->db;
        $tx = $db->beginTransaction();
        try {
            if ($cardEventForm->validate()) {

                $requiresTicket = false;

                /** @var Card $card */
                $card = Card::readCard($cardEventForm->card_number, false);
                if (!isset($card)) {
                    throw new NotFoundHttpException('Card not found: ' . $cardEventForm->card_number, self::CARD_NOT_FOUND);
                }

                if ($card->isFree()) {
                    throw new NotFoundHttpException('Customer not found', self::CUSTOMER_NOT_FOUND);
                }

                $activeTickets = $card->getActiveTickets();
                if (count($activeTickets) === 0) {
                    throw new BadRequestHttpException('Ticket not found1', self::TICKET_NOT_FOUND);
                }

                /** @var Event $event */
                $event = Event::find()->andWhere(['id' => $cardEventForm->event_id])->one();
                if (!isset($event)) {
                    throw new NotFoundHttpException('Event not found: ' . $cardEventForm->event_id);
                }

                if (isset($event->deleted_at)) {
                    throw new BadRequestHttpException('Event deleted', self::EVENT_UNAVAILABLE);
                }

                $now = strtotime("now utc");
                if ($event->start < $now) {
                    throw new BadRequestHttpException('Event start date in past', self::EVENT_START_DATE_IN_PAST);
                }


                if (!$event->hasFreeSeats()) {
                    throw new BadRequestHttpException('No free seats', self::NO_FREE_SEATS);
                }

                $eventType = $event->eventType;

                if (!isset($eventType)) {
                    throw new ServerErrorHttpException('Event type not found', self::EVENT_TYPE_NOT_FOUND);
                }

                //detect if customer is already registered to event
                /** @var EventRegistration[] $registrations */
                $registrations = $event->getActiveEventRegistrations()->all();

                foreach ($registrations as $registration) {
                    if ($registration->id_customer == $card->customer->id_customer) {
                        throw new BadRequestHttpException("Already registered", self::ALREADY_REGISTERED);
                    }
                }

                $selectedTicket = $eventType->findTicketAllowingEventType($activeTickets);

                if (!isset($selectedTicket)) {
                    throw new BadRequestHttpException('Ticket not found2', self::TICKET_INSUFFICIENT);
                }

                try {
                    $selectedTicket->consumeReservationCount(1);
                } catch (\Exception $e) {
                    throw new BadRequestHttpException('Max ticket seat count exceeded', self::MAX_SEAT_COUNT_EXCEEDED);
                }

                $selectedTicket->save(false);

                $registration = new EventRegistration();
                $registration->id_event = $event->id;
                $registration->id_card = $card->id_card;
                $registration->id_ticket = $selectedTicket->id_ticket;
                $registration->id_customer = $card->customer->id_customer;
                try {
                    $registration->save(false);
                } catch (\yii\db\Exception $exception) {
                    throw new ServerErrorHttpException('Failed to save', self::UNKNOWN_ERROR);
                }

                $cardEventForm->registration = $registration;
            }
            $tx->commit();
        } catch (Exception $exception) {
            $tx->rollBack();
            throw $exception;
        }

    }

    public function createFindRegistrationsQuery($idEvent)
    {
        $query = new Query();
        $query->select([
            'event_registration.id as event_registration_id',
            'event_registration.created_at as event_registration_created_at',
            'event_registration.canceled_at as event_registration_canceled_at',
            'event_registration.deleted_at as event_registration_deleted_at',
            'card.id_card as card_id_card',
            'card.number as card_number',
            'customer.id_customer as customer_id_customer',
            'customer.name as customer_name',
            'customer.email as customer_email',
        ]);
        $query->from(EventRegistration::tableName());
        $query->innerJoin(Event::tableName(), 'event_registration.id_event = event.id');
        $query->innerJoin(Card::tableName(), 'event_registration.id_card = card.id_card');
        $query->innerJoin(Customer::tableName(), 'customer.id_customer_card = card.id_card');
        $query->andWhere(['event_registration.id_event' => $idEvent]);

        return $query;
    }


    /**
     * @param $idRegistration
     * @return array|EventRegistration|ActiveRecord|null
     * @throws NotFoundHttpException
     */
    public function loadRegistration($idRegistration, $idCustomer)
    {
        $query = EventRegistration::find()
            ->andWhere(['id' => $idRegistration]);
        if (isset($idCustomer)) {
            $query->andWhere(['id_customer' => $idCustomer]);
        }
        $registration = $query->one();
        if ($registration === null) {
            throw new NotFoundHttpException('The requested registration does not exist.');
        }
        return $registration;
    }

    public function cancelRegistrationTX($registration, $idCustomer, $reason)
    {
        $tx = \Yii::$app->db->beginTransaction();
        try {
            $this->cancelRegistration($registration, $idCustomer, $reason);
            $tx->commit();
        } catch (\Exception $e) {
            $tx->rollBack();
            throw $e;
        }
    }

    /**
     * @param EventRegistration $registration
     * @throws ServerErrorHttpException
     */
    public function cancelRegistration($registration, $idCustomer, $reason)
    {
        if (isset($idCustomer) && $registration->id_customer != $idCustomer) {
            throw new NotFoundHttpException('The requested registration does not exist.');
        }

        if (isset($registration->canceled_at)) {
            throw new BadRequestHttpException('The registration is already canceled', self::ALREADY_CANCELLED);
        }

        if (isset($registration->deleted_at)) {
            throw new BadRequestHttpException('The reservation is already deleted', self::ALREADY_DELETED);
        }

        $event = Event::findOne($registration->id_event);

        if (!isset($event)) {
            throw new BadRequestHttpException('The reservation is already deleted', self::EVENT_NOT_FOUND);
        }

        $now = strtotime("now UTC");

        if ($reason != EventRegistration::CANCEL_REASON_CUSTOMER) {
            $timeUntilEventStart = $event->start - $now;
            if ($timeUntilEventStart < Helper::getGroupTrainingRegistrationCancelLimitMinutes() * 60) {
                throw new BadRequestHttpException('The reservation is already deleted', self::CANCEL_TIME_LIMIT_REACHED);
            }
        }

        $now = strtotime("now");
        // if user cancels then cancel_at will be set
        if ( $reason == EventRegistration::CANCEL_REASON_CUSTOMER){
            $registration->canceled_at = date('Y-m-d H:i:s', $now);
        }else{
            // otherwise ( e.g. event is deleted  ) the delete_at will be set
            $registration->deleted_at = date('Y-m-d H:i:s', $now);
        }

        $registration->save(false);
        $ticket = Ticket::findOne($registration->id_ticket);

        if (!isset($ticket)) {
            throw new BadRequestHttpException('The ticket is not found');
        }

        $ticket->restoreReservationCount(1);
        $ticket->save(false);

    }

    public function findAllRegistrations($idEvent, $idCustomer = null)
    {
        $registrations =
            EventRegistration::find()
                ->andWhere(['id_event' => $idEvent])->all();
        if (isset($idCustomer)) {
            $registrations = EventRegistration::filterForCustomer($registrations, $idCustomer);
        }
        return $registrations;
    }

    public function findActiveRegistrations($idEvent, $idCustomer = null)
    {
        $registrations = $this->findAllRegistrations($idEvent, $idCustomer);
        return EventRegistration::filterActive($registrations);
    }

    /**
     * Delete an event
     * @param Event $event
     * @throws Exception
     * @throws Throwable
     */
    public function deleteEvent($event)
    {
        $db = Yii::$app->db;
        $tx = $db->beginTransaction();
        try {

            $eventRegistrationManager = new EventRegistrationManager();
            $allRegistrations = $eventRegistrationManager->findAllRegistrations($event->id);
            $activeRegistrations = $eventRegistrationManager->findActiveRegistrations($event->id);

            // ////////////////////////////////
            // if event has no registrations
            // we can simply delete it from db
            // ////////////////////////////////
            if (count($allRegistrations) === 0) {
                $event->delete();
            } else {
                // /////////////////////////////
                // otherwise we mark the event deleted
                // and we have to delete the registrations
                // /////////////////////////////
                $event->deleted_at = date('Y-m-d H:i:s');
                $event->save(false);
                foreach ($activeRegistrations as $registration) {
                    if (!isset($registration->deleted_at)) {
                        /** @var EventRegistration $registration */
                        $this->cancelRegistration($registration, null, EventRegistration::CANCEL_REASON_EVENT);
                    }
                }
            }
            $tx->commit();
        } catch (Exception $e) {
            $tx->rollBack();
            throw $e;
        }

    }

}
