<?php

namespace App\Admin;

use App\Src\Encryption;
use Carbon\Carbon;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Mtc\Core\Admin\User;
use Mtc\Core\Permissions;
use Mtc\Shop\Category;
use Mtc\Shop\Item;
use Mtc\Shop\Order;


class OrdersHelper
{

    const ORDER_TYPE_READY = 'ready';
    const ORDER_TYPE_APPROVED = 'approved';
    const ORDER_TYPE_REJECTED = 'rejected';
    const ORDER_TYPE_AWAITING = 'awaiting';
    const ORDER_TYPE_PROCESSING = 'processing';
    const ORDER_TYPE_PENDING_REVIEW = 'pending_review';
    const ORDER_TYPE_REFUNDED = 'refunded';
    const ORDER_TYPE_COMPLETED = 'completed';
    const ORDER_TYPE_CANCELLED = 'cancelled';
    const ORDER_TYPE_RETURNED = 'returned';
    const ORDER_TYPE_NO_RESPONSE = 'no_response';
    const ORDER_TYPE_UNPAID = 'unpaid';

    public array $params;

    public function __construct($params)
    {
        $this->params = $params;
    }

    protected function param($name)
    {
        return $this->params[$name] ?? null;
    }

    /**
     * Gets categories for filter
     *
     * @return array
     */
    protected function getFilterCategories(): array
    {
        $allowedCategories = User::getAllowedCategories();
        $hasAllowed = $allowedCategories instanceof \Illuminate\Support\Collection && $allowedCategories->isNotEmpty();

        if (empty($this->params['category']) && !$hasAllowed) {
            return [];
        }

        if ($hasAllowed) {
            if (!empty($this->params['category']) && in_array((int)$this->params['category'], $allowedCategories->map(static fn($id) => (int)$id)->all(), true)) {
                return Category::getFlatTree($this->params['category'], true);
            }
            $selectCategories = [];
            foreach ($allowedCategories as $category) {
                $selectCategories = array_merge(
                    $selectCategories,
                    Category::getFlatTree($category, true)
                );
            }
            return $selectCategories;
        }
        return Category::getFlatTree($this->params['category'], true);
    }

    /**
     * Gets order query based on tab and params
     *
     * @param string|null $orderType
     * @return Builder
     */
    public function getOrdersBuilderQuery(?string $orderType = null): Builder
    {
        $type = $orderType;

        $filterCategories = $this->getFilterCategories();
        $joiningOrderInfo =
            !empty($this->params['contact_no']) ||
            !empty($this->params['date_of_birth']) ||
            !empty($this->params['email']);
        $filterByName = !empty($this->params['firstname']) || !empty($this->params['lastname']);
        $hideArchived = !in_array($type, Order::$order_types[self::ORDER_TYPE_COMPLETED]);
        $basketBuilder = !empty($this->params['basket_builder']) && config('basketbuilder.enabled');

        $filterByStatus = is_numeric($this->param('status'));
        $needsAddressFilter =
            !empty($this->params['postcode']) ||
            $filterByName;

        $query = Order::query()
            ->selectRaw('
                `order`.*,
                (
                    SELECT
                        COUNT(past_orders.id)
                    FROM
                        `order` past_orders
                    WHERE
                        past_orders.id < `order`.id AND
                        past_orders.member = `order`.member AND
                        past_orders.member > 0 AND
                        past_orders.paid = 1 AND
                        past_orders.`date` > DATE_SUB(`order`.`date`, INTERVAL 28 DAY)
                ) as ordered_within_four_weeks
            ')
            ->when($type !== self::ORDER_TYPE_UNPAID, function (Builder $query) {
                $query->where('paid', 1);
            })
            ->where('failed', 0)
            ->when($type !== self::ORDER_TYPE_CANCELLED, function (Builder $query) {
                $query->whereNotIn('status', array_keys(Order::$failure_statuses));
            })
            // LEFT JOIN so orders that lost their last line item (e.g. admin
            // refund-then-delete) still appear in their respective tab. The
            // outer query is ->distinct()'d, so duplicates from the join are
            // already collapsed.
            ->leftJoin('order_items', 'order_items.order_id', '=', 'order.id')
            ->leftJoin('order_address', 'order.id', '=', 'order_address.order_id')
            ->when(!empty($this->params['product_type']), function (Builder $query) {
                $query->whereHas('items', function (Builder $query) {
                    $query->whereHas('item', function (Builder $query) {
                        $productType = $this->params['product_type'] === 'general'
                            ? ''
                            : $this->params['product_type'];
                        $query->where('items.product_type', $productType);
                        if ($productType !== Item::TYPE_CONSULTATION) {
                            $query->where('items.product_type', '!=', Item::TYPE_CONSULTATION);
                        }
                    });
                });
            })
            ->when(empty($this->params['product_type']), function (Builder $query) {
                // "Hide consultations" is the default rule. A zero-item order
                // can't be a consultation, so let it pass — otherwise admins
                // lose visibility of refunded orders whose last line was deleted.
                $query->where(function (Builder $query) {
                    $query->whereDoesntHave('items')
                        ->orWhereHas('items', function (Builder $query) {
                            $query->whereHas('item', function (Builder $query) {
                                $query->where('items.product_type', '!=', Item::TYPE_CONSULTATION);
                            });
                        });
                });
            })
            ->when(Permissions::can(Permissions::DOCTOR_ORDER_VIEW), function (Builder $query) {
                $query->whereHas('items', function (Builder $query) {
                    $query->whereHas('item', function (Builder $query) {
                        $query->where('items.product_type', Item::TYPE_DOCTOR);
                    });
                });
            })
            ->when(!empty($filterCategories), function (Builder $query) use ($filterCategories) {
                $query->leftJoin('items_categories', 'order_items.item_id', '=', 'items_categories.item_id')
                    ->whereIn('items_categories.cat_id', $filterCategories);
            })
            ->when($joiningOrderInfo, function (Builder $query) {
                $query->leftJoin('order_info', 'order_info.order_id', '=', 'order.id')
                    ->when(!empty($this->params['contact_no']), function (Builder $query) {
                        $query->where('order_info.contact_no_hash', Encryption::makeHash($this->params['contact_no']));
                    })
                    ->when(!empty($this->params['date_of_birth']), function (Builder $query) {
                        try {
                            $DOB = Carbon::createFromFormat('d/m/Y', $this->params['date_of_birth']);
                            $query->where('order_info.dob_hash', $DOB);
                        } catch (Exception) {
                        }
                    })
                    ->when(!empty($this->params['email']), function (Builder $query) {
                        $query->where('order_info.email_hash', Encryption::makeHash($this->params['email']));
                    });
            })
            ->when(!empty($this->params['coupon']), function (Builder $query) {
                $query->leftJoin('order_coupon', 'order.id', '=', 'order_coupon.order_id')
                    ->where(function (Builder $query) {
                        $query->where('order_coupon.code', $this->params['coupon'])
                            ->orWhere('order_coupon.code', 'LIKE', $this->params['coupon'] . '-%');
                    });
            })
            ->when(!empty($this->params['id']), function (Builder $query) {
                $query->where('order.order_ref', 'LIKE', str_replace('*', '%', $this->params['id']));
            })
            ->when($needsAddressFilter, function (Builder $query) {
                if (!empty($this->params['postcode'])) {
                    $query->where('order_address.postcode', 'LIKE', str_replace('*', '%', $this->params['postcode']));
                }
                if (!empty($this->params['firstname'])) {
                    $query->where('order_address.firstname_hash', Encryption::makeHash($this->params['firstname']));
                }
                if (!empty($this->params['lastname'])) {
                    $query->where('order_address.lastname_hash', Encryption::makeHash($this->params['lastname']));
                }
            })
            ->when(!empty($this->params['start_date']), function (Builder $query) {
                $datearray = explode("/", $this->params['start_date']);
                $startdate = $datearray[2] . "-" . $datearray[1] . "-" . $datearray[0] . ' 00:00:00';
                $query->where('order.date', '>=', $startdate);
            })
            ->when(!empty($this->params['end_date']), function (Builder $query) {
                $datearray = explode("/", $this->params['end_date']);
                $enddate = $datearray[2] . "-" . $datearray[1] . "-" . $datearray[0] . ' 23:59:59';
                $query->where('order.date', '<=', $enddate);
            })
            ->when(!empty($this->params['type']), function (Builder $query) {
                $query->where('order.checkout', 'LIKE', $this->params['type']);
            })
            ->when($this->param('trustpilot_status') === 'sent', function (Builder $query) {
                $query->where('order.trustpilot_sent', '<>', '0000-00-00 00:00:00');
            })
            ->when($this->param('trustpilot_status') === 'not_sent', function (Builder $query) {
                $query->where('order.trustpilot_sent', '0000-00-00 00:00:00');
            })
            ->when($this->param('approval_status') === 'approved', function (Builder $query) {
                $query->whereDoesntHave('items', function (Builder $query) {
                    $query->where('order_items.approved', '!=', 1);
                })->whereDoesntHave('items', function (Builder $query) {
                    $query->whereHas('refund_items', function (Builder $query) {
                        $query->where('order_refund_items.quantity', '>', 0);
                    });
                });
            })
            ->when($this->param('approval_status') === 'not_approved', function (Builder $query) {
                $query->where(function (Builder $query) {
                    $query->where('order.status', Order::STATUS_WAITING_FOR_RESPONSE)
                        ->orWhereHas('items', function (Builder $query) {
                            $query->where('order_items.approved', '!=', 1)
                                ->whereDoesntHave('refund_items', function (Builder $query) {
                                    $query->where('order_refund_items.quantity', '>', 0);
                                });
                        });
                });
            })
            ->when($this->param('doctor_note') === 'not_empty', function (Builder $query) {
                $query->where('order.doctor_note', '!=', '');
            })
            ->when($this->param('doctor_note') === 'empty', function (Builder $query) {
                $query->where('order.doctor_note', '');
            })
            ->when($this->param('pharmacist_note') === 'not_empty', function (Builder $query) {
                $query->where('order.pharmacist_note', '!=', '');
            })
            ->when($this->param('pharmacist_note') === 'empty', function (Builder $query) {
                $query->where('order.pharmacist_note', '');
            })
            ->when($this->param('shipment_created') === 'yes', function (Builder $query) {
                $query->where('order.shipping_label', '!=', '');
            })
            ->when($this->param('shipment_created') === 'no', function (Builder $query) {
                $query->where('order.shipping_label', '');
            })
            ->when($hideArchived, function (Builder $query) {
                $query->where('order.archived', 0);
            })
            ->when($filterByStatus, function (Builder $query) {
                $query->where('order.status', $this->params['status']);
            })
            ->when($basketBuilder, function (Builder $query) {
                $query->where('order.basket_builder', 1);
            })
            ->distinct();

        if ($type === self::ORDER_TYPE_PENDING_REVIEW) {

            $query = $query->whereIn('order.status', Order::$order_types['processing'])
                ->whereHas('items', function (Builder $query) {
                    $query->where('order_items.approved', 0);
                });

        } elseif ($type === self::ORDER_TYPE_APPROVED) {

            $query = $query->whereIn('order.status', Order::$order_types['processing'])
                ->whereHas('items', function (Builder $query) {
                    $query->where('order_items.approved', 1);
                })
                ->when(Permissions::can(Permissions::PHARMACIST_ORDER_VIEW), function (Builder $query) {
                    $query->whereHas('items', function (Builder $query) {
                        $query->where('clinic_checked', 0)
                            ->whereHas('item', function (Builder $query) {
                                $query->where('items.product_type', Item::TYPE_DOCTOR);
                            });
                    });
                });

        } elseif ($type === self::ORDER_TYPE_REJECTED) {

            $query = $query->whereHas('items', function (Builder $query) {
                $query->where('order_items.approved', 2);
            });

        } elseif ($type === self::ORDER_TYPE_CANCELLED) {

            $query = $query->where('order.status', Order::STATUS_CANCELLED);

        } elseif ($type === self::ORDER_TYPE_READY) {

            // Ready order only to show fully reviewed orders:
            //    - Doctor items to be approved and clinically checked
            //    - Pharmacy items to be approved
            $query = $query->whereIn('order.status', Order::$order_types['processing'])
                ->where(function (Builder $query) {
                    $query->whereHas('items', function (Builder $query) {
                        $query->where('approved', 1)
                            ->where('clinic_checked', 1)
                            ->whereHas('item', function (Builder $query) {
                                $query->where('items.product_type', Item::TYPE_DOCTOR);
                            });
                    })->orWhereHas('items', function (Builder $query) {
                        $query->where('approved', 1)
                            ->whereHas('item', function (Builder $query) {
                                $query->where('items.product_type', Item::TYPE_PHARMACY);
                            });
                    })->orWhereHas('items', function (Builder $query) {
                        $query->whereHas('item', function (Builder $query) {
                            $query->where('items.product_type', '');
                        });
                    });
                })
                ->whereDoesntHave('items', function (Builder $query) {
                    // Doctor items that are either:
                    // - not yet reviewed by doctor
                    // - approved by doctor, but not yet clinically checked by pharmacist
                    $query->whereIn('approved', [0, 1])
                        ->where('clinic_checked', 0)
                        ->whereHas('item', function (Builder $query) {
                            $query->where('items.product_type', Item::TYPE_DOCTOR);
                        });
                })->whereDoesntHave('items', function (Builder $query) {
                    // Pharmacy products that are not approved by pharmacist
                    $query->where('approved', 0)
                        ->whereHas('item', function (Builder $query) {
                            $query->where('items.product_type', Item::TYPE_PHARMACY);
                        });
                });

        } elseif (!empty($type)) {

            $query = $query->whereIn('order.status', Order::$order_types[$type]);

        } elseif (Permissions::can(Permissions::DOCTOR_ORDER_VIEW)) {

            $query = $query->where('order.status', Order::STATUS_PROCESSING);

        }

        return $query;

    }

}
