<?php

namespace App\Models;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

use App\Casts\{CollectionOrNull,CompressedString};
use App\Interfaces\Packet;
use App\Pivots\ViaPivot;
use App\Traits\{MessageAttributes,MsgID};

final class Netmail extends Model implements Packet
{
	use SoftDeletes,MsgID,MessageAttributes;

	private const LOGKEY = 'MN-';
	private const PATH_REGEX = '/^([0-9]+:[0-9]+\/[0-9]+(\..*)?)\s+@([0-9.a-zA-Z]+)\s+(.*)$/';

	/**
	 * Kludges that we absorb in this model
	 */
	private const kludges = [
		'MSGID:'=>'msgid',
		'REPLY:'=>'replyid',
		'Via' => 'set_path',
	];

	protected $casts = [
		'datetime' => 'datetime:Y-m-d H:i:s',
		'kludges' => CollectionOrNull::class,
		'msg' => CompressedString::class,
		'msg_src' => CompressedString::class,
		'sent_at' => 'datetime:Y-m-d H:i:s',
	];

	public function __set($key,$value)
	{
		switch ($key) {
			case 'kludges':
				if (! count($value))
					return;

				if (array_key_exists($value[0],self::kludges)) {
					$this->{self::kludges[$value[0]]} = $value[1];

				} else {
					$this->kludges->put($value[0],$value[1]);
				}

				break;

			case 'set_fftn':
			case 'set_tftn':
			// Values that we pass to boot() to record how we got this netmail
			case 'set_pkt':
			case 'set_recvtime':
			case 'set_sender':
			// @todo We'll normalise these values when saving the netmail
			case 'set_tagline':
			case 'set_tearline':
			case 'set_origin':
				$this->set->put($key,$value);
				break;

			// The path the netmail went through to get here
			case 'set_path':
				if (! $this->set->has($key))
					$this->set->put($key,collect());

				$this->set->get($key)->push($value);
				break;

			default:
				parent::__set($key,$value);
		}
	}

	public static function boot()
	{
		parent::boot();

		static::creating(function($model) {
			if (isset($model->errors) && $model->errors->count())
				throw new \Exception('Cannot save, validation errors exist');
		});

		static::created(function($model) {
			$nodes = collect();

			// Parse PATH
			// <FTN Address> @YYYYMMDD.HHMMSS[.Precise][.Time Zone] <Program Name> <Version> [Serial Number]
			if ($model->set->has('set_path')) {
				foreach ($model->set->get('set_path') as $line) {
					$m = [];

					if (preg_match(self::PATH_REGEX,$line,$m)) {
						// Address
						// @todo Do we need to add a domain here, since the path line may not include one
						$ao = Address::findFTN($m[1]);

						// Time
						$t = [];
						$datetime = '';

						if (! preg_match('/^([0-9]+\.[0-9]+)(\.?(.*))?$/',$m[3],$t))
							Log::alert(sprintf('%s:! Unable to determine time from [%s]',self::LOGKEY,$m[3]));
						else
							$datetime = Carbon::createFromFormat('Ymd.His',$t[1],$t[3] ?? '');

						if (! $ao) {
							Log::alert(sprintf('%s:! Undefined Node [%s] in netmail path.',self::LOGKEY,$m[1]));
							//$rogue->push(['node'=>$m[1],'datetime'=>$datetime,'program'=>$m[4]]);

						} else {
							$nodes->push(['node'=>$ao,'datetime'=>$datetime,'program'=>$m[4]]);
						}
					}
				}

			// If there are no details (Mystic), we'll create a blank
			} elseif ($model->set->has('set_sender')) {
				$nodes->push(['node'=>$model->set->get('set_sender'),'datetime'=>Carbon::now(),'program'=>'Unknown']);
			}

			// Save the Path
			$ppoid = NULL;

			foreach ($nodes as $path) {
				$po = DB::select('INSERT INTO netmail_path (netmail_id,address_id,parent_id,datetime,program) VALUES (?,?,?,?,?) RETURNING id',[
					$model->id,
					$path['node']->id,
					$ppoid,
					(string)$path['datetime'],
					$path['program'],
				]);

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

			// Our last node in the path is our sender
			if ($nodes->count() && $model->set->has('set_pkt') && $model->set->has('set_sender') && $model->set->has('set_recvtime')) {
				DB::update('UPDATE netmail_path set recv_pkt=?,recv_at=?,recv_id=? where address_id=? and netmail_id=?',[
					$model->set->get('set_pkt'),
					$model->set->get('set_recvtime'),
					$model->set->get('set_sender')->id,
					Arr::get($nodes->last(),'node')->id,
					$model->id,
				]);
			}

			// Save our origin, tearline & tagline
			if ($model->set->has('set_tagline'))
				$model->tagline = $model->set->get('set_tagline');
			if ($model->set->has('set_tearline'))
				$model->tearline = $model->set->get('set_tearline');
			if ($model->set->has('set_origin'))
				$model->origin = $model->set->get('set_origin');

			$model->save();
		});
	}

	/* RELATIONS */

	public function fftn()
	{
		return $this
			->belongsTo(Address::class)
			->withTrashed();
	}

	public function path()
	{
		return $this->belongsToMany(Address::class,'netmail_path')
			->withPivot(['id','parent_id','datetime','program','recv_pkt','recv_id'])
			->orderBy('netmail_path.id')
			->using(ViaPivot::class);
	}

	public function tftn()
	{
		return $this
			->belongsTo(Address::class)
			->withTrashed();
	}

	/* ATTRIBUTES */

	/**
	 * Enable rendering the path even if the model hasnt been saved
	 *
	 * @return Collection
	 */
	public function getPathAttribute(): Collection
	{
		return ((! $this->exists) && $this->set->has('set_path'))
			? $this->set->get('set_path')->map(function($item) {
				$m = [];
				preg_match(self::PATH_REGEX,$item,$m);
				return $m[1];
			})
			: $this->getRelationValue('path');
	}

	/* METHODS */

	/**
	 * Render the via line
	 *
	 * @param Address $ao
	 * @return string
	 * @throws \Exception
	 */
	public function via(Address $ao): string
	{
		if (! $ao->pivot)
			throw new \Exception('Cannot render the via line without an address record without a path pivot');

		return sprintf('%s @%s.UTC %s',
			$ao->ftn3d,
			$ao->pivot->datetime->format('Ymd.His'),
			$ao->pivot->program);
	}
}