<?php

namespace App\Services;

use App\Models\Exhibition;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use App\Exceptions\ApiHttpException;
use App\Libs\Api\ParsedListParams;
use Illuminate\Database\Eloquent\Builder;
use App\Services\DataService;
use Illuminate\Support\Carbon;
use MatanYadaev\EloquentSpatial\Objects\Point;
use App\Collections\ExhibitionsCollection;

use Mcamara\LaravelLocalization\Facades\LaravelLocalization;

class ExhibitionDataService extends DataService
{
    /**
     * Gets and filters exhibitions
     *
     * @param ParsedListParams $params - Filter and order data
     * @return Illuminate\Contracts\Pagination\LengthAwarePaginator
     */
    public function list(ParsedListParams $params): LengthAwarePaginator
    {
        /** @var Builder $query */
        $query = Exhibition::full();

        //load the relationships
        $query->with([
            'image', 'gifImage', 'video',
            'artists' => function ($query) {
                $query->full()->with('image')->select('*');
            },
            'tags' => function ($query) {
                $query->full();
            },
            'location' => function ($query) {
                $query->full()->with([
                    'tags' => function ($query) {
                        $query->full();
                    },
                ]);
            },
        ]);
        //check if the default filters are to be applied
        $this->applyDefaultFilters($query, $params);
        $this->applyQueryParams($query, $params);
        return $query->paginate($params->itemsPerPage, ['*'], 'page', $params->pageNumber);
    }


    /**
     * Finds a single model for the exhibition
     *
     * @param int $id
     * @throws ApiHttpException if the provided Exhibition record is not found.
     * @return Exhibition
     */
    public function find(int $id): Exhibition
    {
        $exhibition = Exhibition::full()
            ->where('status', 'published')
            ->with([
                'image', 'video', 'roomSheetImage', 'gifImage',
                'artists' => function ($query) {
                    $query->full()->with('image')->select('*');
                },
                'tags' => function ($query) {
                    $query->full()->select('*');
                },
                'location' => function ($query) {
                    $query->full()->with(
                        [
                            'city' => function ($cityQuery) {
                                $cityQuery->full();
                            },
                            'tags' => function ($query) {
                                $query->full();
                            },
                        ]
                    );
                },
                'collections' => function ($query) {
                    $query->full()->with('image')->select('*');
                },
                'mediaGalleryItems' => function ($query) {
                    $query->full()->with('image');
                },
            ])->where('id', $id)->first();
        if (!$exhibition) {
            throw new ApiHttpException(404, 'item_not_found');
        }

        return $exhibition;
    }


    /**
     * Applies the query params and filters
     *
     * @param Builder &$query
     * @param ParsedListParams $params
     * @return void
     */
    private function applyQueryParams(Builder $query, ParsedListParams $params): void
    {
        $orderDirection = $params->orderDirection;
        $orderBy        = $params->orderBy;
        if ($orderBy === 'name') {
            $orderBy = 'title';
        }

        //Defaults when no information is provided
        if (!isset($params->originalData['order']['by'])) {
            $orderBy = 'title';
        }
        if (!isset($params->originalData['order']['direction'])) {
            $orderDirection = 'ASC';
        }

        $query->orderBy($orderBy, $orderDirection);

        // Apply the specific Filters
        // Exhibition name Filter
        if (isset($params->filters['name'])) {
            $query->where('title', 'like', $params->filters['name'] . '%');
        }

        // Artist name Filter
        if (isset($params->filters['artist_name'])) {
            $artistName = $params->filters['artist_name'];
            $query->whereHas('artists', function ($query) use ($artistName) {
                $query->full()->where('title', 'like', $artistName . '%');
            });
        }

        // Artist id Filter
        if (isset($params->filters['artist_id'])) {
            $artistIds = $params->filters['artist_id'];
            $query->whereHas('artists', function ($query) use ($artistIds) {
                $query->whereIn('id', $artistIds);
            });
        }

        // City id Filter
        if (isset($params->filters['city_id'])) {
            $cityIds = $params->filters['city_id'];
            $query->whereHas('location', function ($query) use ($cityIds) {
                $query->whereIn('city_id', $cityIds);
            });
        }

        // Location name Filter
        if (isset($params->filters['location_name'])) {
            $locationName = $params->filters['location_name'];
            $query->whereHas('location', function ($query) use ($locationName) {
                $query->full()->where('title', 'like', $locationName . '%');
            });
        }

        // Location_id id Filter
        if (isset($params->filters['location_id'])) {
            $locationIds = $params->filters['location_id'];
            $query->whereHas('location', function ($query) use ($locationIds) {
                $query->whereIn('id', $locationIds);
            });
        }

        // City name Filter
        if (isset($params->filters['city_name'])) {
            $cityName = $params->filters['city_name'];
            $query->whereHas('location', function ($query) use ($cityName) {

                $query->whereHas('city', function ($query) use ($cityName) {
                    $query->full()->where('title', 'like', $cityName . '%');
                });
            });
        }


        // Location Tag id Filter
        if (isset($params->filters['location_tag_id'])) {
            $query->whereHas('location', function ($locationQ) use ($params) {
                $locationQ->whereHas('tags', function ($tagQ) use ($params) {
                    $tagQ->where('id', $params->filters['location_tag_id']);
                });
            });
        }

        // Location type Filter
        if (isset($params->filters['location_tag_type'])) {
            $query->whereHas('location', function ($locationQ) use ($params) {
                $locationQ->whereHas('tags', function ($tagQ) use ($params) {
                    $tagQ->where('type', $params->filters['location_tag_type']);
                });
            });
        }



        // Dates filter
        foreach (['date_from', 'date_to'] as $dateField) {
            if (isset($params->filters[$dateField])) {

                if ($dateField === 'date_from') {
                    $filterDateParam = Carbon::createFromFormat('d-m-Y', $params->filters[$dateField])->startOfDay();
                    $query->whereDate('date_to', '>=', $filterDateParam);
                }

                if ($dateField === 'date_to') {
                    $filterDateParam = Carbon::createFromFormat('d-m-Y', $params->filters[$dateField])->endOfDay();
                    $query->whereDate('date_from', '<=', $filterDateParam);
                }
            }
        }

        // Location filter
        if (isset($data['filter']['location'])) {
            $query->whereIn('location_id', $data['filter']['location']);
        }

        // Highlights Filter
        if (isset($params->filters['highlights'])) {
            $query->where('highlight', $params->filters['highlights']);
        }

        $this->applyCoordinateFilters($query, $params);

        // Country filter - TODO - Check if required???
        if (isset($params->filters['country_code'])) {

            $countryCode = $params->filters['country_code'];
            $query->whereHas('location', function ($locationQuery) use ($countryCode) {
                $locationQuery->whereHas('city', function ($cityyQuery) use ($countryCode) {
                    $cityyQuery->whereHas('country', function ($countryQuery) use ($countryCode) {
                        $countryQuery->where('code', $countryCode);
                    });
                });
            });
        }
    }

    /**
     * Applies the query params and filters
     *
     * @param Builder &$query
     * @param ParsedListParams $params
     * @return void
     */
    private function applyEntryContentQueryParams(Builder $query, ParsedListParams $params): void
    {
        $orderDirection = $params->orderDirection;
        $orderBy        = $params->orderBy;
        if ($orderBy === 'name') {
            $orderBy = 'title';
        }
        $query->orderBy($orderBy, $orderDirection);


        // Coordinates filter
        $this->applyCoordinateFilters($query, $params);
    }


    /**
     * Gets exhibitions based on a tag
     *
     * @param ParsedListParams $params - Filter and order data
     * @param Tag $tag - The tag to get the exhibitions from
     * @return Illuminate\Contracts\Pagination\LengthAwarePaginator
     */
    public function listByTag(ParsedListParams $params, $tag): LengthAwarePaginator
    {

        return $tag->exhibitions()
            ->full()
            ->with([
                'image', 'video', 'gifImage',
                'tags' => function ($query) {
                    $query->full();
                },
                'artists' => function ($query) {
                    $query->full()->with('image')->select('*');
                },
                'location' => function ($query) {
                    $query->full();
                },
            ])
            ->paginate($params->itemsPerPage, ['*'], 'page', $params->pageNumber);
    }



    /**
     * Gets the exhibitions for the entry screen
     * 
     * @param ParsedListParams $filterParams;
     * @return App\\Collections\\ExhibitionsCollection
     */
    public function getEntryScreenExhibitions(ParsedListParams $filterParams): ExhibitionsCollection
    {

        /** @var Builder $query */
        $query = Exhibition::full();

        $this->applyEntryContentQueryParams($query, $filterParams);
        $query->with([
            'image', 'video', 'gifImage',
            'location' => function ($query) {
                $query->full()->with(['image']);
            },
            'artists' => function ($query) {
                $query->full()->with('image')->select('*');
            },
        ])->where('highlight', 1)
            ->where('status', 'published')
            ->where('date_to', '>=',   now())
            ->orderBy('date_to', 'ASC');

        return $query->get();
    }



    /**
     * Gets the exhibitions separated 
     * 
     * @return array
     */
    public function getSeparatedExhibitions(ParsedListParams $parsedListParams): array
    {

        $ret   = static::getExhibitionsStructure();
        /** @var Builder $query */
        $query = Exhibition::full();
        $query->with([
            'image', 'video', 'gifImage',
            'location' => function ($locQuery) {
                $locQuery->full()->with(['image']);
            },
            'artists'  => function ($artistQ) {
                $artistQ->full()->with('image')->select('*');
            },
        ])->where('status', 'published');

        if (isset($parsedListParams->filters['highlights']) && $parsedListParams->filters['highlights'] === 'true') {
            $query->where('highlight', 1);
        }

        $this->applyCoordinateFilters($query, $parsedListParams);

        $exhibitions = $query->get();

        $current     = $exhibitions->current($exhibitions);
        $comingSoon  = $exhibitions->comingSoon($exhibitions);
        $ending      = $exhibitions->ending($exhibitions);


        $ret['coming_soon']['exhibitions'] = $comingSoon;
        $ret['current']['exhibitions']     = $current;
        $ret['ending']['exhibitions']      = $ending;

        return $ret;
    }


    /**
     * Gets the divided exhibitions structure
     */
    public static function getExhibitionsStructure(): array
    {
        return  [
            'coming_soon' => [
                'name' => trans('api.coming_soon'),
                "default" => false,
                "theme" => "dark",
                "bg_color" => "#000000",
                "exhibitions" => [],
            ],
            'current'     => [
                'name' => trans('api.current'),
                "default" => true,
                "theme" => "light",
                "bg_color" => "#ffffff",
                "exhibitions" => [],
            ],
            'ending'      => [
                'name' => trans('api.ending'),
                "default" => false,
                "theme" => "light",
                "bg_color" => "#BFD0DC",
                "exhibitions" => [],
            ],
        ];
    }



    /**
     * Applies coordinate filters to exhibition query
     * 
     * @param Builder $query
     * @param ParsedListParams $params
     * 
     */
    private function applyCoordinateFilters(Builder &$query, ParsedListParams $params): void
    {
        // Coordinates filter
        if (
            isset($params->filters['latitude'])
            && isset($params->filters['longitude'])
        ) {

            $query->whereHas('location', function ($locationQ) use ($params) {

                $radius = config('api.default_radius');
                if (isset($params->filters['radius'])) {
                    $radius = $params->filters['radius'];
                }

                $point = new Point($params->filters['latitude'], $params->filters['longitude']);

                $locationQ->whereDistanceSphere('map_position', $point, '<', $radius);
            });
        }
    }


    /**
     * Gets an exhibition for usage in notifications
     *
     * @param int $id
     * @return ?Exhibition
     */
    public static function getForNotification(int $id): ?Exhibition
    {
        $exhibition = Exhibition::full()
            ->where('status', 'published')
            ->with([
                'image', 'roomSheetImage',
                'artists' => function ($query) {
                    $query->full()->with('image')->select('*');
                },
                'tags' => function ($query) {
                    $query->full()->select('*');
                },
                'location' => function ($query) {
                    $query->full()->with([
                        'city' => function ($cityQuery) {
                            $cityQuery->full();
                        },
                    ]);
                },
                'collections' => function ($query) {
                    $query->full()->with('image')->select('*');
                },
                'mediaGalleryItems' => function ($query) {
                    $query->full()->with('image');
                },
            ])->where('id', $id)->first();


        return $exhibition;
    }


    /**
     * Gets a list of exhibition that open between 2 dates
     *
     * @param string $startDate
     * @param string $endDate
     * @return ExhibitionsCollection
     */
    public function getOpeningBetween($startDate, $endDate): ExhibitionsCollection
    {
        $exhibitions = Exhibition::full()
            ->with([
                'image', 'video',
                'artists' => function ($query) {
                    $query->full()->select('*');
                },
                'tags' => function ($query) {
                    $query->full();
                },
                'location' => function ($query) {
                    $query->full();
                }
            ])
            ->where('status', 'published')
            ->where('date_from', '>=',  $startDate)
            ->where('date_from', '<=',  $endDate)
            ->get();

        return $exhibitions;
    }
}
