<?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 function __construct(public array $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']);
        $showNhsOrders = ! empty($this->params['nhs_orders_only']);
        $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, static function (Builder $query): void {
                $query->where('paid', 1);
            })
            ->where('failed', 0)
            ->when($type !== self::ORDER_TYPE_CANCELLED, static function (Builder $query): void {
                $query->whereNotIn('status', array_keys(Order::$failure_statuses));
            })
            ->join('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): void {
                $query->whereHas('items', function (Builder $query): void {
                    $query->whereHas('item', function (Builder $query): void {
                        $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']), static function (Builder $query): void {
                $query->whereHas('items', static function (Builder $query): void {
                    $query->whereHas('item', static function (Builder $query): void {
                        $query->where('items.product_type', '!=', Item::TYPE_CONSULTATION);
                    });
                });
            })
            ->when(Permissions::can(Permissions::DOCTOR_ORDER_VIEW), static function (Builder $query): void {
                $query->whereHas('items', static function (Builder $query): void {
                    $query->whereHas('item', static function (Builder $query): void {
                        $query->where('items.product_type', Item::TYPE_DOCTOR);
                    });
                });
            })
            ->when(! empty($filterCategories), static function (Builder $query) use ($filterCategories): void {
                $query->leftJoin('items_categories', 'order_items.item_id', '=', 'items_categories.item_id')
                    ->whereIn('items_categories.cat_id', $filterCategories);
            })
            ->when($joiningOrderInfo, function (Builder $query): void {
                $query
                    ->leftJoin('order_info', 'order_info.order_id', '=', 'order.id')
                    ->when(! empty($this->params['contact_no']), function (Builder $query): void {
                        $query->where('order_info.contact_no_hash', Encryption::makeHash($this->params['contact_no']));
                    })
                    ->when(! empty($this->params['date_of_birth']), function (Builder $query): void {
                        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): void {
                        $query->where('order_info.email_hash', Encryption::makeHash($this->params['email']));
                    });
            })
            ->when($showNhsOrders, static function (Builder $query): void {
                $query->where('order_items.nhs_prescription', 1);
            })
            ->when(! $showNhsOrders, static function (Builder $query): void {
                $query->where('order_items.nhs_prescription', 0);
            })
            ->when(! empty($this->params['coupon']), function (Builder $query): void {
                $query
                    ->leftJoin('order_coupon', 'order.id', '=', 'order_coupon.order_id')
                    ->where(function (Builder $query): void {
                        $query->where('order_coupon.code', $this->params['coupon'])
                            ->orWhere('order_coupon.code', 'LIKE', $this->params['coupon'].'-%');
                    });
            })
            ->when(! empty($this->params['id']), function (Builder $query): void {
                $query->where('order.order_ref', 'LIKE', str_replace('*', '%', $this->params['id']));
            })
            ->when($needsAddressFilter, function (Builder $query): void {
                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): void {
                $datearray = explode('/', (string) $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): void {
                $datearray = explode('/', (string) $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): void {
                $query->where('order.checkout', 'LIKE', $this->params['type']);
            })
            ->when($this->param('trustpilot_status') === 'sent', static function (Builder $query): void {
                $query->where('order.trustpilot_sent', '<>', '0000-00-00 00:00:00');
            })
            ->when($this->param('trustpilot_status') === 'not_sent', static function (Builder $query): void {
                $query->where('order.trustpilot_sent', '0000-00-00 00:00:00');
            })
            ->when($this->param('approval_status') === 'approved', static function (Builder $query): void {
                $query->whereDoesntHave('items', static function (Builder $query): void {
                    $query->where('order_items.approved', '!=', 1);
                })->whereDoesntHave('items', static function (Builder $query): void {
                    $query->whereHas('refund_items', static function (Builder $query): void {
                        $query->where('order_refund_items.quantity', '>', 0);
                    });
                });
            })
            ->when($this->param('approval_status') === 'not_approved', static function (Builder $query): void {
                $query->where(static function (Builder $query): void {
                    $query->where('order.status', Order::STATUS_WAITING_FOR_RESPONSE)
                        ->orWhereHas('items', static function (Builder $query): void {
                            $query->where('order_items.approved', '!=', 1)
                                ->whereDoesntHave('refund_items', static function (Builder $query): void {
                                    $query->where('order_refund_items.quantity', '>', 0);
                                });
                        });
                });
            })
            ->when($this->param('doctor_note') === 'not_empty', static function (Builder $query): void {
                $query->where('order.doctor_note', '!=', '');
            })
            ->when($this->param('doctor_note') === 'empty', static function (Builder $query): void {
                $query->where('order.doctor_note', '');
            })
            ->when($this->param('pharmacist_note') === 'not_empty', static function (Builder $query): void {
                $query->where('order.pharmacist_note', '!=', '');
            })
            ->when($this->param('pharmacist_note') === 'empty', static function (Builder $query): void {
                $query->where('order.pharmacist_note', '');
            })
            ->when($this->param('shipment_created') === 'yes', static function (Builder $query): void {
                $query->where('order.shipping_label', '!=', '');
            })
            ->when($this->param('shipment_created') === 'no', static function (Builder $query): void {
                $query->where('order.shipping_label', '');
            })
            ->when($hideArchived, static function (Builder $query): void {
                $query->where('order.archived', 0);
            })
            ->when($filterByStatus, function (Builder $query): void {
                $query->where('order.status', $this->params['status']);
            })
            ->when($basketBuilder, static function (Builder $query): void {
                $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', static function (Builder $query): void {
                    $query->where('order_items.approved', 0);
                });
        } elseif ($type === self::ORDER_TYPE_APPROVED) {
            $query = $query
                ->whereIn('order.status', Order::$order_types['processing'])
                ->whereHas('items', static function (Builder $query): void {
                    $query->where('order_items.approved', 1);
                })
                ->when(Permissions::can(Permissions::PHARMACIST_ORDER_VIEW), static function (Builder $query): void {
                    $query->whereHas('items', static function (Builder $query): void {
                        $query->where('clinic_checked', 0)
                            ->whereHas('item', static function (Builder $query): void {
                                $query->where('items.product_type', Item::TYPE_DOCTOR);
                            });
                    });
                });
        } elseif ($type === self::ORDER_TYPE_REJECTED) {
            $query = $query->whereHas('items', static function (Builder $query): void {
                $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(static function (Builder $query): void {
                    $query
                        ->whereHas('items', static function (Builder $query): void {
                            $query
                                ->where('approved', 1)
                                ->where('clinic_checked', 1)
                                ->whereHas('item', static function (Builder $query): void {
                                    $query->where('items.product_type', Item::TYPE_DOCTOR);
                                });
                        })
                        ->orWhereHas('items', static function (Builder $query): void {
                            $query->where('approved', 1)
                                ->whereHas('item', static function (Builder $query): void {
                                    $query->where('items.product_type', Item::TYPE_PHARMACY);
                                });
                        })
                        ->orWhereHas('items', static function (Builder $query): void {
                            $query->whereHas('item', static function (Builder $query): void {
                                $query->where('items.product_type', '');
                            });
                        });
                })
                ->whereDoesntHave('items', static function (Builder $query): void {
                    // 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', static function (Builder $query): void {
                            $query->where('items.product_type', Item::TYPE_DOCTOR);
                        });
                })
                ->whereDoesntHave('items', static function (Builder $query): void {
                    // Pharmacy products that are not approved by pharmacist
                    $query->where('approved', 0)
                        ->whereHas('item', static function (Builder $query): void {
                            $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;
    }
}
