<?php

namespace App\Helpers\Utility;

use Symfony\Component\Process\Process;
use App\Helpers\Utility\SanityChecker;
use Illuminate\Support\Facades\Log;

class FRR {

  public static function write_bgp($neighbour_v4s = [], $neighbour_v6s = []) {
    // Neighbour arrays look like: [ 'address' => x, 'map' => x, 'description' => x, 'asn' => x ]
    $neighbour_block = [];
    $address_v4_block = [];
    $address_v6_block = [];
      
    // Only add in valid v4 addresses.
    foreach($neighbour_v4s as $key => $neighbour) {
      if(SanityChecker::ip_address($neighbour['address']) && SanityChecker::name($neighbour['description']) && filter_var($neighbour['asn'], FILTER_VALIDATE_INT)) {
        $address = $neighbour['address'];
        $asn = $neighbour['asn'];
        $description = $neighbour['description'];

        $neighbour_block = array_merge($neighbour_block, [
          '-c', "neighbor $address remote-as $asn",
          '-c', "neighbor $address description $description"
        ]);
  
        $address_v4_block = [
          '-c', "neighbor $address next-hop-self force",
          '-c', "neighbor $address remove-private-AS all",
          '-c', "neighbor $address maximum-prefix 30",
          '-c', "neighbor $address route-map $asn in"
        ];
      }
    }

    // Only add in valid v6 addresses.
    foreach($neighbour_v6s as $key => $neighbour) {
      if(SanityChecker::ip_address($neighbour['address']) && SanityChecker::name($neighbour['description']) && filter_var($neighbour['asn'], FILTER_VALIDATE_INT)) {
        $address = $neighbour['address'];
        $asn = $neighbour['asn'];
        $description = $neighbour['description'];

        $neighbour_block = array_merge($neighbour_block, [
          '-c', "neighbor $address remote-as $asn",
          '-c', "neighbor $address description $description"
        ]);
  
        $address_v6_block = [
          '-c', "neighbor $address activate",
          '-c', "neighbor $address next-hop-self force",
          '-c', "neighbor $address remove-private-AS all",
          '-c', "neighbor $address maximum-prefix 30",
          '-c', "neighbor $address route-map $asn in"
        ];
      }
    }

    // Bring all of the command blocks together into a single command.
    $bgp_command = new Process(array_merge(
      ['vtysh', '-c', 'conf t', '-c', 'router bgp ' . config('security.gsl_asn')],
      $neighbour_block,
      ['-c', "address-family ipv4 unicast"],
      $address_v4_block,
      ['-c', "exit-address-family"],
      ['-c', "address-family ipv6 unicast"],
      $address_v6_block,
      ['-c', "exit-address-family"],
      ['-c', "exit", '-c', "exit", '-c', "write"]
    ));
      
    // Run the command
    $bgp_command->run();

    Log::debug($bgp_command->getCommandLine());

    // Check if the command executed 'successfully' as far as POSIX is concerned.
    if($bgp_command->isSuccessful()) {
      if(str_contains($bgp_command->getOutput(), '[OK]')) {
        return true;
      }
    }

    return false;
  }

  public static function add_as_paths($_asns) {
    $as_paths = [];
    
    foreach($_asns as $asn) {
      if(filter_var($asn, FILTER_VALIDATE_INT)) {
        $as_paths[] = '-c';
        $as_paths[] = "bgp as-path access-list $asn seq 5 permit ^$asn$";
        $as_paths[] = '-c';
        $as_paths[] = "route-map $asn permit 10";
        $as_paths[] = '-c';
        $as_paths[] = "call gre-in";
        $as_paths[] = '-c';
        $as_paths[] = "match as-path $asn";
      }
    }

    // Bring all of the command blocks together into a single command.
    $as_path_command = new Process(array_merge(
      ['vtysh', '-c', 'conf t'],
      $as_paths,
      ['-c', 'exit', '-c', 'exit', '-c', 'write']
    ));

    $as_path_command->run();

    if($as_path_command->isSuccessful()) {
      if(str_contains($as_path_command->getOutput(), '[OK]')) {
        return true;
      }
    }

    return false;
  }

  public static function remove_as_path($_asn) {
    if(filter_var($_asn, FILTER_VALIDATE_INT)) {
      // Bring all of the command blocks together into a single command.
      $as_path_command = new Process([
        'vtysh', '-c', 'conf t',
        '-c', "no bgp as-path access-list $_asn", // Destroy the as-path
        '-c', "no route-map $_asn", // Destroy the route-map
        '-c', 'exit', // Exit the conf-t construct
        '-c', 'write' // Write
      ]);

      $as_path_command->run();

      if($as_path_command->isSuccessful()) {
        if(str_contains($as_path_command->getOutput(), '[OK]')) {
          return true;
        }
      }
    }

    return false;
  }

  public static function terminate_bgp($_ip_address) {
    if(SanityChecker::ip_address($_ip_address)) {
      $bgp_command = new Process([
        'vtysh', '-c', 'conf t', '-c', 'router bgp ' . config('security.gsl_asn'), // Access the BGP construct.
        '-c', "no neighbor $_ip_address", // Delete the specific neighbour
        '-c', 'exit', '-c', 'exit', '-c', 'write' // Write changes.
      ]);

      $bgp_command->run();

      if($bgp_command->isSuccessful()) {
        if(str_contains($bgp_command->getOutput(), '[OK]')) {
          return true;
        }
      }
    }

    return false;
  }

  public static function verify_bgp($_ip_address) {
    $valid_address = SanityChecker::ip_address($_ip_address);

    if($valid_address) {
      $bgp_command = new Process(['vtysh', '-c', "show ip bgp neighbors $_ip_address"]);
            
      // Run the command
      $bgp_command->run();
        
      // Check if the command executed 'successfully' as far as POSIX is concerned.
      if($bgp_command->isSuccessful()) {
        $bgp_status = $bgp_command->getOutput();
        
        // Remove spaces from the output then search for a substring verifying it's up.
        str_replace(' ', '', strtoupper($bgp_status));
        
        if(str_contains($bgp_status, 'BGPSTATE=CONNECT') || str_contains($bgp_status, 'BGPSTATE=ACTIVE')) {
          return true;
        }
        else {
          return $bgp_status;
        }
      }
    }

    return false;
  }

}

?>