<?php
namespace Mtc\Shop\Assessment;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\hasMany;
use Illuminate\Support\Collection;
use Mtc\Core\Models\Country;
use Mtc\Modules\Members\Classes\Auth;
use Mtc\Modules\Members\Models\Member;
use Mtc\Shop\Basket;
use Mtc\Shop\Category;
use Mtc\Shop\Item;
use Mtc\Shop\Order;


/**
 * @author Uldis Zvirbulis <uldis.zvirbulis@mtcmedia.co.uk>
 *
 * Accessors
 *
 * @property int $member_id
 * @property int $form_id
 * @property int $review_id
 * @property string $customer_name
 * @property string $customer_dob
 * @property int $item_id
 * @property int $is_completed
 * @property string $additional_info
 *
 *
 * @property Form $form
 * @property \Illuminate\Database\Eloquent\Collection $answers
 *
 */
class Assessment extends Model
{
    const LATEST_ASSESSMENT_SESSION_KEY = 'latest_assessment_id';


    protected $fillable = [
        'member_id',
        'form_id',
        'review_id',
        'customer_name',
        'customer_dob',
        'item_id',
        'is_completed',
        'additional_info',
    ];

    const UPLOAD_PATH = 'assessment_files';

    const ASSESSMENT_INIT_REDIRECT = 'assessment_init_redirect';
    const ASSESSMENT_COMPLETE_REDIRECT = 'assessment_complete_redirect';

    /**
     * Define the relationship to Order
     * @return \Illuminate\Database\Eloquent\Relations\belongsTo
     */
    public function order()
    {
        return $this->belongsTo(Order::class);
    }

    /**
     * Define the relationship to Basket
     * @return \Illuminate\Database\Eloquent\Relations\belongsTo
     */
    public function basket()
    {
        return $this->belongsTo(Basket::class);
    }

    /**
     * Define the relationship to Form
     * @return \Illuminate\Database\Eloquent\Relations\belongsTo
     */
    public function form()
    {
        return $this->belongsTo(Form::class);
    }

    /**
     * Define the relationship to Item
     * @return \Illuminate\Database\Eloquent\Relations\belongsTo
     */
    public function item()
    {
        return $this->belongsTo(Item::class);
    }

    /**
     * Define the relationship to Answers
     * @return hasMany
     */
    public function answers()
    {
        return $this->hasMany(Answer::class);
    }

    /**
     * Define the relationship to Answers
     * @return hasMany
     */
    public function orderItems(): hasMany
    {
        return $this->hasMany(Order\Item::class);
    }

    /**
     * @param \Illuminate\Database\Eloquent\Builder $query Query builder object
     * @return \Illuminate\Database\Eloquent\Builder
     *
     */

    public function scopeCompleted(Builder $query)
    {
        return $query->where('is_completed', 1);
    }


    public function setAsLatest()
    {
        $_SESSION[self::LATEST_ASSESSMENT_SESSION_KEY] = $this->id;
    }


    public static function getLatest()
    {
        $output = null;
        if (isset($_SESSION[self::LATEST_ASSESSMENT_SESSION_KEY])) {
            $output = self::find($_SESSION[self::LATEST_ASSESSMENT_SESSION_KEY]);
        }

        return $output;
    }


    //public static function getCompletedForItem(Item $shop_item, Member $member = null)
    //{
    //    $assessment = null;

    //    $item_assessment_id = $_SESSION['assessments'][$shop_item->id] ?? null;
    //    if ($item_assessment_id) {
    //        $assessment = Assessment::find($item_assessment_id);
    //    }

    //    $main_category = $shop_item->getMainCategory();
    //    if (! $assessment && $main_category) {
    //        $category_assessment_id = $_SESSION['category_assessments'][$main_category->id] ?? null;
    //        if ($category_assessment_id) {
    //            $assessment = Assessment::find($category_assessment_id);
    //        }
    //    }

    //    $form = $shop_item->findAssessmentForm();
    //    if (! $assessment && $member && $form) {
    //        $assessment = self::query()
    //            ->where('member_id', $member->id)
    //            ->where('form_id', $form->id)
    //            ->orderBy('created_at', 'desc')
    //            ->first()
    //        ;
    //    }

    //    return $assessment;
    //}


    public static function getCompletedForCategory(Category $category, ?Member $member = null)
    {
        $assessment = null;

        $category_assessment_id = $_SESSION['category_assessments'][$category->id] ?? null;

        if ($category_assessment_id) {
            $assessment = Assessment::find($category_assessment_id);
        }

        if (!$assessment && $member) {
            $assessment = self::query()
                ->where('member_id', $member->id)
                ->where('category_id', $category->id)
                ->orderBy('created_at', 'desc')
                ->first()
            ;
        }

        return $assessment;
    }

    /**
     * @param Member|null $member
     *
     * @return void
     */
    public function setMember(?Member $member = null): void
    {
        if (!$member) {
            return;
        }

        if ($this->member_id) {
            return;
        }

        $this->member_id = $member->id;
        $this->save();
    }

    public function getExpiryDate($expires_after_days = 9999)
    {
        $expiry_date = '0000-00-00';

        foreach ($this->answers as $temp_answer) {
            $q = $temp_answer->question;
            if ($q->answer_expires_after_days > 0) {
                if ($q->answer_expires_after_days < $expires_after_days) {
                    $expires_after_days = $q->answer_expires_after_days;
                }
            }
        }

        $created_date = Carbon::createFromFormat('Y-m-d H:i:s', $this->created_at);
        $expiry_date = $created_date->addDays($expires_after_days)->format('Y-m-d');

        return $expiry_date;
    }


    public static function encodeCustomerName(string $name): string
    {
        return base64_encode($name);
    }


    public static function decodeCustomerName(string $base64_string): string
    {
        return base64_decode($base64_string);
    }


    public static function getCompletedForItem(Item $shop_item)
    {
        $assessment = null;

        // findAllAssessmentForms() can return nulls when a form_id points at a
        // deleted Form; guard against that before passing through the typed param.
        $forms = array_filter($shop_item->findAllAssessmentForms());
        foreach ($forms as $temp_form) {
            $assessment = self::getCompletedForForm($temp_form);
            if ($assessment) {
                break;
            }
        }

        return $assessment;
    }


    public static function getCompletedForForm(Form $form)
    {
        $assessment = null;


        $member = Auth::getLoggedInMember();

        $assessment_id = $_SESSION['form_assessments'][$form->id] ?? null;

        if ($assessment_id) {
            $assessment = Assessment::find($assessment_id);
        }

        if (!$assessment && $member) {
            $assessment = self::query()
                ->where('member_id', $member->id)
                ->where(function ($query) use ($form) {
                    $query->where('form_id', $form->id);
                })
                ->orderBy('created_at', 'desc')
                ->first()
            ;

        }

        return $assessment;
    }


    public function getWeight($default_value = null)
    {
        $weight = null;

        foreach ($this->answers as $answer) {
            if ($answer->question->question_type == 'weight') {
                $weight = (int) $answer->answer;
            }
        }

        if (!$weight) {
            $weight = $default_value;
        }

        return $weight;
    }


    public function getAdditionalInfoValue(string $key)
    {
        if (empty($this->additional_info)) {
            return null;
        }

        $info_values = json_decode($this->additional_info);

        return $info_values->dates->{$key} ?? null;
    }


    public function getDestinations(): array
    {
        $destinations = [];

        $info_values = json_decode($this->additional_info);

        $values = $info_values->destinations ?? [];
        foreach ($values as $value) {
            $country_name = Country::getNameByCode($value);
            if ($country_name) {
                $destinations[] = $country_name;
            }
        }

        return $destinations;
    }


    public function getAge($default_value = null): int
    {

        try {
            $age = Carbon::createFromFormat('Y-m-d', $this->customer_dob)->age;
        } catch (\Exception $e) {
            $age = $default_value;
        }

        return (int) $age;
    }

    public function buildProductListBasedOnAnswers(Collection $items): Collection
    {
        $recommended = $this->getRecommendedItems();
        $items = $this->removeRecommendedItemsFromNonRecommended($items, $recommended);

        $items = $items->concat($recommended);
        $items = $this->applyExclusions($items);

        return $this->filterEmptyItems($items);
    }

    /**
     * Have all recommended sizes removed from non-recommended products so there's no duplication
     *
     * @param Collection $items
     * @param Collection $recommended
     * @return Collection
     */
    protected function removeRecommendedItemsFromNonRecommended(
        Collection $items,
        Collection $recommended
    ): Collection {
        /** @var Item $rItem */
        foreach ($recommended as $rItem) {
            // If there are no sizes, remove whole product
            if ($rItem->sizes->count() === 0) {
                $items = $items->reject(function (Item $item) use ($rItem) {
                    return $item->id == $rItem->id;
                });
                continue;
            }
            foreach ($items as $key => $item) {
                $sizes = $item->sizes->reject(function ($size) use ($rItem) {
                    return in_array($size->id, $rItem->sizes->pluck('id')->toArray());
                });
                unset($item->sizes);
                $item->sizes = $sizes->values();
                $items[$key] = $item;
            }
        }

        return $items->values();
    }

    /**
     * Remove excluded items / sizes from the list
     *
     * @param Collection $items
     * @return Collection
     */
    protected function applyExclusions(Collection $items): Collection
    {
        $exclusions = $this->getExclusions();

        foreach ($exclusions as $itemID => $sizeIDs) {

            // If no sizes are set, we remove all items by ID
            if (empty($sizeIDs)) {
                $items = $items->reject(function (Item $item) use ($itemID) {
                    return $item->id == $itemID;
                });
                continue;
            }

            /** @var Item $item */
            foreach ($items as $key => $item) {
                if ($item->id != $itemID) {
                    continue;
                }
                // Item ID matches, remove any size that is in the excluded array
                $sizes = $item->sizes->reject(function ($size) use ($sizeIDs) {
                    return in_array($size->id, $sizeIDs);
                });
                unset($item->sizes);
                $item->sizes = $sizes->values();
                $items[$key] = $item;
            }
        }

        return $items->values();
    }

    /**
     * Remove any items that have sizes but have all removed due to filter
     *
     * @param Collection $items
     * @return Collection
     */
    protected function filterEmptyItems(Collection $items): Collection
    {
        foreach ($items as $key => $item) {
            $originalItem = Item::query()
                ->with('sizes')
                ->find($item->id);

            if ($originalItem->sizes->count() > 0 && $item->sizes->count() === 0) {
                unset($items[$key]);
            }
        }
        return $items->values();
    }

    /**
     * Get recommended products based on assessment answers
     *
     * @return Collection
     */
    public function getRecommendedItems(): Collection
    {
        $questionsWithRecommended = $this->form
            ->questions
            ->whereNotNull('recommended');

        if ($questionsWithRecommended->count() === 0) {
            return collect();
        }

        $allRecommendedItems = collect();

        /** @var Question $question */
        foreach ($questionsWithRecommended as $question) {
            $answer = $this->answers
                ->where('question_id', $question->id)
                ->first();
            if (empty($answer)) {
                continue;
            }
            if (empty($question->recommended[$answer->answer])) {
                continue;
            }
            $recommendedItems = $question->recommended[$answer->answer];
            foreach ($recommendedItems as $itemId => $attributes) {
                $item = Item::query()
                    ->with('images')
                    ->with('sizes')
                    ->active()
                    ->find($itemId);
                if (empty($item)) {
                    continue;
                }
                $item->recommended_from_answers = true;
                if (!empty($attributes['sizes'])) {
                    $sizes = collect([]);
                    foreach ($attributes['sizes'] as $sizeAttributes) {
                        // find() returns null when the configured size_id no longer
                        // exists on the item (e.g. a size was deleted but the question's
                        // recommended mapping still references it). Skip those rather than
                        // pushing null, which produces a sparse [null,...] sizes array and
                        // crashes the product-selection Vue render.
                        $size = $item->sizes->find($sizeAttributes['size_id']);
                        if (empty($size)) {
                            continue;
                        }
                        $sizes->push($size);
                    }
                    unset($item->sizes);
                    $item->sizes = $sizes->values();
                }
                $allRecommendedItems->push($item);
            }
        }

        return $allRecommendedItems->unique();
    }

    /**
     * Get exclusions from answers
     *
     * @return array
     */
    protected function getExclusions(): array
    {
        $questionsWithExcluded = $this->form
            ->questions
            ->whereNotNull('excluded');

        if ($questionsWithExcluded->count() === 0) {
            return [];
        }

        $exclusions = [];

        foreach ($questionsWithExcluded as $question) {
            $answer = $this->answers
                ->where('question_id', $question->id)
                ->first();
            if (empty($answer)) {
                continue;
            }
            if (empty($question->excluded[$answer->answer])) {
                continue;
            }
            $excludedItems = $question->excluded[$answer->answer];
            foreach ($excludedItems as $itemId => $attributes) {
                $item = Item::query()
                    ->with('images')
                    ->with('sizes')
                    ->active()
                    ->find($itemId);
                if (empty($item)) {
                    continue;
                }
                if (empty($exclusions[$itemId])) {
                    $exclusions[$itemId] = [];
                }
                if (!empty($attributes['sizes'])) {
                    foreach ($attributes['sizes'] as $sizeAttributes) {
                        if (!in_array($sizeAttributes['size_id'], $exclusions[$itemId])) {
                            $exclusions[$itemId][] = $sizeAttributes['size_id'];
                        }
                    }
                }
            }
        }
        return $exclusions;
    }
}
