<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Mail\AsnApproval;
use App\Models\Organisation;
use App\Helpers\Utility\WhoIs;
use App\Http\Controllers\Provisioning\ComputeController;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;

use Illuminate\Support\Str;
use App\Traits\Owned;
use Exception;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Throwable;

class Asns extends Model
{
    use SoftDeletes;
    use Owned;

    protected $fillable = [
        'asn',
        'approved',
        'organisation_id'
    ];

    public static function make($_asn, $_email, $_routelist, $_organisation_id, $_admin_bypass = false) {
        if(Organisation::find($_organisation_id)) {

            $asn_exists = Asns::where('asn', '=', ltrim($_asn, 'ASN'))->first() ?? false;

            if($_admin_bypass) {
                if($asn_exists) {

                    $should_clean = $asn_exists->organisation_id != 1 && $asn_exists->organisation_id != $_organisation_id;

                    Asns::where('asn', '=', ltrim($_asn, 'ASN'))->update([
                        'approved' => 1,
                        'organisation_id' => $_organisation_id
                    ]);

                    if($should_clean) {
                        $asn_exists->refresh();
                        $asn_exists->clean();
                    }
                }
                else {
                    Asns::create([
                        'asn' => ltrim($_asn, 'ASN'),
                        'approved' => 1,
                        'organisation_id' => $_organisation_id
                    ]);    
                }

                return true;
            }

            $email_list = WhoIs::asn_emails('AS' . ltrim($_asn, 'ASN')) ?? [];

            Log::info("Attempting to add ASN $_asn to Organisation #$_organisation_id using $_email - Admin bypass? $_admin_bypass");

            Log::info("Emails found for ASN $_asn " . implode(', ', $email_list ?: []));

            $forbidden_asns = [ '137409', '136510', '7578' ];
    
            // Perform a who-is for the provided ASN, then check if the provided email is associated with the ASN who-is records.
            if(in_array($_email, $email_list)) {

                $final_asn = null;
    
                if(!$asn_exists) { // We're creating a whole new ASN

                    $final_asn = Asns::create([
                        'asn' => ltrim($_asn, 'ASN'),
                        'approved' => 0,
                        'organisation_id' => $_organisation_id
                    ]);

                }
                elseif(!in_array($asn_exists->asn, $forbidden_asns) || $asn_exists->organisation_id == Auth::user()->organisation_id) { // We're reassociating a GSL "owned" ASN OR the user is re-sending an invite.

                    $final_asn = $asn_exists;
                }

                if($final_asn !== null) {

                    // Generate a token.
                    $email_token = Str::random(3) . '-' . Str::random(3) . '-' . Str::random(3) . '-' . Str::random(3) . '-' . Str::random(3) . '-' . Str::random(3);
                            
                    while(CodeAsnClaim::withTrashed()->where('token', '=', hash('sha256', $email_token))->count()) {
                        $email_token = Str::random(3) . '-' . Str::random(3) . '-' . Str::random(3) . '-' . Str::random(3) . '-' . Str::random(3) . '-' . Str::random(3); /// Friendlier token generation - Helps us avoid slurs.
                    }

                    CodeAsnClaim::create([
                        'asn_id' => $final_asn->id, 
                        'organisation_id' => Auth::user()->organisation_id,
                        'token' => hash('sha256', $email_token),
                        'expiry_at' => Carbon::now()->addDays(2)
                    ]);

                    $is_transfer = $final_asn->organisation_id != 1 && Auth::user()->organisation_id != $final_asn->organisation_id;

                    // Send ASN approval email to the NOC.
                    Mail::to(config('app.env') === 'development' ? config('security.development_email_direct') : $_email)->send(new AsnApproval($email_token, $is_transfer));

                    return true;
                }
            }

            return false;
        }
        else {
            throw new Exception('No matching organisation found.');
        }
    }

    // This exists to clean up any associated AS-SETs, dedi-sessions, prefixes and tunnels for an ASN - Only needs to occur with Org <-> Org transfers
    public function clean() {

        /** Temporary code for debugging an extremely strange situation. */
        
        if (!isset($this->id) || $this->id === null || empty($this->id)) {

            Log::error('CLEAN CALLED UNDER STRANGE CIRCUMSTANCES: ' . json_encode([
                'time' => microtime(true),
                'asn_id' => $this->id ?? 'NULL',
                'backtrace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS),
                'server' => $_SERVER,
            ]));

            return;
        }

        /** End temporary code */

        Log::info("Cleaning ASN ID $this->id");

        // Remove prefixes
        Prefixes::where([['asn_id', '=', $this->id], ['organisation_id', '!=', 1]])->orderBy('mask', 'desc')->get()->each->delete();

        // Transfer AS-SETs
        AsSets::where('as_set', 'LIKE', "AS$this->asn:%")->update([
            'organisation_id' => $this->organisation_id
        ]);

        // Delete tunnels
        // TO-DO - DELETE ALL TUNNELS FOR A GIVEN ASN

        // Remove DEDI sessions
        
        $compute = new ComputeController;
        $current_offset = 0;
        $scan_complete = false;
        $to_delete = [];

        try {
            while(!$scan_complete) {

                $row_selection = $compute->helper_get_peers($current_offset, 100);

                foreach($row_selection as $row) {

                    if($row['asn'] == $this->asn) {
                        $to_delete[] = $row['id'];
                    }
                }

                if(sizeof($row_selection) != 100) {
                    $scan_complete = true;
                    break;
                }

                $current_offset += 100;
            }
        }
        catch(Throwable $t) {
            Log::error($t);
            Log::error("Difficulty retrieving DEDI sessions for ASN ID $this->id");
        }

        foreach($to_delete as $x) {
            try {
                \Log::debug($x);
                //$compute->helper_delete_peer($x);
            }
            catch(Throwable $t) {
                Log::error($t);
                Log::error("Difficulty deleting a DEDI session for ASN ID $this->id");
            }
        }

    }

}
