<?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\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;

use App\Classes\FTN\Message;
use App\Models\{Address,Echoarea,Echomail,Netmail,Setup,User};
use App\Notifications\Netmails\{EchoareaNotExist,EchoareaNotSubscribed,EchoareaNoWrite,NetmailForward,Reject};
use App\Traits\ParseAddresses;

class MessageProcess implements ShouldQueue
{
	private const LOGKEY = 'JMP';

	use Dispatchable,InteractsWithQueue,Queueable,SerializesModels,ParseAddresses;

	private Address $sender;
	private Message $msg;
	private Address $pktsrc;
	private Carbon $recvtime;
	private bool $skipbot;
	private string $packet;

	public function __construct(Message $msg,string $packet,Address $sender,Address $pktsrc,Carbon $recvtime,bool $skipbot=FALSE)
	{
		$this->msg = $msg;
		$this->packet = $packet;
		$this->sender = $sender;
		$this->pktsrc = $pktsrc;
		$this->recvtime = $recvtime;
		$this->skipbot = $skipbot;
	}

	public function __get($key): mixed
	{
		switch ($key) {
			case 'subject':
				return sprintf('%s-%s-%s',$this->packet,$this->sender->ftn,$this->msg->msgid);

			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()
	{
		// Load our details
		$ftns = Setup::findOrFail(config('app.id'))->system->addresses;

		// If we are a netmail
		if ($this->msg->isNetmail()) {
			Log::info(sprintf('%s:- Processing Netmail [%s] to (%s) [%s] from (%s) [%s].',
				self::LOGKEY,
				$this->msg->msgid,
				$this->msg->user_to,$this->msg->tftn,
				$this->msg->user_from,$this->msg->fftn,
			));

			// @todo Enable checks to reject old messages

			// Check for duplicate messages

			// FTS-0009.001
			if ($this->msg->msgid) {
				$o = Netmail::where('msgid',$this->msg->msgid)
					->where('fftn_id',($x=$this->msg->fboss_o) ? $x->id : NULL)
					->where('datetime','>',Carbon::now()->subYears(3))
					->single();

				Log::debug(sprintf('%s:- Checking for duplicate from host id [%d].',self::LOGKEY,($x=$this->msg->fboss_o) ? $x->id : NULL));

				if ($o) {
					Log::alert(sprintf('%s:! Duplicate netmail [%s] in [%s] from (%s) [%s] to (%s) - ignorning.',
						self::LOGKEY,
						$this->msg->msgid,
						$this->msg->echoarea,
						$this->msg->user_from,$this->msg->fftn,
						$this->msg->user_to,
					));

					if (! $o->msg_crc)
						$o->msg_crc = md5($this->msg->message);

					$o->save();

					return;
				}
			}

			// @todo Enable checks to see if this is a file request or file send

			$o = new Netmail;
			$o->to = $this->msg->user_to;
			$o->from = $this->msg->user_from;

			$o->fftn_id = ($x=$this->msg->fftn_o) ? $x->id : NULL;
			$o->tftn_id = ($x=$this->msg->tftn_o) ? $x->id : NULL;

			$o->datetime = $this->msg->date;
			$o->tzoffset = $this->msg->date->utcOffset();

			$o->flags = $this->msg->flags;
			$o->cost = $this->msg->cost;
			$o->msgid = $this->msg->msgid;

			$o->subject = $this->msg->subject;
			$o->msg = $this->msg->message;

			foreach ($this->msg->via as $v)
				$o->msg .= sprintf("\01Via %s\r",$v);

			$o->msg_src = $this->msg->message_src;
			$o->msg_crc = md5($this->msg->message);

			$o->set_pkt = $this->packet;
			$o->set_sender = $this->sender;
			$o->set_path = $this->msg->via;
			$o->set_recvtime = $this->recvtime;
			// Strip any local/transit flags
			$o->flags &= ~(Message::FLAG_LOCAL|Message::FLAG_INTRANSIT);

			// Determine if the message is to this system, or in transit
			if ($ftns->search(function($item) { return $this->msg->tftn === $item->ftn; }) !== FALSE) {
				// @todo Check if it is a duplicate message
				// @todo Check if the message is from a system we know about

				$processed = FALSE;

				// If the message is to a bot, we'll process it
				if (! $this->skipbot)
					foreach (config('process.robots') as $class) {
						if ($processed=$class::handle($this->msg)) {
							$o->flags |= Message::FLAG_RECD;
							$o->save();

							Log::info(sprintf('%s:= Netmail [%s] from (%s:%s) - was processed by us internally [%d]',
								self::LOGKEY,
								$this->msg->msgid,
								$this->msg->user_from,
								$this->msg->fftn,
								$o->id,
							));
							break;
						}
					}

				if (! $processed) {
					// Check if the netmail is to a user, with netmail forwarding enabled
					$uo = User::active()
						->where(function($query) {
							return $query->whereRaw(sprintf("LOWER(name)='%s'",strtolower($this->msg->user_to)))
								->orWhereRaw(sprintf("LOWER(alias)='%s'",strtolower($this->msg->user_to)));
						})
						->whereNotNull('system_id')
						->single();

					if ($uo && ($ao=$uo->system->match($this->msg->tftn_o->zone)?->pop())) {
						$note = "+--[ FORWARDED MESSAGE ]----------------------------------+\r";
						$note .= "+ This message has been forwarded to you, it was originally sent to you\r";
						$note .= sprintf("+ at [%s]\r",$this->msg->tftn_o->ftn);
						$note .= "+---------------------------------------------------------+\r\r";
						$o->msg = $note.$this->msg->message;
						$o->tftn_id = $ao->id;
						$o->flags |= Message::FLAG_INTRANSIT;
						$o->save();
						$processed = TRUE;

						// Dont send an advisement to an areabot
						if (! in_array(strtolower($this->msg->user_from),config('fido.areabots')))
							Notification::route('netmail',$this->msg->fftn_o)->notify(new NetmailForward($this->msg,$ao));

					// We'll ignore messages from *fix users
					} elseif (in_array(strtolower($this->msg->user_from),config('fido.areabots'))) {
						$o->flags |= Message::FLAG_RECD;
						$o->save();

						Log::alert(sprintf('%s:! Ignoring Netmail [%s] to the Hub from (%s:%s) - its from a bot [%d]',
							self::LOGKEY,
							$this->msg->msgid,
							$this->msg->user_from,
							$this->msg->fftn,
							$o->id,
						));

						$processed = TRUE;
					}
				}

				// If not processed, no users here!
				if (! $processed) {
					Log::alert(sprintf('%s:! Netmail to the Hub from (%s) [%s] but no users here.',self::LOGKEY,$this->msg->user_from,$this->msg->fftn));

					Notification::route('netmail',$this->msg->fftn_o)->notify(new Reject($this->msg));
				}

			// If in transit, store for collection
			} else {
				// @todo Check if the message is to a system we know about
				// @todo In transit loop checking
				// @todo In transit TRACE response

				$o->flags |= Message::FLAG_INTRANSIT;
				$o->save();

				Log::info(sprintf('%s:= Netmail [%s] in transit to (%s:%s) from (%s:%s) [%d].',
					self::LOGKEY,
					$this->msg->msgid,
					$this->msg->user_to,$this->msg->tftn,
					$this->msg->user_from,$this->msg->fftn,
					$o->id,
				));
			}

		// Else we are echomail
		} else {
			Log::debug(sprintf('%s:- Looking for echomail area [%s] for mail from [%s]',self::LOGKEY,$this->msg->echoarea,$this->msg->fboss));

			if (! $this->msg->fboss_o) {
				Log::error(sprintf('%s:! Cannot process message for echomail area [%s] for mail from [%s] with msgid [%s] - no boss object?',self::LOGKEY,$this->msg->echoarea,$this->msg->fboss,$this->msg->msgid));
				return;
			}

			$ea = Echoarea::where('name',strtoupper($this->msg->echoarea))
				->where('domain_id',$this->msg->fboss_o->zone->domain_id)
				->single();

			if (! $ea) {
				Log::alert(sprintf('%s:! Echoarea [%s] doesnt exist for zone [%d-%d]',self::LOGKEY,$this->msg->echoarea,$this->msg->fboss_o->zone->domain_id,$this->msg->fboss_o->zone->zone_id));

				Notification::route('netmail',$this->pktsrc)->notify(new EchoareaNotExist($this->msg));
				return;
			}

			Log::debug(sprintf('%s:- Processing echomail [%s] in [%s].',self::LOGKEY,$this->msg->msgid,$this->msg->echoarea));

			if (! $this->pktsrc->zone->domain->zones->pluck('zone_id')->contains($this->msg->fboss_o->zone->zone_id)) {
				Log::alert(sprintf('%s:! The message [%s] is from a different zone [%d] than the packet sender [%d] - not importing',
					self::LOGKEY,
					$this->msg->msgid,
					$this->msg->fboss_o->zone->zone_id,
					$this->pktsrc->zone->zone_id));

				return;
			}

			// Check for duplicate messages
			// FTS-0009.001
			if ($this->msg->msgid) {
				$o = Echomail::where('msgid',$this->msg->msgid)
					->where('fftn_id',($x=$this->msg->fboss_o) ? $x->id : NULL)
					->where('datetime','>=',$this->msg->date->subYears(3))
					->where('datetime','<=',$this->msg->date)
					->single();

				Log::debug(sprintf('%s:- Checking for duplicate from host id [%d].',self::LOGKEY,($x=$this->msg->fboss_o) ? $x->id : NULL));

				if ($o) {
					Log::alert(sprintf('%s:! Duplicate echomail [%s] in [%s] from (%s) [%s] to (%s) - updating seenby.',
						self::LOGKEY,
						$this->msg->msgid,
						$this->msg->echoarea,
						$this->msg->user_from,$this->msg->fftn,
						$this->msg->user_to,
					));

					if (! $o->msg_crc)
						$o->msg_crc = md5($this->msg->message);

					$o->save();

					// @todo This duplicate message may have gone via a different path, be nice to record it.
					// If we didnt get the path on the original message, we'll override it
					if (! $o->path->count()) {
						$dummy = collect();
						$path = $this->parseAddresses('path',$this->msg->path,$this->pktsrc->zone,$dummy);

						/*
						// If our sender is not in the path, add it
						if (! $path->contains($this->sender->id)) {
							Log::alert(sprintf('%s:? Echomail adding sender to PATH [%s] for [%d].',self::LOGKEY,$x->ftn,$o->id));
							$path->push($this->sender->id);
						}
						*/

						$ppoid = NULL;
						foreach ($path as $aoid) {
							$po = DB::select('INSERT INTO echomail_path (echomail_id,address_id,parent_id) VALUES (?,?,?) RETURNING id',[
								$o->id,
								$aoid,
								$ppoid,
							]);

							$ppoid = $po[0]->id;
						}
					}

					// @todo if we have an export for any of the seenby addresses, remove it
					$seenby = $this->parseAddresses('seenby',$this->msg->seenby,$this->pktsrc->zone,$o->rogue_seenby);
					$x = $o->seenby()->syncWithoutDetaching($seenby);

					// In case our rogue_seenby changed
					if ($o->getDirty())
						$o->save();

					return;
				}
			}

			// Find another message with the same msg_crc
			if ($this->msg->message) {
				$o = Echomail::where('msg_crc',$xx=md5($this->msg->message))
					->where('fftn_id',($x=$this->msg->fboss_o) ? $x->id : NULL)
					->where('datetime','>',Carbon::now()->subWeek())
					->get();

				if ($o->count())
					Log::alert(sprintf('%s:! Duplicate message CRC [%s] in [%s].',
						self::LOGKEY,
						$xx,
						$o->pluck('id')->join('|')
					));
			}

			// Can the system send messages to this area?
			if (! $ea->sec_write || ($this->pktsrc->security < $ea->sec_write)) {
				Log::alert(sprintf('%s:! FTN [%s] is not allowed to post [%s] to [%s].',self::LOGKEY,$this->pktsrc->ftn,$this->msg->msgid,$ea->name));
				if (! $this->msg->rescanned->count())
					Notification::route('netmail',$this->pktsrc)->notify(new EchoareaNoWrite($this->msg));

				return;
			}

			// If the node is not subscribed
			if ($this->pktsrc->echoareas->search(function($item) use ($ea) { return $item->id === $ea->id; }) === FALSE) {
				Log::alert(sprintf('%s:! FTN [%s] is not subscribed to [%s] for [%s].',self::LOGKEY,$this->pktsrc->ftn,$ea->name,$this->msg->msgid));

				if (! $this->msg->rescanned->count())
					Notification::route('netmail',$this->pktsrc)->notify(new EchoareaNotSubscribed($this->msg));
			}

			// We know about this area, store it
			$o = new Echomail;
			$o->init();

			$o->to = $this->msg->user_to;
			$o->from = $this->msg->user_from;
			$o->subject = $this->msg->subject;
			$o->datetime = $this->msg->date;
			$o->tzoffset = $this->msg->date->utcOffset();

			if ($x=$this->msg->fboss_o) {
				$o->fftn_id = $x->id;

			} else {
				$o->fftn_id = NULL; // @todo This should be the node that originated the message - but since that node is not in the DB it would be null
			}

			$o->echoarea_id = $ea->id;
			$o->msgid = $this->msg->msgid;
			$o->replyid = $this->msg->replyid;

			$o->msg = $this->msg->message_src."\r";
			$o->msg_src = $this->msg->message_src;
			$o->msg_crc = md5($this->msg->message);

			$o->set_path = $this->msg->path;
			$o->set_seenby = $this->msg->seenby;
			$o->set_recvtime = $this->recvtime;
			$o->set_sender = $this->pktsrc->id;
			// Record receiving packet and sender
			$o->set_pkt = $this->packet;

			$o->save();

			Log::info(sprintf('%s:= Echomail [%s] in [%s] from (%s) [%s] to (%s) - [%s].',
				self::LOGKEY,
				$this->msg->msgid,
				$this->msg->echoarea,
				$this->msg->user_from,$this->msg->fftn,
				$this->msg->user_to,
				$o->id,
			));

			// If the message is to a bot, but not rescanned, or purposely skipbot set, we'll process it
			if ((! $this->skipbot) && (! $this->msg->rescanned->count()))
				foreach (config('process.echomail') as $class) {
					if ($class::handle($this->msg)) {
						break;
					}
				}
		}
	}
}