<?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 Illuminate\Support\Facades\Storage;

use App\Casts\{CollectionOrNull,CompressedStringOrNull};

class File extends Model
{
	use SoftDeletes;

	private const LOGKEY = 'MF-';
	private bool $no_export = FALSE;

	public Collection $set_path;
	public Collection $set_seenby;
	public string $src_file = '';

	protected $casts = [
		'kludges' => CollectionOrNull::class,
		'datetime' => 'datetime:Y-m-d H:i:s',
		'desc' => CompressedStringOrNull::class,
		'ldesc' => CompressedStringOrNull::class,
		'rogue_seenby' => CollectionOrNull::class,
		'rogue_path' => CollectionOrNull::class,
		'size' => 'int',
	];

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

		static::creating(function($model) {
			if (! $model->filearea_id) {
				Log::alert(sprintf('%s:- File has no filearea, not processing creating [%s]',self::LOGKEY,$model->name));
				return;
			}

			Log::info(sprintf('%s:- Storing file [%s] in [%s]',self::LOGKEY,$model->src_file,$model->rel_name));

			$srcfs = Storage::disk(config('fido.local_disk'));
			$tgtfs = Storage::disk(config('fido.file_disk'));

			// Delete anything being replaced
			foreach (self::where('name',$model->name)->where('filearea_id',$model->filearea_id)->get() as $fo) {
				Log::info(sprintf('%s:%% Deleting old file record [%d] for file [%s]',self::LOGKEY,$fo->id,$fo->rel_name));

				$tgtfs->move($fo->rel_name,$fo->relname.'.'.$fo->id);
				$fo->delete();
			}

			// Store file
			if ($tgtfs->put($model->rel_name,$srcfs->get($model->src_file),'public')) {
				$srcfs->delete($model->src_file);

			} else {
				throw new \Exception(sprintf('Unable to move file [%s] to [%s]',$model->src_file,$model->rel_name));
			}
		});

		// @todo if the file is updated with new SEEN-BY's from another route, we'll delete the pending export for systems (if there is one)
		static::created(function($model) {
			if (! $model->filearea_id) {
				Log::alert(sprintf('%s:- File has no filearea, not exporting [%d]',self::LOGKEY,$model->id));
				return;
			}

			$rogue = collect();
			$seenby = collect();
			$path = collect();

			$zone = $model->fftn->zone;

			// Parse PATH
			/**
			 * Path 21:4/106.0 @231005001126 PST+7 Foobar
			 * Path 21:1/100 1696489954 Thu Oct 05 07:12:34 2023 UTC htick/lnx 1.9 2022-07-03
			 */
			foreach ($model->set_path as $line) {
				$matches = [];
				preg_match(sprintf('#^(%s)\ ((@?)(\d+)(\ ([A-Z]{3}([\+\-][0-9]+)))?)\ ?(.*)$#',Address::ftn_regex),$line,$matches);

				if ($x=Arr::get($matches,1)) {
					$ftn = Address::parseFTN($x);

					// If domain should be flattened, look for node regardless of zone (within the list of zones for the domain)
					$ao = ($zone->domain->flatten)
						? Address::findZone($zone->domain,$ftn['n'],$ftn['f'],0)
						: Address::findFTN($x);

					if (! $ao)
						$ao = Address::createFTN($x,System::createUnknownSystem());

					$datetime = ($matches[8] === '@')
						? Carbon::createFromFormat('ymdHis',$matches[9],$matches[12])
						: Carbon::createFromTimestamp($matches[7]);

					$path->push(['address'=>$ao,'datetime'=>$datetime,'extra'=>$matches[13]]);
				}
			}

			// Save the Path
			$ppoid = NULL;
			foreach ($path as $item) {
				$po = DB::select('INSERT INTO file_path (file_id,address_id,parent_id,datetime,extra) VALUES (?,?,?,?,?) RETURNING id',[
					$model->id,
					$item['address']->id,
					$ppoid,
					$item['datetime'],
					$item['extra'],
				]);

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

			// Make sure all the path is in the seenby
			// Add zone to seenby
			$model->set_seenby = $model->set_seenby->merge($path->pluck('address.ftn3d'))->unique()->filter();

			foreach ($model->set_seenby as $sb) {
				$ftn = Address::parseFTN($sb);

				$ao = ($zone->domain->flatten)
					? Address::findZone($zone->domain,$ftn['n'],$ftn['f'],0)
					: Address::findFTN($sb);

				if ($ao)
					$seenby->push($ao->id);
				else
					$rogue->push($sb);
			}

			$model->rogue_seenby = $rogue;

			$model->seenby()->sync($seenby);
			$model->save();

			// See if we need to export this file.
			if ($model->filearea->sec_read) {
				$exportto = $model
					->filearea
					->addresses
					->filter(function($item) use ($model) { return $model->filearea->can_read($item->security); })
					->pluck('id')
					->diff($seenby);

				if ($exportto->count()) {
					if ($model->no_export) {
						Log::alert(sprintf('%s:- NOT processing exporting of message by configuration [%s] to [%s]',self::LOGKEY,$model->id,$exportto->join(',')));
						return;
					}

					Log::info(sprintf('%s:- Exporting file [%s] to [%s]',self::LOGKEY,$model->id,$exportto->join(',')));

					// Save the seenby for the exported systems
					$model->seenby()->syncWithPivotValues($exportto,['export_at'=>Carbon::now()],FALSE);
				}
			}
		});
	}

	/* RELATIONS */

	public function filearea()
	{
		return $this->belongsTo(Filearea::class);
	}

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

	public function seenby()
	{
		return $this->belongsToMany(Address::class,'file_seenby')
			->ftnOrder();
	}

	public function path()
	{
		return $this->belongsToMany(Address::class,'file_path')
			->withPivot(['id','parent_id','datetime','extra']);
	}

	/* ATTRIBUTES */

	public function getOriginAttribute(): Address
	{
		return $this->path->where('pivot.parent_id','=',NULL)->pop();
	}

	/**
	 * Return the relative path to Storage::disk() in the store
	 *
	 * @return string
	 */
	public function getRelNameAttribute(): string
	{
		return sprintf('%04X/%s',$this->filearea_id,$this->name);
	}

	/* METHODS */

	public function jsonSerialize(): array
	{
		return $this->encode();
	}
}