<?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\{Echomail,Netmail,System};
use App\Notifications\Netmails\PacketPasswordInvalid;

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

	use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

	private string $filename;
	private System $so;
	private Carbon $rcvd_time;
	private bool $interactive;
	private bool $nobot;

	public function __construct(string $filename,System $so,bool $interactive=TRUE,Carbon $rcvd_time=NULL,bool $nobot=FALSE)
	{
		$this->filename = $filename;
		$this->so = $so->withoutRelations();
		$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;
			$bad_archive = FALSE;

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

					// 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]',self::LOGKEY,$this->filename,$pkt->fftn_t));

						// @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;
					}

					// If we dont have the tftn in the DB, then packet cannot be to us
					if (! $pkt->tftn) {
						Log::error(sprintf('%s:! Packet [%s] is from a system [%s] we dont know about?',self::LOGKEY,$this->filename,$pkt->tftn_t));

						// @todo Notification::route('netmail',$pkt->fftn)->notify(new UnexpectedPacketToUs($this->filename));
						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 ($pkt->fftn->pass_packet !== 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 [%s] dispatching message [%s] (%d:%s-%s).',self::LOGKEY,get_class($e),$msg->msgid,$e->getLine(),$e->getFile(),$e->getMessage()));
						}
					}

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

				} catch (\Exception $e) {
					Log::error(sprintf('%s:! Got an exception [%s] processing packet',self::LOGKEY,$e->getMessage()));

					$bad_archive = TRUE;
				}
			}

			if ((! $processed) || $bad_archive) {
				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()]);
		}
	}
}