<?php

namespace MtcPharmacy\Subscriptions\Classes;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Mtc\Modules\Members\Classes\Auth;
use Mtc\Modules\Members\Models\Member;
use Mtc\Shop\Basket\Address as BasketAddress;
use Mtc\Shop\Basket\Item as BasketItem;
use Mtc\Shop\Checkout\OrderWorldpay;
use Mtc\Shop\Order;


class Subscription extends Model
{
    const STATUS_PENDING = 'pending';
    const STATUS_ACTIVE = 'active';
    const STATUS_PAUSED = 'paused';
    const STATUS_EXPIRED = 'expired';
    const STATUS_PAYMENT_FAILED = 'payment-failed';
    const STATUS_CANCELLED = 'cancelled';

    const INTERVAL_DAYS = 'days';
    const INTERVAL_WEEKS = 'weeks';
    const INTERVAL_MONTHS = 'months';


    protected $table = 'subscriptions';

    protected $fillable = [
        'label',
        'price_per_issue',
        'interval_multiplier',
        'interval',
        'status',
        'can_be_paused',
        'max_number_of_issues',
        'expiry_date',
        'next_issue_date',
    ];

    private $rules = [
        'interval_multiplier' => 'integer | required',
        'interval' => 'required',
        'status' => 'required',
        'can_be_paused' => 'nullable | boolean',
        'max_number_of_issues' => 'nullable | integer | min:1',
        'expiry_date' => 'nullable | date',
        'next_issue_date' => 'nullable | date',
        'price_per_issue' => 'nullable | numeric',
    ];


    private function getValidationRules()
    {
        $this->rules['status'] = [ 'required', Rule::in(Subscription::get_allowed_statuses()) ];
        $this->rules['interval'] = [ 'required', Rule::in(Subscription::get_allowed_intervals()) ];

        return $this->rules;
    }


    public function validate()
    {
        return Validator::make($this->attributes, $this->getValidationRules());
    }



    public function items()
    {
        return $this->hasMany(SubscriptionItem::class);
    }


    public function orders()
    {
        return $this->belongsToMany(Order::class, 'subscriptions_orders');
    }


    public function member()
    {
        return $this->belongsTo(Member::class);
    }


    public static function get_allowed_statuses()
    {
        return [
            Subscription::STATUS_PENDING,
            Subscription::STATUS_ACTIVE,
            Subscription::STATUS_PAUSED,
            Subscription::STATUS_EXPIRED,
            Subscription::STATUS_PAYMENT_FAILED,
            Subscription::STATUS_CANCELLED,
        ];
    }


    public static function get_allowed_intervals()
    {
        return [
            Subscription::INTERVAL_DAYS,
            Subscription::INTERVAL_WEEKS,
            Subscription::INTERVAL_MONTHS,
        ];
    }


    public function scopeActive($query)
    {
        return $query->where('status', Subscription::STATUS_ACTIVE);
    }


    public function isPaused()
    {
        return (bool)($this->status == Subscription::STATUS_PAUSED);
    }

    public function setPaused()
    {
        $this->status = Subscription::STATUS_PAUSED;
        $this->save();
    }


    public function isActive()
    {
        return (bool)($this->status == Subscription::STATUS_ACTIVE);
    }

    public function setActive()
    {
        $this->status = Subscription::STATUS_ACTIVE;
        $this->save();
    }


    public function isCancelled()
    {
        return (bool)($this->status == Subscription::STATUS_CANCELLED);
    }

    public function setCancelled()
    {
        $this->status = Subscription::STATUS_CANCELLED;
        $this->save();
    }


    public function setFailedPayment()
    {
        $this->status = Subscription::STATUS_PAYMENT_FAILED;
        $this->save();
    }

    public function hasFailedPayment()
    {
        return (bool)($this->status == Subscription::STATUS_PAYMENT_FAILED);
    }

    public function getLatestFailedPayment()
    {
        $order = $this
            ->orders()
            ->where('order.paid', false)
            ->orderBy('order_id', 'desc')
            ->first()
        ;

        return $order;
    }


    public function getLatestSuccessfulOrder()
    {
        $order = $this
            ->orders()
            ->where('order.paid', true)
            ->orderBy('order_id', 'desc')
            ->first()
        ;

        return $order;
    }


    public function getLatestOrder()
    {
        $order = $this
            ->orders()
            ->orderBy('order_id', 'desc')
            ->first()
        ;

        return $order;
    }


    public function getCanBePaused()
    {
        return (bool)$this->can_be_paused ?? true;
    }


    public function getCanBePausedString()
    {
        return $this->getCanBePaused() ? 'Y' : 'N';
    }


    public function getMaxNumberOfIssues()
    {
        return $this->max_number_of_issues ?? 99;
    }


    public function getPricePerIssue()
    {
        $price = 0;

        foreach ($this->items as $item) {
            if ($item->shop_item_size) {
                $price += $item->shop_item_size->price;
            } else {
                $price += $item->shop_item->price;
            }
        }

        return $this->price_per_issue ?? $price;
    }


    public function getNextIssueDate() : string
    {
        $next_date = $this->next_issue_date;

        if (! $next_date) {
            $latest_order = $this->getLatestOrder();

            if ($latest_order) {
                $latest_order_date = Carbon::createFromFormat('Y-m-d H:i:s', $latest_order->date);
                $next_order_date = $latest_order_date->addMonths($this->interval_multiplier);
                $next_date = $next_order_date->format('Y-m-d');
            } else {
                $next_date = '0000-00-00';
            }
        }

        return $next_date;
    }


    public function authoriseAccessByCurrentUser()
    {
        $can_access = false;

        $logged_in_member = Auth::getLoggedInMember();

        if ($logged_in_member->id == $this->member_id) {
            $can_access = true;
        }

        if (! $can_access) {
            abort(403, 'You have no permissions to access this subscription.');
        }
    }


    private function getShopItemCategories()
    {
        $categories = collect([]);

        foreach ($this->items as $sub_item) {
            $category = $sub_item->shop_item->getMainCategory();
            if ($category) {
                $categories->push($category);
            }
        }

        return $categories;
    }


    public function getSimilarSubscriptions() : array
    {
        $similar_subscriptions = [];

        $potentially_active_subscriptions = self::query()
            ->where('member_id', $this->member->id)
            ->where('id', '!=', $this->id)
            ->whereNotIn('status', [ Subscription::STATUS_EXPIRED, Subscription::STATUS_CANCELLED ])
            ->get()
        ;

        $subscription_item_category_ids = $this->getShopItemCategories()->pluck('id');

        $temp_items = collect([]);
        foreach ($potentially_active_subscriptions as $sub) {
            foreach ($sub->items as $sub_item) {
                $shop_item = $sub_item->shop_item;
                if (! $shop_item) continue;

                if (! $shop_item->product_type) continue; // Ignore general products.

                if (! $subscription_item_category_ids->contains($shop_item->getMainCategory()->id)) continue;

                $similar_subscriptions[] = $sub;
            }

        }

        return $similar_subscriptions;
    }


    public function getIssueNumber(Order $order)
    {
        return $this
            ->orders()
            ->where('order.date', '<=', $order->date)
            ->count()
        ;
    }


    public function canOrderBeGenerated($print_debug_info = false)
    {
        $can = true;

        if (! $this->isActive()) {
            $can = false;
            $print_debug_info and var_dump('status');
        }

        $today = Carbon::now();

        $expiry_date = Carbon::createFromFormat('Y-m-d', $this->expiry_date);
        if ($today >= $expiry_date) {
            $can = false;
            $print_debug_info and var_dump('expiry date');
        }

        $next_issue_date = Carbon::createFromFormat('Y-m-d', $this->getNextIssueDate());
        if ($today != $next_issue_date) {
            $can = false;
            $print_debug_info and var_dump('next issue date');
        }

        if ($this->orders()->count() >= $this->max_number_of_issues) {
            $can = false;
            $print_debug_info and var_dump('max number of issues');
        }

        return $can;
    }


    public function generateNewOrder($ignore_checks = false, $attempt_token_payment = false)
    {
        $order = null;

        if ($ignore_checks || $this->canOrderBeGenerated()) {

            $basket = new \Mtc\Shop\Basket();
            $basket->customer()->associate($this->member);
            $basket->subscription()->associate($this);
            $basket->save();

            foreach ($this->items as $subscription_item) {
                $basket_item = new BasketItem();
                $basket_item->item_id = $subscription_item->shop_item->id;
                $basket_item->quantity = 1;
                $basket_item->PLU = $subscription_item->shop_item->name;
                $basket_item->save();
                $basket->items()->save($basket_item);
            }

            $shipping_address_data = $this->member->addressShipping->toArray();
            $order_shipping_address = new BasketAddress($shipping_address_data);
            $basket->shippingAddress()->save($order_shipping_address);

            $billing_address_data = $this->member->addressBilling->toArray();
            $order_billing_address = new BasketAddress($billing_address_data);
            $basket->billingAddress()->save($order_billing_address);

            $basket_legacy = $basket->legacy();

            Order::newOrder($basket_legacy);
            $order = Order::find($basket_legacy->order_id);
            $order->legacy()->updateStatus(Order::STATUS_PROCESSING);

            $this->orders()->attach($order);
            $order->save();

            if ($attempt_token_payment) {
                $this->collectPayment($order);
                $order->refresh();
            }

        }

        return $order;
    }


    private function collectPayment(Order $order)
    {
        $last_successful_order = $this->getLatestSuccessfulOrder();
        $worldpay_token = OrderWorldpay::getPaymentToken($last_successful_order->id);

        try {
            $response = OrderWorldpay::repeatOrder([
                'token' => $worldpay_token,
                'order_id' => $order->id,
                'amount' => $order->cost_total,
            ]);
        } catch (\Exception $e) {
            error_log($e->getMessage());
        }

        if (isset($response['paymentStatus']) && $response['paymentStatus'] == 'SUCCESS') {
            $order->legacy()->markPaid(\Order::PAYMENT_TYPE_WORLDPAY);
            $order->legacy()->saveWorldpay($response);
        } else {
            $this->setFailedPayment();
            Event::dispatch('subscriptions.paymentFailed', $this);
        }
    }
}
