<?php

namespace App\Services;

use App\Models\Favourite;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use App\Libs\Api\ParsedListParams;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use App\Services\DataService;
use App\Services\ArtistDataService;
use App\Services\ExhibitionDataService;
use App\Services\CollectionDataService;
use App\Models\Artist;
use App\Models\Collection;
use App\Models\Exhibition;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use App\Exceptions\ApiHttpException;
use Illuminate\Support\Facades\DB;


class FavouriteDataService extends DataService
{


    protected const SELECT_ARRAY = [
        'exhibitions.id as exhibition_id',
        'favourites.user_id',
        'users.name',
        'users.language_id as language',
        'devices.fcm_token',
        'fcm_messages.fcm_id as msgId',
    ];


    /**
     * Creates a favourite in the database
     *
     * @param  User $user
     * @param  array $params
     * @return Favourite
     */
    public function store(User $user, array $params): Favourite
    {

        $typeParam = $params['favourite_type'];
        $type = Favourite::$types[$typeParam];

        if (!$this->checkModelExistance($type, $params['favourite_id'])) {
            throw new ApiHttpException(404, 'item_not_found');
        }
        $favourite = Favourite::firstOrCreate([
            'user_id' => $user->id,
            'favourite_id' =>  $params['favourite_id'],
            'favourite_type' =>  $type,
        ]);
        $favourite->save();

        return $favourite;
    }



    /**
     * Gets and filters favourites for a user
     *
     * @param ParsedListParams $params - Filter and order data
     * @return Illuminate\Contracts\Pagination\LengthAwarePaginator
     */
    public function list(ParsedListParams $params, User $user): LengthAwarePaginator
    {
        //The app is not using the pagination for listing the services
        //we will set the number of items to a ridiculous number of items
        $params->itemsPerPage = 9999999;
        /** @var Builder $query */
        $query = Favourite::where('user_id', $user->id)->with($this->getWithStructure());
        return $query->paginate($params->itemsPerPage, ['*'], 'page', $params->pageNumber);
    }


    /**
     * @return array
     */
    public function getWithStructure(): array
    {
        return ['favouritable' => function (MorphTo $morphTo) {

            $morphTo->constrain([
                Artist::class => function (Builder $query) {
                    $query->full()->with('image')->select('*');
                },
                Exhibition::class => function (Builder $query) {
                    $query->full()->with([
                        'image',
                        'artists' => function ($artistQ) {
                            $artistQ->full()->with('image')->select('*');
                        },
                        'location' => function ($locationQ) {
                            $locationQ->full()->with('image');
                        }
                    ])->select('*');
                },
                Collection::class => function (Builder $query) {
                    $query->full()->with('image')->select('*');
                },
            ]);
        }];
    }

    /**
     * Removes a favourite from the database
     *
     * @param int $id
     * @return bool
     */
    public function delete(int $id): bool
    {
        return Favourite::destroy($id) === 0 ? false : true;
    }



    /**
     * Checks if a model of a certain type exists
     *
     * @param  string $type
     * @param  integer $id
     * @return bool
     */
    private function checkModelExistance(string $type, $id): bool
    {

        $dataService = null;

        switch ($type) {
            case Artist::class:
                $dataService = new ArtistDataService();
                break;
            case Exhibition::class:
                $dataService = new ExhibitionDataService();
                break;
            case Collection::class:
                $dataService = new CollectionDataService();
                break;
        }

        if ($dataService) {
            $item  = $dataService->find($id);
            return $item !== null;
        }

        return false;
    }



    /**
     * Gets an array with information regarding the favourites that are related to unnotified ending exhibitions
     * @return array
     */
    public function getEndingExhibitionFavourites(): array
    {

        $daysToConsiderClosing = config('api.fcm_exhibit_ending_criteria');
        $criteriaDate          = now()->addDays($daysToConsiderClosing);
        $query                 = DB::table('favourites')->select(static::SELECT_ARRAY);

        $this->getBaseJoins($query);
        $query->join('exhibitions', function ($join) use ($criteriaDate) {
            $join->on('favourites.favourite_id', '=', 'exhibitions.id')
                ->where('favourites.favourite_type', Exhibition::class)
                ->where('exhibitions.date_to', '<=', $criteriaDate);
        })
            ->leftJoin('fcm_messages', function ($join) {
                $join->on('favourites.favourite_id', '=', 'fcm_messages.content_id')
                    ->where('fcm_messages.type_identifier', FcmMessageDataService::FAVOURITE_ENDING_EXHIBITION_TYPE)
                    ->where('fcm_messages.type', 'token');
            })
            ->whereNull('fcm_messages.fcm_id')
            ->orderBy('favourites.favourite_id', 'ASC');

        return $query->get()->toArray();
    }


    /**
     * Gets an array with information regarding the for exhibits that will open tomorrow
     * @return array
     */
    public function getOpeningTomorrowExhibitionFavourites(): array
    {

        $criteriaDate = now()->addDays(1)->endOfDay();
        $query        = DB::table('favourites')->select(static::SELECT_ARRAY);

        $this->getBaseJoins($query);
        $query->join('exhibitions', function ($join) use ($criteriaDate) {
            $join->on('favourites.favourite_id', '=', 'exhibitions.id')
                ->where('favourites.favourite_type', Exhibition::class)
                ->where('exhibitions.date_from', '<=', $criteriaDate)
                ->where('exhibitions.date_from', '>', now());
        })
            ->leftJoin('fcm_messages', function ($join) {
                $join->on('favourites.favourite_id', '=', 'fcm_messages.content_id')
                    ->where('fcm_messages.type_identifier', FcmMessageDataService::FAVOURITE_OPENING_TOMORROW_EXHIBITION_TYPE)
                    ->where('fcm_messages.type', 'token');
            })
            ->whereNull('fcm_messages.fcm_id');

        return $query->get()->toArray();
    }


    /**
     * Gets an array with information regarding exhibits for artists that will open in the future
     * 
     * @param int $daysUntinOpen The number of days to look ahead (defaults to one) 
     * 
     * @return array
     */
    public function getArtistOpeningInFutureExhibitionFavourites($daysUntinOpen = 1): array
    {

        $criteriaDate = now()->addDays($daysUntinOpen)->endOfDay();
        $query        = DB::table('favourites')->select(static::SELECT_ARRAY);

        $this->getBaseJoins($query);
        $query->join('artists', function ($join) {
            $join->on('favourites.favourite_id', '=', 'artists.id')
                ->where('favourites.favourite_type', Artist::class);
        })
            ->join('artist_exhibition', function ($join) {
                $join->on('artists.id', '=', 'artist_exhibition.artist_id');
            })
            ->join('exhibitions', function ($join) use ($criteriaDate) {
                $join->on('artist_exhibition.exhibition_id', '=', 'exhibitions.id')
                    ->where('exhibitions.date_from', '<=', $criteriaDate)
                    ->where('exhibitions.date_from', '>', now());
            })
            ->leftJoin('fcm_messages', function ($join) {
                $join->on('exhibitions.id', '=', 'fcm_messages.content_id')
                    ->where('fcm_messages.type_identifier', FcmMessageDataService::FAVOURITE_ARTIST_EXHIBITION_OPENING_TOMORROW_TYPE)
                    ->where('fcm_messages.type', 'token');
            })
            ->whereNull('fcm_messages.fcm_id');

        return $query->distinct()->get()->toArray();
    }


    /**
     * Gets an array with information regarding exhibits for artists that have recently opened
     * 
     * @param int $daysAfterOpen The number of days to look ahead (defaults to 10) 
     * 
     * @return array
     */
    public function getArtistRecentlyOpenedExhibitionFavourites($daysAfterOpen = 10): array
    {

        $criteriaDate = now()->subDays($daysAfterOpen)->startOfDay();
        $query        = DB::table('favourites')->select(static::SELECT_ARRAY);

        $this->getBaseJoins($query);

        $query->join('artists', function ($join) {
            $join->on('favourites.favourite_id', '=', 'artists.id')
                ->where('favourites.favourite_type', Artist::class);
        })
            ->join('artist_exhibition', function ($join) {
                $join->on('artists.id', '=', 'artist_exhibition.artist_id');
            })
            ->join('exhibitions', function ($join) use ($criteriaDate) {
                $join->on('artist_exhibition.exhibition_id', '=', 'exhibitions.id')
                    ->whereBetween('exhibitions.date_from', [$criteriaDate, now()->endOfDay()]);
            })
            ->leftJoin('fcm_messages', function ($join) {
                $join->on('exhibitions.id', '=', 'fcm_messages.content_id')
                    ->where('fcm_messages.type_identifier', FcmMessageDataService::FAVOURITE_ARTIST_EXHIBITION_NEW_EXHIBIT_TYPE)
                    ->where('fcm_messages.type', 'token');
            })
            ->whereNull('fcm_messages.fcm_id');

        return $query->distinct()->get()->toArray();
    }


    /**
     * Gets an array with information regarding exhibits for artists that will end tomorrow
     * @return array
     */
    public function getArtistEndingTomorrowExhibitionFavourites(): array
    {

        $criteriaDate = now()->addDays(1)->endOfDay();
        $query        = DB::table('favourites')->select(static::SELECT_ARRAY);

        $this->getBaseJoins($query);
        $query->join('artists', function ($join) {
            $join->on('favourites.favourite_id', '=', 'artists.id')
                ->where('favourites.favourite_type', Artist::class);
        })
            ->join('artist_exhibition', function ($join) {
                $join->on('artists.id', '=', 'artist_exhibition.artist_id');
            })
            ->join('exhibitions', function ($join) use ($criteriaDate) {
                $join->on('artist_exhibition.exhibition_id', '=', 'exhibitions.id')
                    ->where('exhibitions.date_to', '<=', $criteriaDate)
                    ->where('exhibitions.date_to', '>', now());
            })
            ->leftJoin('fcm_messages', function ($join) {
                $join->on('exhibitions.id', '=', 'fcm_messages.content_id')
                    ->where('fcm_messages.type_identifier', FcmMessageDataService::FAVOURITE_ARTIST_EXHIBITION_ENDING_TOMORROW_TYPE)
                    ->where('fcm_messages.type', 'token');
            })
            ->whereNull('fcm_messages.fcm_id');

        return $query->distinct()->get()->toArray();
    }

    /**
     * Applies the base join for the favourites queries
     * @param Builder $query
     */
    protected function getBaseJoins($query): void
    {
        $query->join('website_objects', function ($join) {
            $join->on('favourites.favourite_id', '=', 'website_objects.object_id')
                ->where('website_objects.content_type', 'exhibition')
                ->where('website_objects.status', 'published');
        })
            ->join('users', function ($join) {
                $join->on('favourites.user_id', '=', 'users.id');
            })
            ->join('devices', function ($join) {
                $join->on('users.id', '=', 'devices.user_id')
                    ->whereNotNull('devices.fcm_token');
            });
    }
}
