<?php

namespace App\Jobs;

use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Storage;
use League\Flysystem\UnableToMoveFile;

use App\Classes\File;
use App\Classes\FTN\Packet;
use App\Exceptions\InvalidPacketException;
use App\Models\{Domain,Echomail,Netmail};
use App\Notifications\Netmails\PacketPasswordInvalid;

class PacketProcess implements ShouldQueue
{
	private const LOGKEY = 'JPP';

	use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

	private string $filename;
	private Domain $do;
	private Carbon $rcvd_time;
	private bool $interactive;
	private bool $nobot;

	public function __construct(string $filename,Domain $do,bool $interactive=TRUE,Carbon $rcvd_time=NULL,bool $nobot=FALSE)
	{
		$this->filename = $filename;
		$this->do = $do;
		$this->interactive = $interactive;
		$this->rcvd_time = $rcvd_time ?: Carbon::now();
		$this->nobot = $nobot;
	}

	public function __get($key): mixed
	{
		switch ($key) {
			case 'jobname':
				return $this->filename;

			default:
				return NULL;
		}
	}

	/**
	 * When calling MessageProcess - we assume that the packet is from a valid source, and
	 * the destination (netmail/echomail) is also valid
	 */
	public function handle()
	{
		Log::info(sprintf('%s:- Processing mail [%s]',self::LOGKEY,$this->filename));

		$fs = Storage::disk(config('fido.local_disk'));

		// @todo Catch files that we cannot process, eg: ARJ bundles.
		try {
			$f = new File($fs->path($this->filename));

			$processed = FALSE;

			foreach ($f as $packet) {
				$pkt = Packet::process($packet,Arr::get(stream_get_meta_data($packet),'uri'),$f->itemSize(),$this->do);

				// Check that the packet is from a system that is defined in the DB
				if (! $pkt->fftn) {
					Log::error(sprintf('%s:! Packet [%s] is not from a system we know about? [%s]',self::LOGKEY,$this->filename,$pkt->fftn_t));

					break;
				}

				if (! our_nodes($pkt->fftn->zone->domain)->contains($pkt->fftn)) {
					Log::error(sprintf('%s:! Packet [%s] is from a system that is not configured with us? [%s] for [%s]',self::LOGKEY,$this->filename,$pkt->fftn_t,$this->do->name));

					// @todo Notification::route('netmail',$pkt->fftn)->notify(new UnexpectedPacketFromYou($this->filename));
					// @todo Parse the packet for netmails and process them. We'll only accept netmails to us, and ignore all others
					break;
				}

				// Check the packet is to our address, if not we'll reject it.
				if (! our_address($pkt->tftn->zone->domain)->contains($pkt->tftn)) {
					Log::error(sprintf('%s:! Packet [%s] is not to our address? [%s]',self::LOGKEY,$this->filename,$pkt->tftn->ftn));

					// @todo Notification::route('netmail',$pkt->fftn)->notify(new UnexpectedPacketToUs($this->filename));
					break;
				}

				// Check the packet password
				if (strtoupper($pkt->fftn->session('pktpass')) !== strtoupper($pkt->password)) {
					Log::error(sprintf('%s:! Packet from [%s] with password [%s] is invalid.',self::LOGKEY,$pkt->fftn->ftn,$pkt->password));

					Notification::route('netmail',$pkt->fftn)->notify(new PacketPasswordInvalid($pkt->password,$f->pktName()));
					break;
				}

				Log::info(sprintf('%s:- Packet has [%d] messages',self::LOGKEY,$pkt->count()));

				// Queue messages if there are too many in the packet.
				if ($queue = ($pkt->count() > config('fido.queue_msgs')))
					Log::info(sprintf('%s:- Messages will be sent to the queue for processing',self::LOGKEY));

				$count = 0;
				foreach ($pkt as $msg) {
					if ($msg instanceof Netmail)
						Log::info(sprintf('%s:- Netmail from [%s] to [%s]',self::LOGKEY,$msg->fftn->ftn,$msg->tftn->ftn));
					elseif ($msg instanceof Echomail)
						Log::info(sprintf('%s:- Echomail from [%s]',self::LOGKEY,$msg->fftn->ftn));

					if ($msg->errors->count()) {
						Log::error(sprintf('%s:! Message [%s] has [%d] errors, unable to process',self::LOGKEY,$msg->msgid,$msg->errors->count()));

						continue;
					}

					$msg->set_sender = $pkt->fftn->withoutRelations();
					// Record receiving packet and sender
					$msg->set_pkt = $f->pktName();
					$msg->set_recvtime = $this->rcvd_time;

					if ($queue || (! $this->interactive))
						Log::info(sprintf('%s:! Message [%s] will be sent to the queue to process',self::LOGKEY,$msg->msgid));

					try {
						// Dispatch job.
						if ($queue || (! $this->interactive))
							MessageProcess::dispatch($msg->withoutRelations(),$this->nobot);
						else
							MessageProcess::dispatchSync($msg->withoutRelations(),$this->nobot);

						$count++;

					} catch (\Exception $e) {
						Log::error(sprintf('%s:! Got error dispatching message [%s] (%d:%s-%s).',self::LOGKEY,$msg->msgid,$e->getLine(),$e->getFile(),$e->getMessage()));
					}
				}

				if ($count === $pkt->count())
					$processed = TRUE;
			}

			if (! $processed) {
				Log::alert(sprintf('%s:- Not deleting packet [%s], it doesnt seem to be processed?',self::LOGKEY,$this->filename));

			} else {
				// If we want to keep the packet, we could do that logic here
				if (config('fido.packet_keep')) {
					$dir = sprintf('%s/%s/%s/%s',config('fido.dir'),($x=Carbon::now())->format('Y'),$x->format('m'),$x->format('d'));
					Log::debug(sprintf('%s:- Moving processed packet [%s] to [%s]',self::LOGKEY,$this->filename,$dir));

					try {
						if ($fs->makeDirectory($dir)) {
							$fs->move($this->filename,$x=sprintf('%s/%s',$dir,$f->itemName()));
							Log::info(sprintf('%s:- Moved processed packet [%s] to [%s]',self::LOGKEY,$this->filename,$x));

						} else
							Log::error(sprintf('%s:! Unable to create dir [%s]',self::LOGKEY,$dir));

					} catch (UnableToMoveFile $e) {
						Log::error(sprintf('%s:! Unable to move packet [%s] to [%s] (%s)',self::LOGKEY,$this->filename,$dir,$e->getMessage()));

					} catch (\Exception $e) {
						Log::error(sprintf('%s:! Failed moving packet [%s] to [%s] (%s)',self::LOGKEY,$this->filename,$dir,$e->getMessage()));
					}

				} else {
					Log::debug(sprintf('%s:- Deleting processed packet [%s]',self::LOGKEY,$this->filename));

					$fs->delete($this->filename);
				}
			}

		} catch (InvalidPacketException $e) {
			Log::error(sprintf('%s:- Not deleting packet [%s], as it generated an InvalidPacketException',self::LOGKEY,$this->filename),['e'=>$e->getMessage()]);

		} catch (\Exception $e) {
			Log::error(sprintf('%s:- Not deleting packet [%s], as it generated an uncaught exception',self::LOGKEY,$this->filename),['e'=>$e->getMessage(),'l'=>$e->getLine(),'f'=>$e->getFile()]);
		}
	}
}