<?php
/**
 * Category Object Eloquent model
 *
 * @version 16/09/16
 * @author Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
 */

namespace Mtc\Shop;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\belongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Mtc\Plugins\Wisebee\Classes\Models\WisebeeCategory;
use Mtc\Shop\Assessment\Assessment;
use Mtc\Shop\Assessment\Form;
use Mtc\Shop\Category\ItemComparison;
use Mtc\Shop\Category\RestrictedZone;
use Spatie\Sitemap\Contracts\Sitemapable;
use Spatie\Sitemap\Tags\Url;

/**
 * Category Object Eloquent model
 *
 * @version 16/09/16
 * @author Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
 */
class Category extends Model implements Sitemapable
{
    /**
     * @var string Model table name
     */
    protected $table = 'categories';

    /**
     * @var array|bool DateTime columns for object
     */
    public $timestamps = false;

    /**
     * @var array The attributes that are mass assignable.
     */
    protected $fillable = [
        'order',
        'name',
        'display_name',
        'old_slug',
        'slug',
        'sub_id',
        'image_alt',
        'description',
        'information_leaflet',
        'side_effects',
        'cautions',
        'hide',
        'seo_title',
        'seo_keywords',
        'seo_description',
        'custom_field_set_id',
        'is_online_doctor',
        'hide_pharmacy_items',
        'description_small',
        'starting_price',
        'thumb_image',
        'thumb_image_alt',
    ];

    public static function boot() : void
    {
        parent::boot();

        self::saved(function (self $category) {
            // Clear category listing cache when categories are created or updated
            Cache::forget('category_listing_groups');

            // Clear route prefix cache and route cache when parent online doctor category slug changes
            if ($category->is_online_doctor && $category->sub_id == 0 && $category->isDirty('slug')) {
                Cache::forget('online_doctor_route_prefix');
                \Illuminate\Support\Facades\Artisan::call('route:clear');
            }

            // Update items to have online doctor review status when category gets changed
            if (array_key_exists('is_online_doctor', $category->getDirty())) {
                $category->items()
                    ->update([
                        'product_type' => Item::TYPE_DOCTOR
                    ]);

                // Category nesting - when parent gets set as online doctor so do childs
                if ($category->is_online_doctor == 1) {
                    $category->children
                        ->each(function (self $category) {
                            $category->is_online_doctor = 1;
                            $category->save();
                        });
                }
            }
        });

        self::deleted(function (self $category) {
            // Clear category listing cache when categories are deleted
            Cache::forget('category_listing_groups');
        });
    }

    /**
     * Scope - active()
     * Discards all hidden and deleted brands
     *
     * @param \Illuminate\Database\Eloquent\Builder $query Query builder object
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeActive($query)
    {
        return $query->where('hide', 0)
            ->where('deleted', 0);
    }

    /**
     * Scope - selectBasics()
     * Retrieves basic info about brand - id & name
     *
     * @param \Illuminate\Database\Eloquent\Builder $query Query builder object
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeSelectBasics($query)
    {
        return $query->select([
            'categories.id',
            'categories.name',
            'categories.image'
        ]);
    }

    /**
     * Scope - ChildOf()
     * Define scope to check if category is a child of different category
     *
     * @param \Illuminate\Database\Eloquent\Builder $query Query builder object
     * @param int $cat_id parent category ID
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeChildOf($query, $cat_id)
    {
        return $query->where('sub_id', $cat_id);
    }

    /**
     * Scope - isRoot()
     * Find only root level categories
     *
     * @param \Illuminate\Database\Eloquent\Builder $query Query builder object
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeIsRoot($query)
    {
        return $query->ChildOf(0);
    }

    /**
     * Define relationship with parent category
     * @return BelongsTo
     */
    public function parent()
    {
        return $this->belongsTo(self::class, 'sub_id');
    }

    /**
     * Define relationship with child category
     * @return HasMany
     */
    public function children(): HasMany
    {
        return $this->hasMany(self::class, 'sub_id')->orderBy('order');
    }

    /**
     * Define the relationship to Forms
     * @return belongsTo
     */
    public function form()
    {
        return $this->belongsTo(Form::class, 'form_id');
    }

    /**
     * Define the relationship to Restricted Zones
     * @return hasMany
     */
    public function restricted_zones()
    {
        return $this->hasMany(RestrictedZone::class);
    }

    /**
     * Define the relationship to Comparison Items
     * @return hasMany
     */
    public function comparison_items()
    {
        return $this->hasMany(ItemComparison::class);
    }


    /**
     * Check if category has sub-categories
     *
     * @param int $cat_id category ID to check
     * @return int child category count
     */
    public static function hasChildren($cat_id)
    {
        return self::ChildOf($cat_id)->count();
    }

    /**
     * Generate flat tree from root category
     *
     * @param int $cat_id root category id
     * @param bool $only_ids whether to fetch ids or whole objects
     * @param bool $include_current Whether to include current level category in the list
     * @return int[]|Category[] list of flat categories|ids
     */
    public static function getFlatTree($cat_id, $only_ids = false, $include_current = true)
    {
        $cacheKey = $cat_id . '|' . ($only_ids ? 'ids' : 'obj') . '|' . ($include_current ? 'with' : 'without');

        static $resultCache = [];
        if (isset($resultCache[$cacheKey])) {
            return $resultCache[$cacheKey];
        }

        static $descendantsMap;
        static $categoryLookup;

        if ($descendantsMap === null) {
            $descendantsMap = self::query()
                ->select(['id', 'sub_id', 'order'])
                ->orderBy('sub_id')
                ->orderBy('order')
                ->orderBy('name')
                ->get()
                ->groupBy('sub_id')
                ->map(fn($children) => $children->pluck('id')->all())
                ->toArray();
        }

        $ids = [];
        $stack = [];

        $pushChildren = static function (array $children) use (&$stack) {
            for ($i = count($children) - 1; $i >= 0; $i--) {
                $stack[] = (int)$children[$i];
            }
        };

        if ($include_current) {
            $ids[] = (int)$cat_id;
            $pushChildren($descendantsMap[$cat_id] ?? []);
        } else {
            $pushChildren($descendantsMap[$cat_id] ?? []);
        }

        $visited = [];
        while (!empty($stack)) {
            $current = array_pop($stack);
            if (isset($visited[$current])) {
                continue;
            }
            $visited[$current] = true;
            $ids[] = $current;
            $pushChildren($descendantsMap[$current] ?? []);
        }

        if ($only_ids) {
            return $resultCache[$cacheKey] = $ids;
        }

        if ($categoryLookup === null) {
            $categoryLookup = self::query()->get()->keyBy('id');
        }

        $collection = collect();
        foreach ($ids as $id) {
            if (!$include_current && $id === (int)$cat_id) {
                continue;
            }
            if ($categoryLookup->has($id)) {
                $collection->push(clone $categoryLookup[$id]);
            }
        }

        return $resultCache[$cacheKey] = $collection;
    }

    /**
     * Get a complete ordered flat tree of categories
     *
     * @param int $parentID
     * @param null $tree
     * @param int $level
     * @return Collection
     */
    public static function getCompleteFlatTree(int $parentID = 0, &$tree = null, int $level = 0): Collection
    {
        static $categoryCache;

        if ($categoryCache === null) {
            $categoryCache = Category::query()
                ->select(['categories.id', 'categories.name', 'categories.sub_id', 'categories.order'])
                ->orderBy('sub_id')
                ->orderBy('order')
                ->orderBy('name')
                ->get()
                ->groupBy('sub_id');
        }

        if (empty($tree)) {
            $tree = collect();
        }

        $children = $categoryCache->get($parentID, collect());
        foreach ($children as $category) {
            $categoryClone = clone $category;
            $categoryClone->level = $level;
            $categoryClone->intended_name = str_pad('', $level * 2, '- ') . $categoryClone->name;
            $tree->push($categoryClone);
            self::getCompleteFlatTree($categoryClone->id, $tree, $level + 1);
        }

        return $tree;
    }

    /**
     * Render category list.
     *
     * @param array $config list configuration.
     * @return string rendered template content
     */
    public static function renderList($config)
    {
        global $twig;
        $available_templates = [
            'option',
            'card_list'
        ];

        $categories_query = self::isRoot()
            ->orderBy('name')
            ->selectBasics()
        ;

        if (!empty($config['template']) && in_array($config['template'], $available_templates)) {
            return $twig->render('shop/category/' . $config['template'] . '.twig', [
                'categories' => $categories_query->get(),
                'config' => $config,
                'prefix' => '',
            ]);
        }
        return '';
    }

    public static function getParentCategories($order = 'name') {
        return self::isRoot()
            ->orderBy($order)
            ->selectBasics()
            ->get();
    }

    /**
     * Traverse through all parent categories and return list of IDs
     *
     * @param int $category_id child category id
     * @return int[] list of parent categories
     */
    public static function allParents($category_id)
    {
        $all_category_ids = [];
        $category = Category::query()
            ->find($category_id);

        while(!empty($category->sub_id)) {
            $category = $category->parent;
            $all_category_ids[] = $category->id;
        }
        return array_reverse($all_category_ids);
    }

    /*
     * Items relationship.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function items()
    {
        return $this->belongsToMany(Item::class, 'items_categories', 'cat_id');
    }

    public function getUrl()
    {
        return $this->getUrlAttribute();
    }


    public function getUrlAttribute()
    {
        $url = null;

        if ($this->is_online_doctor) {
            $url = $this->getDoctorLink();
        } else {
            $url = browse_url($this->id);
        }

        return $url;
    }


    public static function getOnlineDoctorRoutePrefix(): string
    {
        $cacheKey = 'online_doctor_route_prefix';

        if ($cached = Cache::get($cacheKey)) {
            return $cached;
        }

        try {
            $slug = static::where('is_online_doctor', 1)
                ->where('sub_id', 0)
                ->value('slug');
        } catch (\Throwable $e) {
            return 'online-doctor';
        }

        $slug = $slug ?: 'online-doctor';
        Cache::put($cacheKey, $slug, 3600 * 12);

        return $slug;
    }

    public function getLink($absolute = true)
    {
        if ($this->sub_id == 0) {
            $link = '/' . $this->slug;
        } else {
            $link = '/' . static::getOnlineDoctorRoutePrefix() . '/' . $this->slug;
        }

        if ($absolute) {
            $link = SITE_URL . $link;
        }

        return $link;
    }


    public function getDoctorLink()
    {
        return $this->getLink(false);
    }

    /**
     * Check if this url slug exists
     *
     * @param $slug
     * @param int $exclude_id
     * @return bool
     */
    public static function slugExists($slug, $exclude_id = 0)
    {
        $exclude_ids = [ $exclude_id ];

        $temp_query = self::query()
            ->where('categories.slug', $slug)
            ->whereNotIn('categories.id', $exclude_ids)
        ;

        return $temp_query->exists();
    }


    public function assignAssessmentForm(?Form $form = null, $ignore_if_no_change = true)
    {
        if ($ignore_if_no_change) {
            if ($this->form && $form && $this->form->id == $form->id) {
                return;
            }
        }

        $this->form()->associate($form)->save();

        foreach ($this->items as $item) {
            $item->form()->associate($form)->save();
        }
    }


    public function getLowestPrice()
    {
        $lowest_price = 99999;

        $active_items = $this->items()->active()->get();

        foreach ($active_items as $item) {
            if ($item->price > 0 && $item->price < $lowest_price) {
                $lowest_price = $item->price;
            }
        }

        return $lowest_price;
    }

    public function replicateWithRelations()
    {
        $clone = $this->replicate();
        $clone->save();

        return $clone;
    }


    public function getAdminUrl()
    {
        $url = "/shop/admin/categories/edit.category.php?id={$this->id}";

        return $url;
    }


    public static function getExportableFields()
    {
        return [
            'categories.id',
            'categories.sub_id',
            'categories.name',
            'categories.display_name',
            'categories.slug',
            'categories.order',
            'categories.hide',
            'categories.description',
            'categories.information_leaflet',
            'categories.side_effects',
            'categories.cautions',
            'categories.seo_title',
            'categories.seo_keywords',
            'categories.seo_description',
            'categories.is_online_doctor',
            'categories.hide_pharmacy_items',
            'categories.starting_price',
            'categories.description_small',
        ];
    }


    public function updateSlug(string $slug)
    {
        $this->slug = $slug;

        return $this->save();
    }


    public function hasCompletedAssessment()
    {
        $has_completed = false;

        if ($this->form) {
            $completed_assessment = Assessment::getCompletedForForm($this->form);
            if ($completed_assessment) {
                $has_completed = true;
            }
        } else {
            // Category doesn't have an assessment.
            $has_completed = true;
        }


        return $has_completed;
    }


    public function getCategoryCardData(bool $include_category_link = true) : array
    {

        $data = [];


        $child_category_data = [];



        if(!empty($this->sub_id)) {

            foreach ($this->children as $category) {


                if (!empty($category->form_id) && $category->is_online_doctor) {
                    $params = [
                        'form_id' => $category->form_id,
                        'c' => $category->id,
                    ];
                    $buttonTitle = 'Begin consultation for ' . $category->name;

                    $actionURL = route('assessment-form', $params);
                    $params['redirect'] = route('assessment-form', $params, false);

                    if (config('wisebee.enabled')) {
                        if ($redirect = WisebeeCategory::getAssessmentRedirect($params)) {
                            $basket = new \Basket();
                            $basket->Go_Basket();
                            if (!$basket->hasConsultation) {
                                $buttonTitle = 'Book a consultation for ' . $category->name;
                            }
                            $actionURL = $redirect;
                        }
                    }

                    $child_category_data[] = [
                        'actionUrl'         => $actionURL,
                        'actionLabel'       => $buttonTitle,

                        'showCategoryPopup' => false,
                        'childCategoryData' => []
                    ];


                }

            }

        }


        if ($this->form) {
            $params = [
                'form_id' => $this->form->id,
                'c' => $this->id,
            ];
            $buttonTitle = 'Begin your consultation';

            $actionURL = route('assessment-form', $params);
            $params['redirect'] = route('assessment-form', $params, false);

            if (config('wisebee.enabled')) {
                if ($redirect = WisebeeCategory::getAssessmentRedirect($params)) {
                    $basket = new \Basket();
                    $basket->Go_Basket();
                    if (!$basket->hasConsultation) {
                        $buttonTitle = 'Book a consultation';
                    }
                    $actionURL = $redirect;
                }
            }

            $data = [
                'actionUrl' => $actionURL,
                'actionLabel'       => $buttonTitle,
                'showCategoryPopup' => true,
                'childCategoryData' => []
            ];
        }

        if(!empty($child_category_data)) {
            $data['childCategoryData'] = $child_category_data;
        }

        if(!empty($child_category_data)) {
            $data['childCategoryData'] = $child_category_data;
        }

        return $data;
    }


    public function toSitemapTag(): Url | string | array
    {
        return Url::create(config('app.url') . $this->url)
            ->setLastModificationDate(Carbon::create($this->updated))
            ->setChangeFrequency(Url::CHANGE_FREQUENCY_MONTHLY)
            ->setPriority(0.6)
            ;
    }


}
