<?php

namespace Mtc\Plugins\Clinic\Src\Models;

use App\Casts\OptionalEncrypted;
use App\Src\Encryption;
use Carbon\Carbon;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Mtc\Core\Admin\User;
use Mtc\Core\AdminUser;
use Mtc\Core\Permissions;
use Mtc\Modules\Members\Models\Member;
use Mtc\Shop\Assessment\Assessment;
use Mtc\Shop\Assessment\Form;
use Mtc\Shop\Category;
use Mtc\Shop\Order;

/**
 * Accessors
 *
 * @property int $id
 * @property int $member_id
 * @property int|null $form_id
 * @property int|null $category_id
 * @property string $type
 * @property string $status
 * @property string $medium
 * @property string $due_date
 * @property string $notes
 * @property string $hash
 *
 * @property Carbon $created_at
 * @property Carbon $updated_at
 *
 * @property Member $member
 * @property Collection $reviews
 * @property Form $form
 * @property Category $category
 */
class Review extends Model
{
    protected $table = 'reviews';

    protected $fillable = [
        'member_id',
        'form_id',
        'category_id',
        'user_id',
        'status',
        'type',
        'medium',
        'due_date',
        'notes',
        'hash',
    ];

    protected $casts = [
        'notes' => OptionalEncrypted::class,
    ];

    public static array $intervals = [
        2,
        4,
        6,
        8,
        12,
    ];

    const MEDIUM_ONLINE = 'online';
    const MEDIUM_VIDEO = 'video';
    const MEDIUM_PHONE = 'phone';

    public static array $mediums = [
        self::MEDIUM_ONLINE => 'Online assessment',
        self::MEDIUM_VIDEO => 'Video consultation',
        self::MEDIUM_PHONE => 'Phone call',
    ];

    const TYPE_NEW = 'new';
    const TYPE_REPEATED = 'repeated';

    public static array $types = [
        self::TYPE_NEW => 'New',
        self::TYPE_REPEATED => 'Repeated',
    ];

    const STATUS_UNSCHEDULED = 'unscheduled';
    const STATUS_PENDING = 'pending';
    const STATUS_SUBMITTED = 'submitted';
    const STATUS_COMPLETE = 'complete';
    const STATUS_CANCELLED = 'cancelled';

    public static array $statuses = [
        self::STATUS_UNSCHEDULED => 'To be scheduled',
        self::STATUS_PENDING => 'Pending',
        self::STATUS_SUBMITTED => 'Submitted',
        self::STATUS_COMPLETE => 'Complete',
        self::STATUS_CANCELLED => 'Cancelled',
    ];

    /**
     * @return BelongsTo
     */
    public function form(): BelongsTo
    {
        return $this->belongsTo(Form::class, 'form_id');
    }

    /**
     * @return BelongsTo
     */
    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class, 'category_id');
    }

    /**
     * @return BelongsTo
     */
    public function user(): BelongsTo
    {
        return $this->belongsTo(AdminUser::class, 'user_id');
    }

    /**
     * @return BelongsTo
     */
    public function member(): BelongsTo
    {
        return $this->belongsTo(Member::class, 'member_id');
    }

    /**
     * @return HasMany
     */
    public function assessments(): HasMany
    {
        return $this->hasMany(Assessment::class, 'review_id');
    }

    /**
     * @return HasMany
     */
    public function orders(): HasMany
    {
        return $this->hasMany(Order::class, 'review_id');
    }

    /**
     * @return HasMany
     */
    public function brTracks(): HasMany
    {
        return $this->hasMany(BpTrack::class, 'review_id');
    }

    /**
     * @return HasMany
     */
    public function weightTracks(): HasMany
    {
        return $this->hasMany(WeightTrack::class, 'review_id');
    }

    /**
     * Get date interval options
     *
     * @return array
     */
    public static function getDateOptions(): array
    {
        $options = [];
        foreach (self::$intervals as $interval) {
            $options[Carbon::now()->addWeeks($interval)->format('d/m/Y')] = $interval . ' weeks';
        }
        return $options;
    }

    /**
     * Simple validator
     *
     * @param array $input
     * @return array
     */
    public static function validate(array $input): array
    {
        $required = [
            'due_date',
            'member_id',
        ];

        $errors = [];

        foreach ($required as $field) {
            if (empty($input[$field])) {
                $errors[$field] = 'This field is required';
            }
        }
        return $errors;
    }

    /**
     * Get review alerts displayed on the dashboard
     *
     * @param array $filterParams
     * @return Collection
     */
    public static function getReviewAlerts(array $filterParams): Collection
    {
        $adminUser = User::getAdminUser();

        $reviewsQuery = self::query()
            ->with('member.addressBilling')
            ->with('category')
            ->with('form')
            ->with('user')
            ->where(function (Builder $query) {
                $query->where(function (Builder $query) {
                    // Unscheduled reviews that are due in less than 7 days
                    $weekFromNow = Carbon::now()
                        ->addWeek()
                        ->format('Y-m-d');

                    $query->whereIn('status', [
                        self::STATUS_UNSCHEDULED
                    ])
                        ->where('due_date', '<', $weekFromNow);
                })
                    ->orWhere(function (Builder $query) {
                        // Pending reviews that are overdue
                        $query->whereIn('status', [
                            self::STATUS_PENDING
                        ])
                            ->where('due_date', '<', Carbon::now()->format('Y-m-d'));
                    })
                    ->orWhere('status', Review::STATUS_SUBMITTED);
            })
            ->when(Permissions::can(Permissions::SEE_ALL_REVIEW_ALERTS), function (Builder $query) use ($adminUser) {
                // Doctors and pharmacists to only see their own review alerts
                $query->where('user_id', $adminUser->id);
            });

        return self::filterReviews($reviewsQuery, $filterParams)
            ->orderBy('due_date')
            ->get();
    }

    /**
     * Apply filter
     *
     * @param Builder $query
     * @param array $filters
     * @return Builder
     */
    public static function filterReviews(Builder $query, array $filters): Builder
    {
        if (!empty($filters['id'])) {
            $query->where('id', $filters['id']);
        }

        if (!empty($filters['status'])) {
            $query->where('status', $filters['status']);
        }

        if (!empty($filters['member_id'])) {
            $query->where('member_id', $filters['member_id']);
        }

        if (!empty($filters['form_id'])) {
            $query->where('form_id', $filters['form_id']);
        }

        if (!empty($filters['category_id'])) {
            $query->where('category_id', $filters['category_id']);
        }

        if (!empty($filters['type'])) {
            $query->where('type', $filters['type']);
        }

        if (!empty($filters['medium'])) {
            $query->where('medium', $filters['medium']);
        }

        if (!empty($filters['due_date_from'])) {
            $dateFrom = Carbon::createFromFormat('d/m/Y', $filters['due_date_from'])
                ->format('Y-m-d');
            $query->where('due_date', '>=', $dateFrom);
        }

        if (!empty($filters['due_date_to'])) {
            $dateTo = Carbon::createFromFormat('d/m/Y', $filters['due_date_to'])
                ->format('Y-m-d');
            $query->where('due_date', '<=', $dateTo);
        }

        if (!empty($filters['email'])) {
            $query->whereHas('member', function (Builder $query) use ($filters) {
                $query->where('email_hash', Encryption::makeHash($filters['email']));
            });
        }

        if (!empty($filters['contact_no'])) {
            $query->whereHas('member', function (Builder $query) use ($filters) {
                $query->where('contact_no_hash', Encryption::makeHash($filters['contact_no']));
            });
        }

        if (!empty($filters['firstname'])) {
            $query->whereHas('member', function (Builder $query) use ($filters) {
                $query->whereHas('addresses', function (Builder $query) use ($filters) {
                    $query->where('firstname_hash', Encryption::makeHash($filters['firstname']));
                });
            });
        }

        if (!empty($filters['lastname'])) {
            $query->whereHas('member', function (Builder $query) use ($filters) {
                $query->whereHas('addresses', function (Builder $query) use ($filters) {
                    $query->where('lastname_hash', Encryption::makeHash($filters['lastname']));
                });
            });
        }

        return $query;
    }

    /**
     * Generates hash on review
     *
     * @return void
     */
    public function generateHash(): void
    {
        $this->hash = sha1(Str::random(50) . time());
    }

    /**
     * Check that the assessment can be started
     *
     * @param Request $request
     * @return void
     */
    public static function checkAndRedirectFromAssessment(Request $request): void
    {
        if (empty($request->input('review'))) {
            return;
        }
        /** @var self $review */
        $review = self::query()
            ->where('hash', $request->input('review'))
            ->first();
        if (empty($review)) {
            return;
        }
        if ($review->status === self::STATUS_PENDING) {
            return;
        }

        // Create redirect that can be picked up by assessment form
        session()->now(Assessment::ASSESSMENT_INIT_REDIRECT, route('reviews-result', [
            'hash' => $request->input('review'),
        ], false));
    }

    /**
     * Checks submitted assessment - if it's a part of review, we update review status
     * and link assessment with the review
     *
     * @param Assessment $assessment
     * @param Request $request
     * @return void
     */
    public static function checkAndStoreFromAssessment(Assessment $assessment, Request $request): void
    {
        if (empty($request->input('review_hash'))) {
            return;
        }
        /** @var self $review */
        $review = self::query()
            ->where('hash', $request->input('review_hash'))
            ->where('status', self::STATUS_PENDING)
            ->where('medium', self::MEDIUM_ONLINE)
            ->first();
        if (empty($review)) {
            return;
        }
        $assessment->review_id = $review->id;
        $assessment->save();

        $review->status = self::STATUS_SUBMITTED;
        $review->save();

        // Create redirect that can be picked up by assessment form
        session()->now(Assessment::ASSESSMENT_COMPLETE_REDIRECT, route('reviews-result', [
            'hash' => $request->input('review_hash'),
        ], false));
    }

    /**
     * Gets assessment URL for a review
     *
     * @return string
     */
    public function getAssessmentURL(): string
    {
        return route('assessment-form', [
            'review' => $this->hash,
            'form_id' => $this->form_id,
        ]);
    }

    /**
     * Finds all patients that have pending online review in 3 days and send invitations
     *
     * @return void
     */
    public static function sendInvitations(): void
    {
        self::query()
            ->where('medium', self::MEDIUM_ONLINE)
            ->where('status', self::STATUS_PENDING)
            ->where('due_date', Carbon::now()->addDays(3)->format('Y-m-d'))
            ->each(function (self $review) {
                $review->sendInvitation();
            });
    }

    /**
     * Sends review invitation to the patient to complete online assessment
     *
     * @return void
     */
    public function sendInvitation(): void
    {
        if (empty($this->hash)) {
            $this->generateHash();
        }
        $emailContent = template('emails/clinic/reviewInvitation.twig', [
            'member' => $this->member,
            'review_url' => route('reviews-access', [
                'hash' => $this->hash,
            ]),
        ]);
        try {
            email($this->member->email, 'Your ' . config('app.name') . ' assessment', $emailContent);
        } catch (Exception $e) {
            Log::error('Error sending review invitation: ' . $e->getMessage());
        }
    }
}