<?php

namespace Ignite\Core\Console\HealthChecks;

use Exception;
use Ignite\Core\Emails\GenericMailable;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;

class HorizonCheck extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'ignite:health-check:horizon';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Checks the health of Horizon.';

    const STATUS_SUCCESS = 'success';

    const STATUS_PAUSED = 'paused';

    const STATUS_FAILED = 'failed';

    /**
     * Execute the console command.
     *
     * @return string
     */
    public function handle(): string
    {
        try {
            $horizon = app(\Laravel\Horizon\Contracts\MasterSupervisorRepository::class);
        } catch (Exception $e) {
            return $this->sendMail(self::STATUS_FAILED, 'Horizon does not seem to be installed correctly. ');
        }

        $masterSupervisors = $horizon->all();
        if (0 === count($masterSupervisors)) {
            return $this->sendMail(self::STATUS_FAILED, 'Horizon is not running. ');
        }

        $masterSupervisor = $masterSupervisors[0];
        if (self::STATUS_PAUSED === $masterSupervisor->status) {
            return $this->sendMail(self::STATUS_FAILED, 'Horizon is running, but the status is paused. ', self::STATUS_PAUSED);
        }

        return $this->sendMail(self::STATUS_SUCCESS, 'Horizon is running properly.');
    }

    /**
     * Sends notification email and attempts to restart Horizon, if necessary.
     *
     * @param  string  $result
     * @param  string  $message
     * @param  string|null  $status
     *
     * @return string
     */
    protected function sendMail(string $result, string $message, string $status = null): string
    {
        config(['queue.default' => 'sync']);

        $recovered = false;
        $sendEmail = false;
        $date = Carbon::now();
        if (self::STATUS_FAILED === $result) {
            $sendEmail = true;
            if (!Storage::exists('logs/health-check/horizon.txt')) {
                Storage::put('logs/health-check/horizon.txt', $date);
            }
        }

        if (Storage::exists('logs/health-check/horizon.txt')) {
            $date = Storage::get('logs/health-check/horizon.txt');
        }

        $to = Carbon::now();
        $from = Carbon::createFromFormat('Y-m-d H:i:s', $date);

        $diffInMinutes = $to->diffInMinutes($from);
        if (self::STATUS_FAILED === $result) {
            $message .= ((self::STATUS_PAUSED == $status) ? ucfirst(self::STATUS_PAUSED).' ' : 'Down ').'since: '.$date;
        }

        if (self::STATUS_SUCCESS === $result) {
            if (0 < $diffInMinutes) {
                $sendEmail = true;
                $recovered = true;
                $message = 'Horizon has recovered at '.date('Y-m-d H:i:s');
                $this->info($message);

                if (Storage::exists('logs/health-check/horizon.txt')) {
                    Storage::delete('logs/health-check/horizon.txt');
                }
            }
        }

        if ($sendEmail) {
            /* Send Time-based/Status Emails
             * -------------------------------------------------- //
             * Send emails based on the following:
             *
             * The difference in minutes is 0, 5, 10, 15, 30, 45
             * OR
             * Every 60 minutes -- modulus operator
             * OR
             * Horizon has recovered
             *
             * We don't want to be bombarded by emails,
             * especially if horizon has gone down over the
             * weekend. The first hour is a little more crucial,
             * so we send more often, but after that it will
             * only email every hour.
             * -------------------------------------------------- */
            if (in_array($diffInMinutes, [5, 10, 15, 30, 45]) || 0 === ($diffInMinutes % 60) || $recovered) {
                $mailable = (new GenericMailable)
                    ->to(config('mail.from.address'), config('mail.from.name'))
                    ->subject($message)
                    ->markdown('emails.general')
                    ->with('message', $message);
                Mail::queue($mailable);
            }
        }

        if ((self::STATUS_FAILED === $result && self::STATUS_PAUSED !== $status) && (0 !== $diffInMinutes && 0 === ($diffInMinutes % 5))) {
            $this->attemptHorizonRestart();
        }

        if (self::STATUS_FAILED === $result && self::STATUS_PAUSED !== $status) {
            $this->error($message.
                ' | Result: '.$result.
                ' | Diff in Minutes: '.$diffInMinutes.
                ' | Modulus Minutes: '.($diffInMinutes % 5));
        }

        return $message;
    }

    /**
     * Attempts to restart Horizon.
     *
     * @return void
     */
    protected function attemptHorizonRestart(): void
    {
        $this->comment('Attempting to terminate horizon');
        Artisan::call('horizon:terminate');

        $this->comment('Attempting to purge horizon');
        Artisan::call('horizon:purge');
    }
}
