<?php
namespace Clevercherry\Backslash4\Console;

use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Clevercherry\Backslash4\Backslash4ServiceProvider;
use Clevercherry\Backslash4\Backslash4;
use Clevercherry\Backslash4\Console\Traits\askWhichTenant;
use Clevercherry\Backslash4\Models\MediaManager;
use Clevercherry\Backslash4\Models\Admin;
use Clevercherry\Backslash4\Models\Page;
use Clevercherry\Backslash4\Models\Setting;
use SebastianBergmann\Type\NullType;
use ZipArchive;

use function PHPUnit\Framework\fileExists;

class Backslash4Command extends Command
{
    use askWhichTenant;

    protected $name = 'backslash';
    protected $signature = 'backslash {menu=null} {value=null}';
    protected $description = 'backslash 4 operations & system';
    protected $scrWidth = null;
    private $msg = [];

    //word versions of numbered menu
    protected $commands = [
        0 => 'exit',
        1 => 'help',
        2 => 'status',
        21 => 'backup-main',
        22 => 'backup-files',
        23 => 'backup-vendor',
        24 => 'backup-db',
        40 => 'install',
        41 => 'deploy',
        44 => 'add-module',
        45 => 's3-config',
        46 => 'add-event-settings',
        47 => 'install:js',
        49 => 'upgrade',
        51 => 'add-user',
        52 => 'list-users',
        53 => 'add-admin',
        54 => 'list-admins',
        55 => 'reset-admin-password',
        60 => 'route:match',
        70 => 'uninstall',
        71 => 'del-config',
        72 => 'del-module',
        79 => 'uninstall-all',
        80 => 'setup-tenants',
        81 => 'update-tenants',
        82 => 'add-tenant',
        83 => 'show-tenant',
        84 => 'set-tenant-setting',
        85 => 'link-tenants',
        88 => 'migrate',
        89 => 'migrate:rollback',
        90 => 'clear',
        101 => 'bin',
        108 => 'oct',
        116 => 'hex',
    ];

    protected $yesArray = [
        'y',
        'yes',
        '1',
        '-1',
        'ye',
        'yea',
        'yeah',
        'yay',
        'jah',
        'oui',
        'ci',
        'oes',
        'tha',
        'ta',
        'tá',
        'true',
        'confirm',
        'athon',
        'oeí',
        'oei',
        'ya',
        'sran',
        'nása',
        'nasa',
        'náto',
        'nato',
        'ná',
        "hija'",
        'hislah',
        "lu'",
        'lu',
        'luq'
    ];

    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Get Protected.
     * @return array
     */
    public function getProtected($var)
    {
        return $this->{$var};
    }

    public function handle()
    {
        $menu = $this->argument('menu');

        if ((!empty($menu) || ($menu == 0)) && $menu != 'null') {
            $this->doAction($menu, false);
            $this->showMsg();
            return;
        }

        $this->showStatus(false);
        $option = '999';
        while ($option !== '0') {
            $this->newLine();
            $this->info('Backslash4 Main Menu');
            $this->info('--------------------');

            $option = $this->showMenu();

            $this->doAction($option);
        }
    }


    /**
     * ----------------------------------------------------------------------
     *  Command Line Interface & Menu Functions
     * ----------------------------------------------------------------------
     */

    private function showMenu()
    {
        //providing width here means the dataInfo function doesn't have to find it out each call
        $width = $this->getTerminalWidth();

        $this->newLine();
        $this->menuLine(0, 'Exit Backslash4 Main Menu');
        $this->menuLine(1, 'List Commands');
        $this->menuLine(2, 'Show Status');

        $this->menuLine(21, 'ZIP/Backup App Files');
        $this->menuLine(22, 'ZIP/Backup App Files, Public and Storage Media');
        $this->menuLine(23, 'Zip/Backup Vendor Files');
        $this->menuLine(24, 'Zip/Backup Database');

        $this->menuLine(40, 'Install Menu (backslash:install)');
        $this->menuLine(70, 'Uninstall Menu (backslash:uninstall)');
        $this->menuLine(90, 'Clear Laravel Caches (route, cache, view, config)');
        $this->newLine();

        $this->showMsg();

        return $this->ask('Select a menu option');

    }

    /**
     * All functions for all three backslash commands execute through this common switch statement
     * so that the commands can be called as a command line option or from any menu if you know trhe number or command
     */
    public function doAction($option, $pause = true)
    {
        switch ($option) {
            case 0:
            case 'q': //misspelt and aliases
            case 'quit':
            case 'bye':
            case 'xeit':
            case 'xit':
            case $this->commands[0]:
                $option = 0;
                break;
            case 'h': //misspelt aliases for help
            case 'hlp':
            case 'hrlp':
            case 1:
            case $this->commands[1]: //help
            case 911:
            case 999:
                $this->showHelp($pause);
                break;
            case 2:
            case $this->commands[2]:
                $this->showStatus($pause);
                break;
            case 21:
            case $this->commands[21]:
                $this->pushMsg($this->zipBackup());
                break;
            case 22:
            case $this->commands[22]:
                $this->pushMsg($this->zipBackup(true));
                break;
            case 23:
            case $this->commands[23]:
                $this->pushMsg($this->zipVendor());
                break;
            case 24:
            case $this->commands[24]:
                $msgs = $this->backupDatabase();
                foreach ($msgs as $msg)
                    $this->pushMsg($msg);
                break;
            case 40:
            case $this->commands[40]:
                $this->call('backslash:install');
                break;
            case 41:
            case $this->commands[41]:
                $this->deploy();
                $this->upgrade();
                break;
            case 44:
            case $this->commands[44]:
                $this->addModules();
                break;
            case 45:
            case $this->commands[45]:
                $this->s3Config();
                break;
            
            case 46:
            case $this->commands[46]:
                $this->addEventSettings();
                break;
            case 47: //=> 'install:js',
            case $this->commands[47]:
                $this->installJS();
                break;

            case 49:
            case $this->commands[49]:
            case 'update':
                $this->upgrade();
                break;
            case 51:
            case $this->commands[51]:
                $this->addUser();
                break;
            case 52:
            case $this->commands[52]:
                $this->listUsers();
                if ($pause)
                    $this->keyContinue();
                break;
            case 53:
            case $this->commands[53]:
                $this->addAdmin();
                break;
            case 54:
            case $this->commands[54]:
                $this->listAdmins();
                if ($pause)
                    $this->keyContinue();
                break;
            case 55: //55 => 'reset-admin-password',
            case $this->commands[55]:
                $this->resetAdminPassword();
                break;
            
            case 60:
            case $this->commands[60]:
                $this->routeMatch();
                break;
            case 70:
            case $this->commands[70]:
                $this->call('backslash:uninstall');
                break;
            case 71:
            case $this->commands[71]:
                $this->deleteConfig();
                break;
            case 72:
            case $this->commands[72]:
                $this->removeModules();
                break;
            case 79:
            case $this->commands[79]:
                $this->deleteAll();
                break;
            case 80:
            case $this->commands[80]:
                $this->setupTenants();
                break;
            case 81:
            case $this->commands[81]:
                $this->updateTenants();
                break;
            case 82:
            case $this->commands[82]:
                $this->addTenant();
                break;
            case 83:
            case $this->commands[83]:
                $this->showTenant();
                break;
            case 84:
            case $this->commands[84]:
            case 'set-tenant':
                $this->setTenant();
                break;
            case 85:
            case $this->commands[85]:
                $this->reLinkTenants();
                break;
            case 88:
            case $this->commands[88]:
                $this->doMigrations();
                break;
            case 89:
            case $this->commands[89]:
                $this->doMigrationsRollback();
                break;
            case 'uninstall-all-noask':
                $this->deleteAll(false);
                break;
            case 90:
            case $this->commands[90]:
                $this->clearAllLaravel();
                break;
            case 101:
            case 102:
            case $this->commands[101]:
                $this->toBase(2, $this->argument('value'));
                break;
            case 108:
            case $this->commands[108]:
                $this->toBase(8, $this->argument('value'));
                break;
            case 116:
            case $this->commands[116]:
                $this->toBase(16, $this->argument('value'));
                break;
            case 909: //testing funtion for devs
                $this->test909();
                break;

            //Easter Eggs -- fun commands you'll have to run them to see what they say ...ian
            //... and wally says start with help
            case gzuncompress(base64_decode('eNrLTMwDAAJuATk=')):
                //$d="";if(!empty($d)) $this->pushMsg(base64_encode(gzcompress($d,9)));
                $this->pushMsg(gzuncompress(base64_decode('eNoLycgsVihPLFZIyi9NzyhRKMlXSKpUKMlIVchJLSlJLVJ41NGmA+bnleYmAflmlgpqCol5CvlFiXnpQFWZQF26xYlAKT0AH0QbAg==')));
                break;
            case gzuncompress(base64_decode('eNpLLCjISQUABjcCEw==')):
                $this->pushMsg(gzuncompress(base64_decode('eNpzLCjISVXITcxOLVYoyUhVyAzIyM9L1VFIKi1RODzfJM8kL1FFIbEoVaGgFEiklmXm6AEA2o0SHA==')));
                break;
            case gzuncompress(base64_decode('eNrLLs3NLk8sAQAMGgMF')):
                $this->pushMsg(gzuncompress(base64_decode('eNrzLs3NLk8sUVSIzC9VL0tVSEpNzVMozsgvKMjMS1dILFHwVQtWyEgsS81TL1GozC/VAwCv/BD6')));
                break;
            case gzuncompress(base64_decode('eNpLKyrNLAEABnACKw==')):
                $this->pushMsg(gzuncompress(base64_decode('eNqLzC9Vz8lRyEgsS1UoyVfIzS9KVSguSE3OTMtMVkhMyi8tUUgrKs0sAQAncA7C')));
                break;
            case gzuncompress(base64_decode('eNrLL0rMS08FAAjbAn0=')):
                $this->pushMsg(gzuncompress(base64_decode('eNrzL0rMS08tVkgsSlXw91ZIzEtRSCvNyVHIT1MoyyxJzM3MU0jWUUgqLVE4PN8kzyQvUQWstKAUSKSWZeboAQDr/xas')));
                break;
            case gzuncompress(base64_decode('eNpLSswDQgAITwJi')):
                $this->pushMsg(gzuncompress(base64_decode('eNqLzC9VyEgsS1XIyy9RSEpNzVPIySwuSc3LzEuHiFfml+ooHJ7v8Kij38TvcOuh5QqOQa4KrmGePgqKAM8TFv4=')));
                break;
            case gzuncompress(base64_decode('eJwrzctMzk9JBQAL4QLo')):
                $this->toBase(34, $this->argument('value'));
                break;
            case 'dencode':
                $this->dencode();
                break;

            default:
                $this->info('Unknown option, try again...');
        }

    }

    /**
     * Show the help, a list of commands
     */
    private function showHelp($pause = true)
    {
        $this->newLine();
        $this->info('Backslash4 Artisan Commands');
        $this->info('---------------------------');
        $this->info(' backslash            show this menu');
        $this->info(' backslash {option}   execute a menu option {number or command}');
        $this->info(' backslash:install    run the installer');
        $this->info(' backslash:uninstall  run the installer');
        $this->newLine();
        $this->info('Command Name {option}');
        $s = '';
        foreach ($this->commands as $key => $value) {
            if ($key < 65000 && ($key < 81 || $key > 89 || !empty(config('backslash4.tenants-on', 0)))) {
                if (!empty($s))
                    $s .= ', ';

                $s .= $value . (($key < 1000) ? ' [' . $key . ']' : '');
            }
            if ($key == 23) //easter egg fun
                $s .= ', ' . gzuncompress(base64_decode('eNpLKyrNLAEABnACKw=='));

            if ($key == 116) //text only commands no numbers
                $s .= ', uninstall-all-noask';
        }
        $this->info($s);
        $this->newLine();

        if ($pause)
            $this->keyContinue();
    }

    /**
     * ----------------------------------------------------------------------
     *  Output & Delayed Message Handling and Output
     * ----------------------------------------------------------------------
     */

    /**
     * Empty the array or messages to ouput at the end of the command
     */
    public function clearMsg()
    {
        $this->msg = [];
    }

    /**
     * Push a message onto the output list so the info is displayed after the menu is re-shown and info is 
     * not lost in the scroll
     */
    public function pushMsg($str)
    {
        $this->msg[] = $str;
    }

    /**
     * show saved MSG in a highligted box in red
     */
    public function showMsg()
    {
        if (!empty($this->msg)) {
            $this->newLine();
            $this->dataInfo('Message');
            foreach ($this->msg as $line) {
                $this->info(' > ' . $line);
            }
            $this->dataInfo('');
            $this->newLine();
        }
        $this->clearMsg();
    }

    /**
     * Get the terminal width so we can pad output correctly
     * this value is stored so we don't have to run the external command repeatedly
     * @return int    terminal width
     */
    public function getTerminalWidth(): int
    {
        if (empty($this->scrWidth)) {
            $this->scrWidth = exec('tput cols');
            if (empty($this->scrWidth))
                $this->scrWidth = 80;
        }

        return $this->scrWidth;
    }

    /**
     * Pause until user hits enter
     */
    public function keyContinue()
    {
        $this->newLine();
        $this->ask('Press Enter to continue.....');
        $this->newLine();
    }


    /**
     * output info formatted to the screen width
     * @param String  $name    field name left set
     * @param String  $info    right set
     * @param Integer $width   null or screen width, if null screen width is detected
     * @param String  $char    padding character
     * @return void
     */
    public function dataInfo($name, $info = '', $width = null, $char = '.'): void
    {
        if (empty($width)) {
            $width = $this->getTerminalWidth();
        }

        $len = strlen($name) + strlen($info);
        if ($len < ($width - 5))
            $this->info(' ' . $name . ' ' . str_repeat($char, $width - $len - 4) . ' ' . $info);
        else
            $this->info(' ' . $name . ' ... ' . $info);

    }

    /**
     * Show current status of deployment
     * -- needs a lot of improvement and make it briefer more concise
     * -- also needs to be made tenant compatible
     */
    public function showStatus($pause = false)
    {
        //providing width here means the dataInfo function doesn't have to find it out each call
        $width = $this->getTerminalWidth();

        $this->newLine();
        $this->info('STATUS of Master Config');
        $this->dataInfo('Asset: Config Deployed', (file_exists(config_path('backslash4.php')) ? 'YES' : 'NO'), $width);
        $this->dataInfo('Package Path', Backslash4ServiceProvider::packagePath(), $width);
        $this->newLine();

        $config = $this->getConfig();
        $cmsMenu = Backslash4::getDashboardFlatCMS();

        $this->dataInfo('', 'Fr/E Admin', $width, ' ');
        $this->dataInfo('Modules', 'Menu Table Model Cntlr Cntlr', $width);

        $path = app_path('Http/Controllers/Admin/DashboardController.php');
        $i = '  --   --    --   -- ' . (file_exists($path) ? ' yes ' : '  no ');
        $this->dataInfo('> Dashboard', $i, $width);

        foreach ($this->getModules() as $key => $item) {
            $studly = Str::studly(Str::singular($key));

            $i = isset($cmsMenu[$key]) ? ' yes ' : '  no ';
            $i .= Schema::hasTable(Str::snake($key)) ? ' yes  ' : '  no  ';

            $path = app_path('Models/' . $studly . '.php');
            $i .= file_exists($path) ? ' yes ' : '  no ';

            $path = app_path('Http/Controllers/' . $studly . 'Controller.php');
            $i .= file_exists($path) ? ' yes ' : '  no ';
            $path = app_path('Http/Controllers/Admin/' . $studly . 'Controller.php');
            $i .= file_exists($path) ? ' yes ' : '  no ';

            $this->dataInfo('> ' . $key, $i, $width);
        }

        if ($pause)
            $this->keyContinue();
    }

    /**
     * ----------------------------------------------------------------------
     *  Misc Support Functions
     * ----------------------------------------------------------------------
     */

    /**
     * convert decimal to any other base number up to 36
     */
    public function toBase($base, $n = null)
    {
        //see if you can figure out what it is and what it does 
        if ($n == null || $n == 'null')
            $n = $this->ask(gzuncompress(base64_decode('eJxzzStJLVJIVMgrzU1KLQIAJaAFKQ')));
        if (!empty($n) || ($n === '0')) {
            $num = 0 + $n;
            $s = '';
            if ($num == 0)
                $s = '0';
            while ($num > 0) {
                $r = floor($num / $base);
                $s = substr('0123456789ABCDEFGHJKLMNPQRSTUVWXYZ', $num - ($r * $base), 1) . $s;
                $num = $r;
            }
            $baseSmall = '';
            foreach (str_split($base) as $char) // convert to subscript unicode  characters
                $baseSmall .= chr(226) . chr(130) . chr(128 + ord($char) - 48); //substr('₀₁₂₃₄₅₆₇₈₉',3*(ord($char)-48),3);

            $this->info('  ' . $n . $this->subscript('10') . ' => ' . $s . $this->subscript($base));
        }
    }
    /**
     * 
     */
    public function subscript($number) //numbers only
    {
        $small = '';
        foreach (str_split($number) as $char) // convert to subscript unicode  characters
            $small .= chr(226) . chr(130) . chr(128 + ord($char) - 48); //substr('₀₁₂₃₄₅₆₇₈₉',3*(ord($char)-48),3);

        return $small;
    }

    public function dencode()
    {
        //decode and encode strings used in obfuscating stuff domyou want to send secret messages?
        $txt = $this->ask(gzuncompress(base64_decode('eJxzzStJLVJwSSxJVNCwVygoSk3LrFAoyVdISU3OT0nVBACmtgp+')));
        if (!empty($txt)) {
            if (Str::startsWith($txt, gzuncompress(base64_decode('eJyzBwAAQABA')))) {
                $this->info($txt . ' ' . gzuncompress(base64_decode('eJzTSElNzk9J1bS1AwASUAMx')));
                $this->info(gzuncompress(base64_decode(substr($txt, 1))));
            } else {
                $this->info($txt . ' ' . gzuncompress(base64_decode('eJzTSM1Lzk9J1bS1AwASoQM7')));
                $this->info(base64_encode(gzcompress($txt)));
            }
        }

    }

    public function menuLine($number, $text)
    {
        $this->info('[' . $number . '] ' . $text . (!empty(@$this->commands[$number]) ? ' {' . $this->commands[$number] . '}' : ''));
    }

    /**
     * ----------------------------------------------------------------------
     * DEV Helper functions
     * ----------------------------------------------------------------------
     */

     public function routeMatch()
     {
        $path = $this->argument('value');

        if(empty($path) || $path=='null')
            $path = $this->ask('Enter a path:');

        $this->info( ' ');
        $this->info( 'Path: ' . $path);
        try {
            $this->info( json_encode(app('router')->getRoutes()->match(app('request')->create($path)), JSON_PRETTY_PRINT) );
        } catch (\Throwable $th) {
            $this->info('no GET routing');
        }
        try {
            //code...
            echo json_encode(app('router')->getRoutes()->match(app('request')->create($path, 'POST'))->getName()) ;
        } catch (\Throwable $th) {
            $this->info('no POST routing');
        }
    }

    /**
     * ----------------------------------------------------------------------
     *  Clear Cache Functions
     * ----------------------------------------------------------------------
     */

    /**
     * Clear the config and related caches
     */
    public function clearConfigCache()
    {
        //this allows dynamic upgrades of the config file
        $this->callSilently('config:cache');
        $this->callSilently('config:clear');
        $this->callSilently('cache:clear');
    }

    /**
     * Clear everything we can think of that's laravel
     */
    private function clearAllLaravel(): void
    {
        $this->call('route:clear');
        $this->call('cache:clear');
        $this->call('config:clear');
        $this->call('view:clear');
        $this->call('optimize:clear');
        $this->call('clear');
        $this->call('clear-compiled');
        exec('composer cc');
        $this->clearConfigCache();
        
        //tenant views
        if (config('backslash4.tenants-on', 0) != 0) {
            $this->info('Clearing Tenant Views.');
            foreach (config('backslash4.tenants') as $key => $tenant) {

                $nameSpace = Backslash4::getTenantNameSpace($key);
                $path = storage_path('framework/' . $nameSpace . '/views');
                $this->info('Tenant View: '. $key . ' : ' . $nameSpace );
                
                if (is_dir($path)) {
                    File::delete(File::glob($path.'/*.php'));
                    //mkdir($path, 0755, true);
                    touch($path . '//.gitignore');
                }
            }
        }

        $this->pushMsg('Laravel Caches Cleared.');
    }

    /**
     * ----------------------------------------------------------------------
     *  CONFIG Manipulation Functions
     * ----------------------------------------------------------------------
     */

    /**
     * get the config file as an object, after clearing the  config cache
     * @param  String $primaryKey     top level key name for the config, default 'backslash4'
     * @param  bool   $refresh        whether to clear the config cache, default=true
     * @return Array | null           config array
     */
    public function getConfig($primaryKey = null, $refresh = true): array|null
    {
        if ($primaryKey == null)
            $primaryKey = 'backslash4';

        if ($refresh)
            $this->clearConfigCache();

        return Config::get($primaryKey);
    }

    /**
     * get the config file as an object, or the app (backslash4)config tenat key is null
     * @param  String $tenantKey      key name for the config('tenants.$tenantKey'), if null will return the default 'backslash4'
     * @param  bool   $refresh        whether to clear the config cache, default=true
     * @return Array | null           config array
     */
    public function getTenantConfig($tenantKey = null, $refresh = true): array|null
    {
        if ($refresh)
            $this->clearConfigCache();

        if (!empty($tenantKey))
            return Config::get('tenants.' . $tenantKey);

        //else

        return Config::get('backslash4');
    }

    /**
     * var_export() with square brackets and indented 4 spaces, just like we want for the config ini file
     * @deprecated  -- function moved to Backslash4::varexport($expression, $return);
     * 
     * @param  String $expression
     * @param  bool $return         whether to echo or return the tided up string, default false (echo the output)
     * @return String | null
     */
    public function varexport($expression, $return = false): string|null
    {
        return Backslash4::varexport($expression, $return);
    }

    /**
     * Save the config file back out in the correct format
     * @param  Array  $config       config array
     * @param  String $key          top level key (and filename excluding .php) (default 'backslash4')
     * @return void
     */
    public function saveConfig($config, $key = 'backslash4'): void
    {
        config([$key => $config]);

        File::put(
            config_path($key . '.php'),
            "<?php\n\n//dynamic update (B)"
            . "\nreturn " . Backslash4::varexport($config, true) . ";\n\n?>"
        );
    }

    /**
     * Save the config file back out in the correct format, as main backslash4 config or tenant config
     * @param  Array  $config       config array
     * @param  String $tenantKey    config key (and filename excluding .php) (default 'backslash4')
     * @return void
     */
    public function saveTenantConfig($config, $tenantKey = null): void
    {

        if (empty($tenantKey)) {
            $this->saveConfig($config);

        } else {
            $configkey = Backslash4::getDashboardConfigKey($tenantKey);
            config([$configkey => $config]);

            File::put(
                Backslash4::getDashboardConfigPath(config('backslash4.tenants.' . $tenantKey, null)),
                "<?php\n\n//dynamic update (T)"
                . "\nreturn " . Backslash4::varexport($config, true) . ";\n\n?>"
            );
        }
    }

    /**
     * set the value in backslash4
     */
    private function setConfigBaseValue($key, $value): void
    {
        if (config('backslash4.' . $key) != $value) {
            $config = $this->getConfig();

            if (!isset($config[$key]) || $config[$key] != $value) {
                $config[$key] = $value;
                $this->saveConfig($config);
            }
        }
    }

    public function installJS()
    {
        $this->info('Installing Javascript et al');
        $files = [
            [ 'from' => '/resources/js/network/bs4-network-monitor.js',
              'to' => '/js/bs4-network-monitor.js'
            ]
        ];
        foreach($files as $file) {
            $from = Backslash4ServiceProvider::packagePath($file['from']);
            $to = public_path($file['to']);

            if( File::exists($from) ) {
                $this->info('from:' . $from);
                $this->info('  to:' . $to);

                File::ensureDirectoryExists(dirname($to));
                File::copy($from, $to);
                $this->info('    :' . $file['to']);
            }
            
        }
        $this->info('--------------------------');
        $this->info('now include this in the header of your aplication');
        $this->info('   <x-bs-js.network-script />');
        $this->info('--------------------------');
    }

    /**
     * ----------------------------------------------------------------------
     *  Backup ZIP Functions
     * ----------------------------------------------------------------------
     */

    /**
     * Zip and process down through subdirectory tree
     * This takes symlinks into account.
     *
     * @param ZipArchive $zip
     * @param string     $path
     */
    private function zipAddContent(\ZipArchive $zip, string $path)
    {
        /** @var SplFileInfo[] $files */
        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator(
                $path,
                    \FilesystemIterator::FOLLOW_SYMLINKS
            ),
                \RecursiveIteratorIterator::SELF_FIRST
        );

        while ($iterator->valid()) {
            if (!$iterator->isDot()) {
                $filePath = $iterator->getPathName();
                $relativePath = substr($filePath, strlen(dirname($path)) + 1);

                if (!$iterator->isDir()) {
                    $zip->addFile($filePath, $relativePath);
                } else {
                    if ($relativePath !== false) {
                        $zip->addEmptyDir($relativePath);
                    }
                }
            }
            $iterator->next();
        }
    }

    public function zipVendor()
    {
        $zip = new ZipArchive;
        $fileName = 'backslash4-backup-vendor' . date("Y-M-d--His") . '.zip';
        if ($zip->open(base_path($fileName), ZipArchive::CREATE) === TRUE) {
            self::zipAddContent($zip, base_path('vendor'));


            $msg = 'Vendor ZIP created, ' . $zip->filename;
            $zip->close();
            return $msg;
        }
        //else

        return false;
    }

    public function zipBackup($incExtra = false)
    {
        $zip = new ZipArchive;
        $fileName = 'backslash4-backup-' . ($incExtra ? 'plusstorage-' : '') . date("Y-m-d--His") . '.zip';

        if ($zip->open(base_path($fileName), ZipArchive::CREATE) === TRUE) {
            //we're specifiying folder because we don't want vendor
            self::zipAddContent($zip, app_path());
            self::zipAddContent($zip, base_path('bootstrap'));
            self::zipAddContent($zip, config_path());
            self::zipAddContent($zip, database_path());
            self::zipAddContent($zip, lang_path());
            self::zipAddContent($zip, resource_path());
            self::zipAddContent($zip, base_path('routes'));
            self::zipAddContent($zip, base_path('tests'));
            if ($incExtra) {
                self::zipAddContent($zip, base_path('node_modules'));
                self::zipAddContent($zip, public_path());
                self::zipAddContent($zip, storage_path());
            }

            //add root app files excluding ANY zips
            $files = File::files(base_path());
            foreach ($files as $key => $value) {
                $relativeNameInZipFile = basename($value);
                if (!Str::endsWith(strtolower($relativeNameInZipFile), '.zip'))
                    $zip->addFile($value, $relativeNameInZipFile);
            }

            $msg = 'ZIP created, ' . $zip->filename;
            $zip->close();

            return $msg;
        }
        //else
        return false;
    }

    /**
     * This does not run on some systems - suspect permissions issues
     */
    public function backupDatabase()
    {
        $databases = [];
        $databases[] = env('DB_DATABASE');
        $msgs = [];

        if (config('backslash4.tenants-on', 0) != 0) {
            foreach (config('backslash4.tenants') as $tenant) {
                $databases[] = $tenant['database'];
            }
        }

        foreach ($databases as $database) {
            $filename = 'backslash4-backup-' . $database . '-' . date("Y-m-d--His");
            $sqlFilename = base_path($filename . '.sql');

            if (!empty(env('DB_SOCKET')))
                $command = "mysqldump --column-statistics=0 --user=" . env('DB_USERNAME')
                    . " --password=" . env('DB_PASSWORD')
                    . " --protocol=socket -S \"" . env('DB_SOCKET') . "\" "
                    . $database
                    . " > " . $sqlFilename;
            else
                $command = "mysqldump --column-statistics=0 --user=" . env('DB_USERNAME')
                    . " --password=" . env('DB_PASSWORD')
                    . " --host=" . env('DB_HOST') . " "
                    . $database
                    . " > " . $sqlFilename;

            $this->info($command);

            $returnVar = NULL;
            $output = NULL;

            exec($command, $output, $returnVar);

            if (File::exists($sqlFilename)) {
                $zip = new ZipArchive;
                if ($zip->open($sqlFilename . '.zip', ZipArchive::CREATE) === TRUE) {
                    $zip->addFile($sqlFilename, $filename . '.sql');
                }
                $msg = 'SQL saved in ZIP, ' . $zip->filename;

                $zip->close();
                File::delete($sqlFilename);

                $msgs[] = $msg;
            }
        }

        if (!empty($msgs))
            return $msgs;
        //else

        return false;
    }

    /**
     * ----------------------------------------------------------------------
     *  Installer Functions (not tenants)
     * ----------------------------------------------------------------------
     */

     /**
      * 
      */
    public function addEventSettings()
    {
        $this->info('Adding Event Settings');
        $filename = '2025_05_06_150000_settings_for_network_events.php';

        $from = Backslash4ServiceProvider::packagePath("/database/migrations/" . $filename);

        $filename = date('Y_m_d_His') . '_settings_for_network_events.php';
        $to = database_path('migrations/' . $filename);

        File::copy($from, $to);

        $this->info('Migration Added, edit and migrate.');
        $this->info('file: '. $filename . ' in ' . database_path('migrations/'));   
    }

    /**
     * Return an array of defined modules for thebackslash4 installer
     * @return Array
     */
    public function getModules()
    {
        return [
            // name, addmenu-flag, icon, default=1/addon=0
            'Pages' => ['pages', 1, 'page', 1],
            'Blogs' => ['blogs', 1, 'page', 0],
            'Faqs' => ['faqs', 1, 'page', 0],
            'Clients' => ['clients', 1, 'page', 0],
            'Addresses' => ['', 0, 'page', 0],
            'Enquiries' => ['enquiries', 1, 'page', 0],
            'Orders' => ['orders', 1, 'page', 0],
            'Wishlists' => ['wishlists', 1, 'page', 0],
            'Products' => ['products', 1, 'page', 0],
            'ProductCategories' => ['productcategories', 1, 'page', 0],
            'ProductTypes' => ['producttypes', 1, 'page', 0],
            'MediaManager' => ['mediamanager', 1, 'page', 1],
            'Users' => ['users', 1, 'page', 1],
            'Admins' => ['admins', 1, 'page', 1],
            'Settings' => ['settings', 1, 'page', 1],
        ];
    }

    public function listUsers()
    {

        $tenantkey = '';
        if (!empty(config('backslash4.tenants-on', 0))) {
            $tenantkey = $this->askWhichTenant();
            $this->info('Tenant: ' . $tenantkey);
        }

        // empty tenant key falls back to mysql connection
        if (empty($tenantkey))
            $tenantkey = 'mysql';

        $check = $this->usingConnection($tenantkey, function () {
            $this->dataInfo("Username ( 1st & last name )", "email     Active");
            $users = User::get();
            foreach ($users as $user) {
                $line = $user->userName;
                $fname = trim(@$user->firstName . ' ' . @$user->lastName);
                if (!empty($fname))
                    $line .= " ({$fname})";

                $this->dataInfo($line, $user->email . '   ' . ($user->active ? 'Y  ' : 'N  '));
            }
        });
    }

    public function listAdmins()
    {
        $tenantkey = '';
        if (!empty(config('backslash4.tenants-on', 0))) {
            $tenantkey = $this->askWhichTenant();
            $this->info('Tenant: ' . $tenantkey);
        }

        // empty tenant key falls back to mysql connection
        if (empty($tenantkey))
            $tenantkey = 'mysql';

        $check = $this->usingConnection($tenantkey, function () {
            $this->dataInfo("Username ( 1st & last name )", "email     Active");
            $users = Admin::get();
            foreach ($users as $user) {
                $line = $user->userName;
                $fname = trim(@$user->firstName . ' ' . @$user->lastName);
                if (!empty($fname))
                    $line .= " ({$fname})";

                $this->dataInfo($line, $user->email . '   ' . ($user->active ? 'Y  ' : 'N  '));
            }
        });
    }

    public function pickAdmin()
    {
        $users = Admin::get();
        $this->dataInfo( str_pad(0,4,' ', STR_PAD_LEFT) . ' Cancel', '  e-mail Address    Active');

        foreach ($users as $user) {
            $line = str_pad($user->id,4,' ', STR_PAD_LEFT). ' ' .$user->userName;
            $fname = trim(@$user->firstName . ' ' . @$user->lastName);
            if (!empty($fname))
                $line .= " ({$fname})";

            $this->dataInfo($line, $user->email . '   ' . ($user->active ? 'Y  ' : 'N  '));
        }
        $id = $this->ask('Select an Admin', 0);

        return $id;
    }

    public function resetAdminPassword() //55 => 'reset-admin-password',
    {
        $tenantkey = '';
        if (!empty(config('backslash4.tenants-on', 0))) {
            $tenantkey = $this->askWhichTenant();
            $this->info('Tenant: ' . $tenantkey);
        }
        // empty tenant key falls back to mysql connection
        if (empty($tenantkey))
            $tenantkey = 'mysql';

        $check = $this->usingConnection($tenantkey, function () {

            $this->info("\n Select an Admin...");
            $id = trim($this->pickAdmin());
            if(!empty($id))
            {
                $admin = Admin::where('id', $id)->first();
                if(empty($admin) || $id=='0')
                {
                    $this->pushMsg('Could Not find that user, aborting process');
                    return;
                }
                //else

                $password = $this->secret('New Password');
                $password_confirmation = $this->secret('Confirm Password');

                if ($password !== $password_confirmation)
                    $password = $this->passwordRetry();

                if(empty($password) || empty(trim($password)))
                {
                    $this->pushMsg('No password, Update Admin cancelled.');
                    return;
                }
                //else
                $admin->password = Hash::make($password);
                $admin->save();
                $this->pushMsg('Admin password updated.');

            } else
            {
                $this->pushMsg('Update Admin cancelled.');
            }

        });
    }

    public function askNotEmpty($prompt, $default = null)
    {
        $ask = '';
        $faults = 0;
        while (empty($ask)) {
            $ask = trim($this->ask($prompt, $default));
            if (empty($ask)) {
                $this->info($prompt . ' cannot be empty');
                $faults++;
            }
            if ($faults > 2) {
                $this->pushMsg($prompt . ' cannot be empty, process aborted.');
                return false;
            }
        }
        return $ask;
    }

    /**
     * Add a User to the User table
     * @return void
     */
    public function addUser()
    {

        $tenantkey = '';
        if (!empty(config('backslash4.tenants-on', 0))) {
            $tenantkey = $this->askWhichTenant();
            $this->info('Tenant: ' . $tenantkey);
        }
        // empty tenant key falls back to mysql connection
        if (empty($tenantkey))
            $tenantkey = 'mysql';

        $store = [];
        $store['firstName'] = $this->askNotEmpty('First Name');
        if (empty($store['firstName']))
            return;

        $store['lastName'] = $this->askNotEmpty('Last Name');
        if (empty($store['lastName']))
            return;

        $store['userName'] = $this->askNotEmpty('User Name', $store['firstName'] . ' ' . $store['lastName']);
        if (empty($store['userName']))
            return;

        $store['email'] = $this->askNotEmpty('Email');
        if (empty($store['email']))
            return;

        $check = User::where('email', $store['email'])->first();
        if (!empty($check)) {
            $this->pushMsg('Email address already in use, please try again');
            return;
        }

        $password = $this->secret('Password');
        $password_confirmation = $this->secret('Confirm Password');

        if ($password !== $password_confirmation)
            $password = $this->passwordRetry();

        if(empty($password) || empty(trim($password)))
        {
            $this->pushMsg('No password, Create User cancelled.');
            return;
        }

        $store['password'] = Hash::make($password);

        $user = $this->usingConnection($tenantkey, function () use ($store) {
            //Config::set('database.default', $tenantkey);

            return User::create($store);
        });

        if (!empty($user)) {
            $this->pushMsg('New Dashboard user successfully created');
            return;
        }

        $this->pushMsg('Sorry an unknown error occured');
    }

    /**
     * Add a user to the Admin table
     * @return void
     */
    public function addAdmin()
    {

        $tenantkey = '';
        if (!empty(config('backslash4.tenants-on', 0))) {
            $tenantkey = $this->askWhichTenant();
            $this->info('Tenant: ' . $tenantkey);
        }
        // empty tenant key falls back to mysql connection
        if (empty($tenantkey))
            $tenantkey = 'mysql';

        $store = [];
        $store['firstName'] = $this->askNotEmpty('First Name');
        if (empty($store['firstName']))
            return;

        $store['lastName'] = $this->askNotEmpty('Last Name');
        if (empty($store['lastName']))
            return;

        $store['userName'] = $this->askNotEmpty('User Name', $store['firstName'] . ' ' . $store['lastName']);
        if (empty($store['userName']))
            return;

        $store['email'] = $this->askNotEmpty('Email');
        if (empty($store['email']))
            return;

        $check = $this->usingConnection($tenantkey, function () use ($store) {
            return Admin::where('email', $store['email'])->first();

        });
        if (!empty($check)) {
            $this->pushMsg('Email address already in use, please try again');
            return;
        }

        $password = $this->secret('Password');
        $password_confirmation = $this->secret('Confirm Password');

        if ($password !== $password_confirmation)
            $password = $this->passwordRetry();

        if(empty($password) || empty(trim($password)))
        {
            $this->pushMsg('No password, Create Admin cancelled.');
            return;
        }

        $store['password'] = Hash::make($password);

        $user = $this->usingConnection($tenantkey, function () use ($store) {
            //Config::set('database.default', $tenantkey);

            return Admin::create($store);
        });

        if (!empty($user)) {
            $this->pushMsg('New Dashboard user successfully created #' . $user->id);
            return;
        }

        $this->pushMsg('Sorry an unknown error occured');
    }

    /**
     * Get a password with confirmation and retries until successful
     * @return String  the input Password
     */
    public function passwordRetry()
    {
        $this->error('Passwords don\'t match, please try again');
        $password = $this->secret('Password');
        $password_confirmation = $this->secret('Confirm Password');

        if ($password !== $password_confirmation) {
            $password = $this->passwordRetry();
        }

        return $password;
    }

    /**
     * Install Backslash4
     */
    private function deploy()
    { //deploy is only ever for the base non-tenant
        if (file_exists(config_path('backslash4.php'))) {
            $this->upgradeConfig();
        } else {
            \Artisan::call('vendor:publish', ['--tag' => 'backslash4-config']);
            $this->upgradeConfig(true);
        }
        $this->addAdminController('Dashboard', true);

        $this->setupAdmins();
        $this->addModel('admins');
        $this->addAdminController('Admins', true);

        $this->setupBlocks();
        $this->addModel('blocks');

        $this->setupPages();
        $this->addModel('pages');

        $this->setupMediaManager();
        $this->addModel('MediaManager');
        self::addDefaultMediaManagerSeed();

        $this->setupSettings('settings');
        $this->addModel('settings');

        $this->createAllModels();
        $this->setupJS();
        $this->setupSCSS();

        $this->upgrade();
    }

    /**
     * Create Model classes
     */
    private function createAllModels()
    {
        foreach ($this->getModules() as $key => $item)
            $this->addModel(Str::snake($key)); //this is conditional upon the table existsing
    }

    /**
     * Backslash4 upgrade everything
     */
    private function upgrade()
    {
        $this->upgradeConfig();
        $this->upgradeTables();
        $this->createAllModels();

        $this->upgradeControllers();
        $this->upgradeAdminControllers();
        $this->upgradeAdminAuth();
        $this->createAllModels();
        $this->setupJS();
        $this->setupSCSS();

        if (File::exists(public_path('robots.txt')))
            File::delete(public_path('robots.txt'));

        foreach (config('backslash4.tenants', []) as $key => $tenantInfo) {
            if(File::exists(base_path($tenantInfo['paths']['base-path'] . '/' . $tenantInfo['paths']['config-file']))) {         
                //temporary change too a keyname is command line indicator to change database
                //config(['backslash4.tenants-on' => $key]);
                $this->upgradeConfig(false, $key);
                $this->upgradeTables(null, $key);

                //config(['backslash4.tenants-on' => 1]);
            }
        }
    }

    private function upgradeAdminAuth()
    {
        //create
        File::ensureDirectoryExists(app_path('Http/Middleware/'));
        $path = app_path('Http/Middleware/Admin.php');
        if (!file_exists($path)) {
            $template = "<?php\n\nnamespace App\Http\Middleware;\n\n"
                . "class Admin extends \Clevercherry\Backslash4\Middleware\Admin\n"
                . "{\n\n}";
            File::put($path, $template);
        }

        if(file_exists(app_path('Http/Kernel.php')))
        {
            $this->fileInsertAfter(
                app_path('Http/Kernel.php'),
                "\n        'admin' => \App\Http\Middleware\Admin::class,",
                'protected $routeMiddleware = ['
            );

        } else
        {
            // @TODO: Laravel 11, above in Laravel 11
            // this will require mods in the app/bootstrap file  -- done 2024-05-31 ./ianj as below

            $this->fileInsertAfter(
                base_path('bootstrap/app.php'),
                "\n        \$middleware->alias(["
                . "\n            'admin' => \App\Http\Middleware\Admin::class,"
                . "\n        ]);",
                '->withMiddleware(function (Middleware $middleware) {',
                '//'
            );
        }

        // add the required entries in the auth config
        $path = config_path('auth.php');
        $this->fileInsertAfter(
            $path,
            "\n        'admin' => [\n"
            . "            'driver' => 'session',\n"
            . "            'provider' => 'admins',\n"
            . "        ],",
            "'guards' => ["
        );
        $this->fileInsertAfter(
            $path,
            "\n        'admins' => [\n"
            . "            'driver' => 'eloquent',\n"
            . "            'model' => App\Models\Admin::class,\n"
            . "        ],",
            "'providers' => ["
        );
        
    }

    private function upgradeControllers()
    {
        $this->addAdminController('Dashboard', true);

        $this->setupAdmins();
        $this->addModel('admins');
        $this->addAdminController('Admins', true);

        $this->setupBlocks();
        $this->addModel('blocks');

        $this->setupPages();
        $this->addModel('pages');

        $this->setupSettings('settings');
        $this->addModel('settings');

        $this->setupMediaManager();
        $this->addModel('MediaManager');
        self::addDefaultMediaManagerSeed();

        foreach ($this->getModules() as $key => $item) {
            $this->addController($key);
        }
    }
    private function upgradeAdminControllers()
    {
        $this->addAdminController('Dashboard', true);
        foreach ($this->getModules() as $key => $item) {
            $this->addAdminController($key);
        }
    }

    private function upgradeTables($config = null, $tenantKey = null): void
    {
        if ($config == null) {
            $config = $this->getTenantConfig($tenantKey, false); //$this->getConfig();
        }
        $cmsMenu = Backslash4::getDashboardFlatCMS($config);

        $tables = [];
        $tables[] = 'Blocks';
        $tables[] = 'Users';

        // Build a comprehensive list of tables/modules form all the config files
        foreach ($this->getModules() as $key => $item) { //if module table exists run any table upgrades
            if (!in_array($key, $tables) && method_exists(self::class, "setup" . $key) && Schema::hasTable($key))
                $tables[] = $key;
        }

        if (!empty($config) && !empty($cmsMenu))
            foreach ($cmsMenu as $key => $info)
                if (!in_array($key, $tables) && empty(@$item['no-model']) && !isset($info['html']))
                    $tables[] = $key;

        if ($tenantKey != null) { //also get tables list from master config
            foreach (config('backslash4.cmsMenu') as $key => $info)
                if (!in_array($key, $tables) && empty(@$item['no-model']) && !isset($info['html']))
                    $tables[] = $key; //and all other tenants
            foreach (config('backslash4.tenants', []) as $keyT => $tenInfo) {
                foreach (Backslash4::getDashboardFlatCMS(config('tenants.' . $keyT)) as $key => $info)
                    if (!in_array($key, $tables) && empty(@$item['no-model']) && !isset($info['html']))
                        $tables[] = $key;
            }
        } //now ensure all those tables exist/are upgraded in the specified $tenantKey
        foreach ($tables as $table) {
            //$this->info('1199: '.$tenantKey.', '.$key);
            $this->usingConnection($tenantKey, function () use ($table, $tenantKey) {
                $this->info('1235 Tenant Key: ' . $tenantKey . ' table:' . $table);

                if (method_exists(self::class, "setup" . $table))
                    $this->{"setup" . $table}(Str::camel($table));
                else
                    $this->addGenericTable($table, $tenantKey);
            });
        }
    }

    private function addModules(): void
    {
        $arr = ['-cancel-'];
        foreach ($this->getModules() as $key => $item) {
            if ($item[3] == 0)
                array_push($arr, $key);
        }
        array_push($arr, '...Other (specify)');

        $name = $this->choice(
            'Add which module?',
            $arr,
            0,
            1,
            false
        );
        $this->pushMsg($name);
        if ($name == '...Other (specify)') {

            $name = trim($this->askNotEmpty('Your Module Name: '));
            if (!empty($name))
                $this->addGenericModule($name);
            else
                $this->pushMsg('Add Generic Module Aborted');

        } else if ($name != '-cancel-') {
            $this->addModule($name);
        } else {
            $this->pushMsg('Add Module Aborted');
        }
    }

    private function addGenericTable($moduleName, $connection = 'mysql'): void
    {
        //add Generic Table
        $thisTable = Str::snake($moduleName);
        if (empty($connection))
            $connection = 'mysql';
        $this->info('1282: ' . json_encode($connection));

        $this->usingConnection($connection, function () use ($moduleName, $connection) {

            $thisTable = Str::snake($moduleName);
            //shouldn't need this inside a usingConnection, but it does
            DB::connection($connection);
            $this->info('1280: ' . json_encode($connection));
            $this->info('1282: ' . json_encode("database.connections.{$connection}.database"));
            $this->info('1288: ' . json_encode(config("database.connections.{$connection}.database")));
            DB::connection()->setDatabaseName(config("database.connections.{$connection}.database"));
            $this->info('1289: ' . json_encode(DB::connection()->getDatabaseName(), JSON_PRETTY_PRINT));
            $this->info('1290: ' . json_encode(Schema::hasTable($thisTable)));

            if (!Schema::hasTable($thisTable)) {
                $this->info('1294: Creating Table ' . $thisTable . ' @' . $connection);

                Schema::getConnection()->setDatabaseName(config("database.connections.{$connection}.database"));
                Schema::create($thisTable, function (Blueprint $table) {
                    $table->id();
                    $table->String('name')->nullable();
                    $table->String('slug')->nullable();
                    $table->integer('active')->default(1);
                    $table->timestamps();
                });
            } else {
                $this->info('1306: Table Exists ' . $thisTable . ' @' . $connection);

            }
        });
    }

    private function addGenericModule($name)
    {
        $moduleName = Str::pluralStudly($name);
        $modelName = Str::singular($name);

        //add to menu
        $config = $this->getTenantConfig(); //$this->getConfig();
        $cmsMenu = Backslash4::getDashboardFlatCMS();
        if (!isset($cmsMenu[$moduleName])) {
            $config['cmsMenu'][$moduleName] = [
                'name' => Str::headline($moduleName),
                'slug' => Str::slug($moduleName),
                'icon' => 'page',
                'subMenu' => [
                    [
                        'slug' => 'create',
                        'text' => 'Add New ' . ucwords(Str::singular($moduleName))
                    ]
                ],
            ];
            $changed = true;
            $this->saveTenantConfig($config);
        }
        $this->addGenericTable($moduleName);

        //add generic model class
        $this->addModel($moduleName, 'Model');
        //add controller -- 
        $this->addController($moduleName, 'Controller');
        //add admin controller
        $this->addAdminController($moduleName, true, 'BkAdminController');


        //add a Page ??
        $page = Page::where('slug', Str::slug($moduleName))->first();
        if ($page == null) {
            $page = new Page();
            $page->name = Str::headline($moduleName);
            $page->slug = Str::slug($moduleName);
            $page->active = 1;
            $page->save();
        }

        $this->pushMsg('Add ' . $moduleName . ' completed, you may need to add migrations');
        $this->pushMsg('and you may wish to remove the front-end Page ' . Str::headline($moduleName));
    }

    private function addModule($name)
    {
        $originaldb = config("database.connections.mysql.database");

        $tenants = [];
        $tenants[0] = ['database' => $originaldb];
        if (config('backslash4.tenants-on', 0) != 0)
            foreach (config('backslash4.tenants') as $key => $tenant)
                $tenants[$key] = $tenant; foreach ($tenants as $tenantKey => $tenant) {
            $this->usingConnection($tenantKey, function () use ($name, $tenantKey) {
                $this->addMenuModule($name, $tenantKey);

                if (method_exists(self::class, "setup" . $name))
                    $this->{"setup" . $name}($name);
                else
                    $this->addGenericTable($name);

                $this->upgradeTables(null, $tenantKey);
            });
        }

        $this->createAllModels();
        $this->addController($name);
        $this->addAdminController($name);
    }

    private function addModel($module, $bkmodel = null)
    {
        $model = Str::studly(Str::singular($module));
        if (empty($bkmodel))
            $bkmodel = $model;

        $path = app_path('Models/' . $model . '.php');

        if (!file_exists($path)) {
            $table = Str::snake(Str::plural($module));
            if (Schema::hasTable($table)) {
                $this->dataInfo('add Model', $path);

                $guarded = ['id', 'created_at', 'updated_at'];
                $fields = Schema::getColumnListing($table);
                foreach ($guarded as $item) {
                    unset($fields[$item]);
                }

                unset($fields['id']);

                $scopeActive = isset($fields[$item]) ? ', scopeActive' : '';

                $template = "<?php\n\n"
                    . "namespace App\Models;\n\n"
                    . "use \\Clevercherry\\Backslash4\\Models\\{$bkmodel} as bkModel;\n"
                    . "use \\Clevercherry\\Backslash4\\Models\\ColumnFillable;\n"
                    . (!empty($scopeActive) ? "use \\Clevercherry\\Backslash4\\Models\\ScopeActive\n" : "")
                    . "\nclass {$model} extends bkModel\n"
                    . "{\n"
                    . "    use ColumnFillable{$scopeActive};\n"
                    . "    //ColumnFillable - this automagically populates \$fillable\n"
                    . "    //you may want to use this instead\n"
                    . "    //\$fillable=['" . implode("', '", $fields) . "'];\n\n\n"
                    . '}';


                $this->dataInfo('add Cntr', 'writing file');
                File::put($path, $template);
            } else {
                $this->dataInfo('No Table, not creating model', $table);
            }
        } else {
            $this->dataInfo('Model file already exists', $path);
        }
    }
    private function addController($module, $bkController = null)
    {
        $model = Str::studly(Str::singular($module));
        if (empty($bkController)) {
            $bkController = $model . 'Controller';
        }

        $path = app_path('Http/Controllers/' . $model . 'Controller.php');
        if (!file_exists($path)) {
            $table = Str::snake($module);
            if (Schema::hasTable($table)) {
                $this->dataInfo('add Cntr', $path);

                $template = "<?php\n\n"
                    . "namespace App\Http\Controllers;\n\n"
                    . "use Illuminate\Http\Request;\n"
                    . "use \Clevercherry\Backslash4\Controllers\\{$bkController} as bkController;\n\n"
                    . "class {$model}Controller extends bkController\n"
                    . "{\n\n"
                    . "\n\n}";

                File::put($path, $template);
            }
        }
    }

    private function addAdminController($module, $skipTableTest = false, $bkController = null)
    {
        $model = Str::studly(Str::singular($module));
        $content = '';
        $use = '';
        if (empty($bkController)) {
            $bkController = $model . 'Controller';
        } else {
            $use = "use App\\Models\\" . $model . ";\n";

            $content = "\t//public \$adminView = 'backslash4::products';  // uses generic\n"
                . "\tpublic \$adminRoute = 'admin." . Str::lower(Str::plural($module)) . "';\n"
                . "\tpublic \$AppModel = {$model}::class;\n\n";
        }

        $path = app_path('Http/Controllers/Admin/' . $model . 'Controller.php');
        if (!File::exists($path)) {
            $table = Str::snake(Str::plural($module));
            if ($skipTableTest || Schema::hasTable($table)) {
                $this->dataInfo('add AdCtr', $path);

                $template = "<?php\n\n"
                    . "namespace App\Http\Controllers\Admin;\n\n"
                    . "use Illuminate\Http\Request;\n"
                    . "use \Clevercherry\Backslash4\Controllers\\Admin\\{$bkController} as bkAdminController;\n"
                    . $use
                    . "\nclass {$model}Controller extends bkAdminController\n"
                    . "{\n\n"
                    . $content
                    . "\n\n}";

                File::ensureDirectoryExists(app_path('Http/Controllers/Admin/'));
                File::put($path, $template);
            }
        }
    }

    private function setupFaqs($thisTable = 'faqs')
    {
        $thisTable = Str::snake($thisTable);
        $this->info('Setting up ' . $thisTable);
        if (!Schema::hasTable($thisTable))
            Schema::create($thisTable, function (Blueprint $table) {
                $table->id();
                $table->String('name')->nullable();
                $table->String('slug')->nullable();
                $table->text('question');
                $table->text('answer');
                $table->String('category')->nullable();
                $table->json('associated_with')->nullable();
                $table->timestamp('date_published')->useCurrent();
                $table->String('seo_name')->nullable();
                $table->String('seo_description')->nullable();
                $table->integer('active')->default(1);
                $table->timestamps();
            });

        //example adding a field as a later upgrade -- also add it to the above table so it's created out of the drawer
        //because first version created faqs using the generic
        if (!Schema::hasColumn($thisTable, 'question'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->text('question')->after('slug');
            });
        if (!Schema::hasColumn($thisTable, 'answer'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->text('answer')->after('question');
            });
        if (!Schema::hasColumn($thisTable, 'category'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->text('category')->after('answer')->nullable();
            });
        if (!Schema::hasColumn($thisTable, 'associated_with'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->json('associated_with')->after('category')->nullable();
            });
        if (!Schema::hasColumn($thisTable, 'date_published'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->timestamp('date_published')->after('associated_with')->useCurrent();
            });
        if (!Schema::hasColumn($thisTable, 'seo_name'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->String('seo_name')->after('date_published')->nullable();
            });
        if (!Schema::hasColumn($thisTable, 'seo_description'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->String('seo_description')->after('seo_name')->nullable();
            });
        if (Schema::hasColumn($thisTable, 'seo-name'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->dropColumn('seo-name');
            });
        if (Schema::hasColumn($thisTable, 'seo-description'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->dropColumn('seo-description');
            });
    }
    private function setupOrders($thisTable = 'orders')
    {
        $thisTable = Str::snake($thisTable);
        if (!Schema::hasTable($thisTable)) {
            $this->info('Creating Table ' . $thisTable);
            Schema::create($thisTable, function (Blueprint $table) {
                $table->id();
                $table->String('order_id')->nullable();
                $table->bigInteger('account')->default(-1); //Customer Account / Guest Account
                $table->String('customer_name')->nullable();
                $table->String('email')->nullable();
                $table->json('shipping_address')->nullable();
                $table->json('billing_address')->nullable(); //Billing / Shipping Info
                $table->json('delivery_method')->nullable();
                $table->unsignedBigInteger('delivery_option')->nullable();
                $table->String('payment_method')->nullable(); //Transaction Details (payment method and total amount)
                $table->Text('payment_info')->nullable();
                $table->json('items')->nullable(); //Items Ordered
                $table->float('total_shipping', 16, 2)->default(0); //once again all prices should be in pennies to alleviate binary rounding recursion
                $table->float('total_nett', 16, 2)->default(0); //once again all prices should be in pennies to alleviate binary rounding recursion
                $table->float('total_vat', 16, 2)->default(0);
                $table->float('total_gross', 16, 2)->default(0);
                $table->text('notes')->nullable();
                $table->integer('status')->default(0);
                $table->integer('active')->default(1);
                $table->timestamps();
            });
        }

        if (!Schema::hasColumn($thisTable, 'delivery_method'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->json('delivery_method')->nullable()->after('billing_address');
            });
    }

    private function setupWishlists($thisTable = 'wishlists')
    {

        $thisTable = Str::snake($thisTable);
        if (!Schema::hasTable($thisTable)) {
            $this->info('Creating Table ' . $thisTable);
            Schema::create($thisTable, function (Blueprint $table) {
                $table->id();
                $table->bigInteger('contact')->nullable();
                $table->String('email')->nullable();
                $table->json('items')->nullable(); //Items in wishlist
                $table->float('total_shipping', 16, 2)->nullable();
                $table->float('total_nett', 16, 2)->nullable();
                $table->float('total_vat', 16, 2)->nullable();
                $table->float('total_gross', 16, 2)->nullable();
                $table->text('notes')->nullable();
                $table->integer('status')->default(0);
                $table->timestamps();
            });
        }
    }

    private function setupAddresses($thisTable = 'addresses')
    {
        $thisTable = Str::snake($thisTable);
        if (!Schema::hasTable($thisTable)) {
            $this->info('Creating Table ' . $thisTable);
            Schema::create($thisTable, function (Blueprint $table) {
                $table->id();
                $table->integer('type')->default(3);
                $table->unsignedBigInteger('client_id')->nullable();
                $table->String('address_shortname');
                $table->String('name')->nullable();
                $table->String('email')->nullable();
                $table->String('address_1')->nullable();
                $table->String('address_2')->nullable();
                $table->String('address_3')->nullable();
                $table->String('city')->nullable();
                $table->String('region')->nullable();
                $table->String('country')->nullable();
                $table->String('postcode')->nullable();
                $table->String('phone_1')->nullable();
                $table->String('phone_2')->nullable();
                $table->integer('active')->default(1);
                $table->timestamps();
            });
        }
        //example adding a field as a later upgrade -- also add it to the above table so it's created out of the drawer
        if (!Schema::hasColumn($thisTable, 'client_id'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->unsignedBigInteger('client_id')->after('type')->nullable();
            });
        if (!Schema::hasColumn($thisTable, 'address_shortname'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->String('address_shortname')->after('client_id');
            });
    }

    private function setupBlocks($thisTable = 'blocks')
    {
        $thisTable = Str::snake($thisTable);
        if (!Schema::hasTable($thisTable)) {
            $this->info('Creating Table ' . $thisTable);
            Schema::create($thisTable, function (Blueprint $table) {
                $table->id();
                $table->String('type')->default('text');
                $table->String('model');
                $table->bigInteger('modelId')->unsigned();
                $table->integer('order')->default(999999);
                $table->json('data')->nullable();
                $table->integer('active')->default(1);
                $table->timestamps();
            });
        }
    }

    private function setupAdmins($thisTable = 'admins')
    {
        $thisTable = Str::snake($thisTable);
        if (!Schema::hasTable($thisTable)) {
            $this->info('Creating Table ' . $thisTable);
            Schema::create($thisTable, function (Blueprint $table) {
                $table->id();
                $table->String('userName')->nullable()->unique();
                $table->String('email')->nullable()->unique();
                $table->String('password')->nullable();
                $table->String('firstName')->nullable();
                $table->String('lastName')->nullable();
                $table->String('level')->nullable();
                $table->json('security')->nullable();
                $table->integer('active')->default(1);
                $table->timestamps();
            });
        }

        //example adding a field as a later upgrade -- also add it to the above table so it's created out of the drawer
        if (!Schema::hasColumn($thisTable, 'level'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->integer('level')->after('lastName')->nullable();
            });

        if (!Schema::hasColumn($thisTable, 'security'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->integer('security')->after('level')->nullable();
            });

    }
    private function setupPages($thisTable = 'pages')
    {

        //$thisTable = config('database.connections.'.$tenantKey.'.database').'.'.Str::snake($thisTable);
        $thisTable = Str::snake($thisTable);
        if (!Schema::hasTable($thisTable)) {
            $this->info('Creating Table ' . $thisTable);
            Schema::create($thisTable, function (Blueprint $table) {
                $table->id();
                $table->String('name')->nullable();
                $table->String('slug')->nullable();
                $table->String('seo_name')->nullable();
                $table->String('seo_description')->nullable();
                $table->bigInteger('parent')->unsigned()->nullable();
                $table->integer('active')->default(1);
                $table->timestamps();
            });
        } else {
            $this->info('Table ' . $thisTable . ' already exitsts');
        }

        //example adding a field as a later upgrade -- also add it to the above table so it's created out of the drawer
        if (Schema::hasColumn($thisTable, 'title') && !Schema::hasColumn($thisTable, 'name')) {
            Schema::table($thisTable, function (Blueprint $table) {
                $table->renameColumn('title', 'name');
            });
        }
        if (!Schema::hasColumn($thisTable, 'active')) {
            Schema::table($thisTable, function (Blueprint $table) {
                $table->integer('active')->after('parent')->default(1);
            });
        }
        if (!Schema::hasColumn($thisTable, 'seo_name'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->String('seo_name')->after('slug')->nullable();
            });
        if (!Schema::hasColumn($thisTable, 'seo_description'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->String('seo_description')->after('seo_name')->nullable();
            });
        if (Schema::hasColumn($thisTable, 'seo-name'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->dropColumn('seo-name');
            });
        if (Schema::hasColumn($thisTable, 'seo-description'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->dropColumn('seo-description');
            });

        $page = Page::where('slug', '')->orWhere('slug', '/')->first();
        if ($page == null) {
            $page = new Page();
            $page->name = "home";
            $page->slug = '';
            $page->active = 1;
            $page->save();
        }
    }

    private function addIndexPage($name)
    {
        $page = Page::where('slug', Str::slug($name))->first();
        if ($page == null) {
            $page = new Page();
            $page->name = Str::headline($name);
            $page->slug = Str::slug($name);
            $page->active = 1;
            $page->save();
        }
    }
    private function setupEnquiries($thisTable = 'enquiries')
    {
        $thisTable = Str::snake($thisTable);
        if (!Schema::hasTable($thisTable)) {
            $this->info('Creating Table ' . $thisTable);
            Schema::create($thisTable, function (Blueprint $table) {
                $table->id();
                $table->String('first_name')->nullable();
                $table->String('last_name')->nullable();
                $table->String('email')->nullable();
                $table->String('phone')->nullable();
                $table->text('message')->nullable();
                $table->String('type')->nullable();
                $table->unsignedBigInteger('key')->nullable();
                $table->text('notes')->nullable();
                $table->integer('status')->default(0);
                $table->timestamps();
            });
        }
    }
    private function setupBlogs($thisTable) //aka News
    {
        $thisTable = Str::snake($thisTable);
        if (!Schema::hasTable($thisTable)) {
            $this->info('Creating Table ' . $thisTable);
            Schema::create($thisTable, function (Blueprint $table) {
                $table->id();
                $table->String('name')->nullable();
                $table->String('slug')->nullable();
                $table->String('author')->nullable();
                $table->String('media_thumbnail')->nullable();
                $table->Text('short_description')->nullable();
                $table->Text('description')->nullable();
                $table->timestamp('date_published')->useCurrent();
                $table->String('seo_name')->nullable();
                $table->String('seo_description')->nullable();
                $table->integer('active')->default(1);
                $table->timestamps();
            });
        }

        //example adding a field as a later upgrade -- also add it to the above table so it's created out of the drawer
        if (!Schema::hasColumn($thisTable, 'media_thumbnail'))
        Schema::table($thisTable, function (Blueprint $table) {
            $table->String('media_thumbnail')->after('author')->nullable();
        });
        if (!Schema::hasColumn($thisTable, 'short_description'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->Text('short_description')->after('media_thumbnail')->nullable();
            });
        if (!Schema::hasColumn($thisTable, 'description'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->Text('description')->after('short_description')->nullable();
            });
        if (Schema::hasColumn($thisTable, 'summary')) {
            Schema::table($thisTable, function (Blueprint $table) {
                $table->dropColumn('summary');
            });
        }
        if (!Schema::hasColumn($thisTable, 'author'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->String('author')->after('slug')->nullable();
            });
        if (!Schema::hasColumn($thisTable, 'date_published'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->timestamp('date_published')->after('description')->useCurrent();
            });

        //rename media-thumbnail to media_thumbnail
        if (!Schema::hasColumn($thisTable, 'media_thumbnail'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->String('media_thumbnail')->after('author')->nullable();
            });
        if (Schema::hasColumn($thisTable, 'media-thumbnail'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->dropColumn('media-thumbnail');
            });
        if (!Schema::hasColumn($thisTable, 'seo_name'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->String('seo_name')->after('date_published')->nullable();
            });
        if (!Schema::hasColumn($thisTable, 'seo_description'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->String('seo_description')->after('seo_name')->nullable();
            });
        if (Schema::hasColumn($thisTable, 'seo-name'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->dropColumn('seo-name');
            });
        if (Schema::hasColumn($thisTable, 'seo-description'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->dropColumn('seo-description');
            });
        $this->addIndexPage($thisTable);
    }
    private function setupProducts($thisTable)
    {
        $thisTable = Str::snake($thisTable);
        if (!Schema::hasTable($thisTable)) {
            $this->info('Creating Table ' . $thisTable);
            Schema::create($thisTable, function (Blueprint $table) {
                $table->id();
                $table->String('name')->nullable();
                $table->String('slug')->nullable();
                $table->text('description')->nullable();
                $table->String('sku')->nullable();
                $table->float('stock', 16, 2)->nullable();
                $table->String('uom')->nullable();
                $table->float('price', 16, 2)->nullable(); // prices should always be set in pennies, because of binary fraction rounding
                $table->float('price2', 16, 2)->nullable(); // prices should always be set in pennies, because of binary fraction rounding
                $table->float('vat_rate')->nullable();
                $table->json('media')->nullable();
                $table->json('downloads')->nullable();
                $table->json('related')->nullable();
                $table->String('seo_name')->nullable();
                $table->String('seo_description')->nullable();
                $table->integer('featured')->default(0);
                $table->integer('active')->default(1);
                $table->timestamps();
            });
        }

        if (Schema::hasTable('productCategories') && !Schema::hasColumn($thisTable, 'category'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->bigInteger('category')->unsigned()->after('description')->nullable();
            });
        if (Schema::hasTable('productTypes') && !Schema::hasColumn($thisTable, 'type'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->bigInteger('type')->unsigned()->after('description')->nullable();
            });

        //adding a field as a later upgrade -- also add it to the above table so it's created out of the drawer
        if (!Schema::hasColumn($thisTable, 'uom'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->String('uom')->after('stock')->nullable();
            });
        if (!Schema::hasColumn($thisTable, 'seo_name'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->String('seo_name')->after('related')->nullable();
            });
        if (!Schema::hasColumn($thisTable, 'seo_description'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->String('seo_description')->after('seo_name')->nullable();
            });

        $this->addIndexPage($thisTable);
    }
    private function setupProductCategories($thisTable)
    {
        $thisTable = Str::snake($thisTable);
        if (!Schema::hasTable($thisTable)) {
            $this->info('Creating Table ' . $thisTable);
            Schema::create($thisTable, function (Blueprint $table) {
                $table->id();
                $table->String('name')->nullable();
                $table->String('slug')->nullable();
                $table->text('description')->nullable();
                $table->String('seo_name')->nullable();
                $table->String('seo_description')->nullable();
                $table->integer('active')->default(1);
                $table->timestamps();
            });
        }

        if (!Schema::hasColumn($thisTable, 'seo_name'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->String('seo_name')->after('description')->nullable();
            });
        if (!Schema::hasColumn($thisTable, 'seo_description'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->String('seo_description')->after('seo_name')->nullable();
            });

        if (Schema::hasTable('products') && !Schema::hasColumn('products', 'category'))
            Schema::table('products', function (Blueprint $table) {
                $table->bigInteger('category')->unsigned()->after('description')->nullable();
            });

        $this->addIndexPage($thisTable);
    }
    private function setupProductTypes($thisTable)
    {
        $thisTable = Str::snake($thisTable);
        if (!Schema::hasTable($thisTable)) {
            $this->info('Creating Table ' . $thisTable);
            Schema::create($thisTable, function (Blueprint $table) {
                $table->id();
                $table->String('name')->nullable();
                $table->String('slug')->nullable();
                $table->text('description')->nullable();
                $table->String('seo_name')->nullable();
                $table->String('seo_description')->nullable();
                $table->integer('active')->default(1);
                $table->timestamps();
            });
        }

        if (!Schema::hasColumn($thisTable, 'seo_name'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->String('seo_name')->after('description')->nullable();
            });
        if (!Schema::hasColumn($thisTable, 'seo_description'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->String('seo_description')->after('seo_name')->nullable();
            });

        if (Schema::hasTable('products') && !Schema::hasColumn('products', 'type'))
            Schema::table('products', function (Blueprint $table) {
                $table->bigInteger('type')->unsigned()->after('description')->nullable();
            });

        $this->addIndexPage($thisTable);

    }
    private function setupMediaManager($thisTable = "media_managers")
    {
        $thisTable = Str::snake(Str::Plural($thisTable));
        if (!Schema::hasTable($thisTable)) {
            $this->info('Creating Table ' . $thisTable);
            Schema::create($thisTable, function (Blueprint $table) {
                $table->id();
                $table->String('original')->nullable();
                $table->String('optimised')->nullable();
                $table->String('thumbnail')->nullable();
                $table->String('media_type')->nullable(); //this could also be 'youtube'
                $table->integer('opt_width')->nullable();
                $table->integer('opt_height')->nullable();
                $table->json('fields')->nullable(); //misc info
                $table->integer('active')->default(1);
                $table->timestamps();
            });
        }

        if (!Schema::hasColumn($thisTable, 'opt_width'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->integer('opt_width')->after('media_type')->nullable();
            });
        if (!Schema::hasColumn($thisTable, 'opt_height'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->integer('opt_height')->after('opt_width')->nullable();
            });

        self::addDefaultMediaManagerSeed();
    }
    private function addDefaultMediaManagerSeed()
    {
        File::ensureDirectoryExists(storage_path("app/public/images/defaults"));

        $imgs = ['placeholder', '1920x1080', '640x360', '800x450'];
        foreach ($imgs as $img) {
            File::copy(Backslash4ServiceProvider::packagePath() . "/public/img/{$img}.png", storage_path("app/public/images/defaults/{$img}.png"));
            File::copy(Backslash4ServiceProvider::packagePath() . "/public/img/{$img}.opt.webp", storage_path("app/public/images/defaults/{$img}.opt.webp"));
            File::copy(Backslash4ServiceProvider::packagePath() . "/public/img/{$img}.thumb.webp", storage_path("app/public/images/defaults/{$img}.thumb.webp"));

            $image = MediaManager::where('original', 'images/defaults/placeholder.png')->first();
            if (empty($image)) {
                $image = new MediaManager();
                $image->original = "images/defaults/placeholder.png";
                $image->optimised = "images/defaults/placeholder.opt.webp";
                $image->thumbnail = "images/defaults/placeholder.thumb.webp";
                $image->active = 1;
                $image->save();
            }
        }
    }
    private function setupUsers($thisTable = 'users')
    {
        $thisTable = Str::snake($thisTable);
        //Users table setup as part of Laravel Migrations
        if ($thisTable == 'Users') //laravel table is users lowercase
            $thisTable = 'users';

        if (!Schema::hasColumn($thisTable, 'firstName'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->string('firstName')->after('email_verified_at')->nullable();
            });
        if (!Schema::hasColumn($thisTable, 'lastName'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->string('lastName')->after('firstName')->nullable();
            });
        if (!Schema::hasColumn($thisTable, 'active'))
            Schema::table($thisTable, function (Blueprint $table) {
                $this->info('910');
                $table->integer('active')->after('remember_token')->default(1);
            });
    }
    private function setupClients($thisTable)
    {
        $thisTable = Str::snake($thisTable);
        if (!Schema::hasTable($thisTable)) {
            $this->info('Creating Table ' . $thisTable);
            Schema::create($thisTable, function (Blueprint $table) {
                $table->id();
                $table->String('user_name')->nullable();
                $table->String('email')->nullable();
                $table->String('first_name')->nullable();
                $table->String('last_name')->nullable();
                $table->String('password')->nullable();
                $table->String('level')->nullable();
                $table->json('security')->nullable();
                $table->integer('active')->default(0);
                $table->timestamps();
            });
        }
    }
    private function setupSettings($thisTable = 'settings')
    {

        $thisTable = Str::snake($thisTable);
        if (!Schema::hasTable($thisTable)) {
            $this->info('Creating Table ' . $thisTable);
            Schema::create($thisTable, function (Blueprint $table) {
                $table->id();
                $table->String('title')->nullable();
                $table->String('slug')->nullable();
                $table->String('description')->nullable();
                $table->String('group')->nullable();
                $table->String('input')->nullable();
                $table->text('value')->nullable();
                $table->integer('order')->nullable();
                $table->String('default')->nullable();
                $table->boolean('required')->default(true);
                $table->json('options')->nullable();
                $table->timestamps();
            });
        }

        if (!Schema::hasColumn($thisTable, 'value'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->text('value')->after('input')->nullable();
            });
        if (!Schema::hasColumn($thisTable, 'slug'))
            Schema::table($thisTable, function (Blueprint $table) {
                $table->String('slug')->after('title')->nullable();
            });

        $setting = Setting::where('slug', '')->first();
        $this->addSetting('Instagram Client ID', 'instagram_client_id', 'Your Instagram ID', 'social_media_apis', 'text', '', 1810, '', 0);
        $this->addSetting('Instagram Secret', 'instagram_secret', 'Your Instagram Secret', 'social_media_apis', 'text', '', 1820, '', 0);
        $this->addSetting('Link/Add/Renew Instagram Account', 'instagram_renew_link', '', 'social_media_apis', 'link-route:readonly', 'admin.apis.instagram.authentication', 1830, 'Connect Instagram', 0);
    }

    private function addSetting($title, $slug, $desc, $group, $input, $value, $order = 9900, $default = null, $required = 1, $options = null)
    {
        $getSetting = Setting::where('slug', $slug)->first();
        if ($getSetting == null) {
            $setting = new Setting();
            $setting->title = $title;
            $setting->slug = $slug;
            $setting->description = $desc;
            $setting->group = $group;
            $setting->input = $input;
            $setting->value = $value;
            $setting->order = $order;
            $setting->default = $default;
            $setting->required = $required;
            $setting->options = $options;
            $setting->save();
        }
    }
    
    private function updateSetting( $title = null, $slug = null, $desc = null, $group = null, 
                                    $input = null, $value = null, $order = null, 
                                    $default = null, $required = null, $options = null)
    {
        if (empty($slug)) {
            return ;
        }

        $setting = Setting::where('slug', $slug)->first();
        if ($setting == null) {
            $setting = new Setting();
        }
        if ($title !== null)
            $setting->title = $title;

        $setting->slug = $slug;

        if ($desc !== null)
            $setting->description = $desc;

        if ($group !== null)
            $setting->group = $group;

        if ($input !== null)
            $setting->input = $input;

        if ($value !== null)
            $setting->value = $value;

        if ($order !== null)
            $setting->order = $order;

        if ($default !== null)
            $setting->default = $default;

        if ($required !== null)
            $setting->required = $required;

        if ($options !== null)
            $setting->options = $options;

        $setting->save();
    }

    private function addMenuModule($moduleName, $tenantKey = null)
    {
        $changed = false;
        $item = $this->getModules()[$moduleName];
        if ($item[1] > 0) {
            if (empty($tenantKey))
                $config = $this->getConfig();
            else
                $config = $this->getTenantConfig($tenantKey); //$config = $this->getTenantConfig(); //$this->getConfig();

            $cmsMenu = Backslash4::getDashboardFlatCMS($config);

            if (!isset($cmsMenu[$moduleName])) {
                $config['cmsMenu'][$moduleName] = [
                    'name' => Str::headline($moduleName),
                    'slug' => $item[0],
                    'index' => $item[1],
                    'icon' => $item[2],
                ];
                if ($moduleName != "Settings") {
                    $config['cmsMenu'][$moduleName]['subMenu'] = [
                        [
                            'slug' => 'create',
                            'text' => 'Add New ' . ucwords(Str::singular($moduleName))
                        ],
                        'name' => Str::headline($moduleName)
                    ];

                }
                $changed = true;

            } else if (!isset($cmsMenu[$moduleName]['name'])) {
                $config['cmsMenu'][$moduleName]['name'] = Str::headline($moduleName);
                $changed = true;
            }

            if ($changed) {
                if (empty($tenantKey))
                    $this->saveConfig($config);
                else
                    $this->saveTenantConfig($config, $tenantKey);
            }
        }
    }
    private function setupSCSS()
    {
        File::ensureDirectoryExists(resource_path("scss"));

        $file = resource_path("scss/admin.scss");
        if (!file_exists($file)) {
            $scss = "@import '../../vendor/clevercherry/backslash4/resources/scss/admin';";
            File::put($file, trim($scss) . "\n");
        }

        $file = resource_path("scss/app.scss");
        if (!file_exists($file)) {
            $scss = "@use 'app/index' as app;";
            File::put($file, trim($scss) . "\n");
        }

        File::ensureDirectoryExists(resource_path("scss/app/"));

        $fm1 = resource_path("scss/app/index.scss");
        if (!file_exists($fm1))
            File::put($fm1, "\n");

        // add to vite 
        $file = base_path('vite.config.js');
        if (!file_exists($file)) {
            $js = "import { defineConfig } from 'vite';\n"
                . "import laravel from 'laravel-vite-plugin';\n"
                . "export default defineConfig({\n"
                . "    plugins: [\n"
                . "    laravel({\n"
                . "            input: ['resources/css/app.css', 'resources/js/app.js', 'resources/scss/app.scss', 'resources/scss/admin.scss'],\n"
                . "            refresh: true,\n"
                . "        }),\n"
                . "    ],\n"
                . "});\n";
            File::put($file, $js);
        } else { //check entries exist
            $lines = ['resources/scss/app.scss', 'resources/scss/admin.scss'];
            $js = trim(file_get_contents($file));
            $changed = false;

            foreach ($lines as $l)
                if (!str_contains($js, "'" . $l . "'")) {
                    $js = str_replace('input: [', "input: ['{$l}',", $js);
                    $changed = true;
                }
            if ($changed)
                File::put($file, $js);
        }
    }
    private function setupJS()
    {
        //add our JS
        $lines = [
            "import './app/app';"
        ];
        $file = resource_path("js/app.js");
        if (!file_exists($file)) {
            $js = "//import './bootstrap';";
            foreach ($lines as $l)
                $js = trim($js) . "\n" . $l . "\n";
        } else {
            $js = trim(file_get_contents($file));

            $line = "import './bootstrap';";
            if (str_contains($js, $line)) {
                $js = str_replace($line, '//' . $line, $js);
                if (Str::startswith($js, '////'))
                    $js = substr($js, 2);
                if (Str::startswith($js, '// //'))
                    $js = substr($js, 3);
            }

            foreach ($lines as $l)
                if (!str_contains($js, $l))
                    $js = trim($js) . "\n" . $l . "\n";

            $js = str_replace("\n\n", "\n", $js);
        }
        File::put($file, trim($js) . "\n");

        File::ensureDirectoryExists(resource_path("js/app/"));

        $fapp = resource_path("js/app/app.js");
        $this->datainfo('test', $fapp);
        $this->datainfo('fileExists', json_encode(file_exists($fapp)));
        //$this->datainfo('is_file', json_encode(is_file($fapp)) );
        if (!file_exists($fapp)) //fileExists was returning bad data
        {
            $this->datainfo('wr', $fapp);
            $js = "//import '../../vendor/clevercherry/backslash4/resources/js/aos\n"
                . "//import '../../vendor/clevercherry/backslash4/resources/js/fancybox\n"
                . "//import '../../vendor/clevercherry/backslash4/resources/js/js-cookies\n"
                . "//import '../../vendor/clevercherry/backslash4/resources/js/swiper\n"
                . "//import '../../vendor/clevercherry/backslash4/resources/js/uikit\n"
                . "//import '../../vendor/clevercherry/backslash4/resources/js/teithan\n"
                . "\n"
                . "import './module1';\n"
                . "import './module2';\n"
                . "import './module3';\n\n";
            File::put($fapp, $js);
        }

        $fm1 = resource_path("js/app/module1.js");
        if (!file_exists($fm1))
            File::put($fm1, "\n");

        $fm2 = resource_path("js/app/module2.js");
        if (!file_exists($fm2))
            File::put($fm2, "\n");

        $fm3 = resource_path("js/app/module3.js");
        if (!file_exists($fm3))
            File::put($fm3, "\n");

        //-- admin js -------------------
        $lines = [
            "import '../../vendor/clevercherry/backslash4/resources/js/admin';",
            "import './admin/admin';"
        ];

        $file = resource_path("js/admin.js");
        if (!file_exists($file)) {
            $js = "";
            foreach ($lines as $l)
                $js = trim($js) . "\n" . $l . "\n";
        } else {
            $js = trim(file_get_contents($file));

            foreach ($lines as $l)
                if (!str_contains($js, $l))
                    $js = trim($js) . "\n" . $l . "\n";

            $js = str_replace("\n\n", "\n", $js);
        }
        File::put($file, trim($js) . "\n");

        File::ensureDirectoryExists(resource_path("js/admin/"));

        $fapp = resource_path("js/admin/admin.js");
        $this->datainfo('test', $fapp);
        $this->datainfo('fileExists', json_encode(file_exists($fapp)));
        //$this->datainfo('is_file', json_encode(is_file($fapp)) );
        if (!file_exists($fapp)) //fileExists was returning bad data
        {
            $this->datainfo('wr', $fapp);
            $js = "import './module1';\n"
                . "import './module2';\n"
                . "import './module3';\n\n";
            File::put($fapp, $js);
        }

        $fm1 = resource_path("js/admin/module1.js");
        if (!file_exists($fm1))
            File::put($fm1, "\n");

        $fm2 = resource_path("js/admin/module2.js");
        if (!file_exists($fm2))
            File::put($fm2, "\n");

        $fm3 = resource_path("js/admin/module3.js");
        if (!file_exists($fm3))
            File::put($fm3, "\n");

        // add admin.js to vite 
        $file = base_path('vite.config.js');
        $this->dataInfo('vite', $file);
        if (!file_exists($file)) {
            $js = "import { defineConfig } from 'vite';\n"
                . "import laravel from 'laravel-vite-plugin';\n"
                . "export default defineConfig({\n"
                . "    plugins: [\n"
                . "    laravel({\n"
                . "            input: ['resources/css/app.css', 'resources/js/app.js', 'resources/js/admin.js'],\n"
                . "            refresh: true,\n"
                . "        }),\n"
                . "    ],\n"
                . "});\n";
            File::put($file, $js);
        } else { //check entries exist
            $this->dataInfo('upgrading vite', '');
            $lines = ['resources/js/admin.js', 'resources/js/app.js'];
            $js = trim(file_get_contents($file));
            $changed = false;

            foreach ($lines as $l)
                if (!str_contains($js, "'" . $l . "'")) {
                    $this->dataInfo('vite adding', $l);

                    $js = str_replace('input: [', "input: ['{$l}',", $js);
                    $changed = true;
                }
            if ($changed)
                File::put($file, $js);
        }
    }

    private function upgradeConfig($new = false, $tenantKey = null)
    {
        //this allows dynamic upgrades of the config file
        $config = $this->getTenantConfig($tenantKey); //getConfig();
        $cmsMenu = Backslash4::getDashboardFlatCMS($config);
        //dump('a',$config); //definitely an array

        $changed = false;

        // check setting exist and if not create them
        // we can also do changes here
        if (isset($config['media'])) {
            $config['media'] = [];
            $changed = true;
        }
        if (!isset($config['adminPath'])) {
            $config['adminPath'] = 'admin';
            $changed = true;
        }
        if (!isset($config['show-only-app-blocks'])) {
            $config['show-only-app-blocks'] = 1;
            $changed = true;
        }
        $this->addAdminController('Dashboard');

        if (!isset($config['cmsMenu'])) {
            $config['cmsMenu'] = [];
            $changed = true;
        }
        if (!isset($config['cmsMenu']['Dashboard'])) {
            $config['cmsMenu']['Dashboard'] = [
                'slug' => '/',
                'no-model' => 1,
                'icon' => 'home'
            ];
            $changed = true;
        }
        //add the no-model parameter
        if (!isset($config['cmsMenu']['Dashboard']['no-model'])) {
            $config['cmsMenu']['Dashboard']['no-model'] = 1;
            $changed = true;
        }
        if (!isset($cmsMenu['Admins'])) {
            $config['cmsMenu']['Admins'] = [
                'slug' => 'admins',
                'icon' => 'users',
                'subMenu' => [
                    ['slug' => 'create', 'text' => 'Add new admin']
                ],
            ];
            $changed = true;
        }
        if (!isset($cmsMenu['Pages'])) {
            $config['cmsMenu']['Pages'] = [
                'slug' => 'pages',
                'icon' => 'page',
                'subMenu' => [
                    ['slug' => 'create', 'text' => 'Add new page']
                ],
            ];
            $changed = true;
        }
        if (!isset($cmsMenu['MediaManager'])) {
            $config['cmsMenu']['MediaManager'] = [
                'slug' => 'mediamanager',
                'icon' => 'upload',
                'subMenu' => [
                    ['slug' => 'create', 'text' => 'Add new media']
                ],
            ];
            $changed = true;
        }
        if (!isset($cmsMenu['Settings'])) {
            $config['cmsMenu']['Settings'] = [
                'slug' => 'settings',
                'icon' => 'page',
                'subMenu' => [],
            ];
            $changed = true;
        }
        //remove the unused 'index parameter
        foreach ($config['cmsMenu'] as $key => $info) {
            if (isset($info['index'])) {
                unset($config['cmsMenu'][$key]['index']);
                $changed = true;
            }
        }

        // after all changes write them back to the file if necessary
        if ($changed) {
            if ($new)
                $this->pushMsg('Inserted std values into new config');
            else
                $this->pushMsg('Inserted new values into existing config');

            $this->saveTenantConfig($config, $tenantKey);
        } else {
            if ($new)
                $this->pushMsg('New config already up-to-date');
            else
                $this->pushMsg('Existing config already up-to-date');

        }
    }

    /**
     * ----------------------------------------------------------------------
     *  Uninstall Base Backslash 4 functions (does not uninstall tenants)
     * ----------------------------------------------------------------------
     */

    private function deleteConfig()
    {
        if (file_exists(config_path('backslash4.php'))) {
            $this->newLine();
            $this->info('Delete the backslash4 config from your application...');
            $this->info('-- This cannot be undone');
            if ($this->confirm('Do you wish to continue?')) {
                File::delete(config_path('backslash4.php'));

            } else {
                $this->pushMsg('The backslash4 config deletion was cancelled.');
            }
        } else {
            $this->pushMsg('The backslash4 config does not exist in your application.');
        }
    }

    private function deleteJS()
    {
        $file = resource_path("js/app.js");
        if (!file_exists($file)) {
            $js = "import './bootstrap';";
        } else {
            $js = file_get_contents($file);
            $js = str_replace("//import './bootstrap';", "import './bootstrap';", $js);
            $js = str_replace("// import './bootstrap';", "import './bootstrap';", $js);
            $js = str_replace("import '../../vendor/clevercherry/backslash4/resources/js/admin';", "", $js);
            $js = str_replace("import './app/app';", "", $js);
            if (!str_contains($js, "import './bootstrap';"))
                $js = "import './bootstrap';\n" . $js;

            $js = str_replace("\n\n", "\n", $js);
        }
        File::put($file, trim($js) . "\n");

        if (File::exists(resource_path("js/app/"))) {
            File::delete(resource_path("js/app/app.js"));
            File::delete(resource_path("js/app/module1.js"));
            File::delete(resource_path("js/app/module2.js"));
            File::delete(resource_path("js/app/module3.js"));

            //delete if folder is empty, contains no user added files
            if (empty(File::allFiles(resource_path("js/app/"), true)))
                File::deleteDirectory(resource_path("js/app/"));
        }

        if (File::exists(resource_path("js/admin/"))) {
            File::delete(resource_path("js/admin/app.js"));
            File::delete(resource_path("js/admin/admin.js"));
            File::delete(resource_path("js/admin/module1.js"));
            File::delete(resource_path("js/admin/module2.js"));
            File::delete(resource_path("js/admin/module3.js"));

            //delete if folder is empty, contains no user added files
            if (empty(File::allFiles(resource_path("js/admin/"), true)))
                File::deleteDirectory(resource_path("js/admin/"));
        }
        File::delete(resource_path("js/admin.js"));

        $file = base_path('vite.config.js');
        if (!file_exists($file)) {
            $js = "import { defineConfig } from 'vite';\n"
                . "import laravel from 'laravel-vite-plugin';\n"
                . "export default defineConfig({\n"
                . "    plugins: [\n"
                . "    laravel({\n"
                . "            input: ['resources/css/app.css', 'resources/js/app.js' ],\n"
                . "            refresh: true,\n"
                . "        }),\n"
                . "    ],\n"
                . "});\n";
            File::put($file, $js);
        } else { //check entries do exist
            $lines = ['resources/js/app.js', 'resources/js/admin.js'];
            $js = trim(file_get_contents($file));
            $changed = false;

            foreach ($lines as $l)
                if (str_contains($js, "'" . $l . "'")) {
                    $js = str_replace("'{$l}',", '', $js);
                    $changed = true;
                }
            if ($changed)
                File::put($file, trim($js) . "\n"); //exactly as before
        }
    }

    private function deleteSCSS()
    {
        if (File::exists(resource_path("scss/app/"))) {
            File::delete(resource_path("scss/app/app.scss"));
            File::delete(resource_path("scss/app/index.scss"));
            File::delete(resource_path("scss/app/module1.scss"));
            File::delete(resource_path("scss/app/module2.scss"));
            File::delete(resource_path("scss/app/module3.scss"));

            //delete if folder is empty, contains no user added files
            if (empty(File::allFiles(resource_path("scss/app/"), true)))
                File::deleteDirectory(resource_path("scss/app/"));
        }
        File::delete(resource_path("scss/app.scss"));
        File::delete(resource_path("scss/admin.scss"));

        if (File::exists(resource_path("scss/")) && empty(File::allFiles(resource_path("scss/"), true)))
            File::deleteDirectory(resource_path("scss/"));

        $file = base_path('vite.config.js');
        if (!file_exists($file)) {
            $js = "import { defineConfig } from 'vite';\n"
                . "import laravel from 'laravel-vite-plugin';\n"
                . "export default defineConfig({\n"
                . "    plugins: [\n"
                . "    laravel({\n"
                . "            input: ['resources/css/app.css', 'resources/js/app.js' ],\n"
                . "            refresh: true,\n"
                . "        }),\n"
                . "    ],\n"
                . "});\n";
            File::put($file, $js);
        } else { //check entries do exist
            $lines = ['resources/scss/app.scss', 'resources/scss/admin.scss'];
            $js = trim(file_get_contents($file));
            $changed = false;

            foreach ($lines as $l)
                if (str_contains($js, "'" . $l . "'")) {
                    $js = str_replace("'{$l}',", '', $js);
                    $changed = true;
                }
            if ($changed)
                File::put($file, trim($js) . "\n");
        }
    }

    private function deleteFiles($ask = true): void
    {
        $this->newLine();
        $this->info('Delete All of the backslash4 files from your application...');
        if ($ask)
            $this->info('-- This cannot be undone');
        if (!$ask || $this->confirm('Do you wish to continue?')) {
            if ($ask) {
                $this->newLine();
                $this->info('You probably want to backup the project before you delete stuff...');
            }
            if (!$ask || $this->confirm('Do You Want to Make a ZIP backup of the backslash4 files first?', true)) {
                $zip = $this->zipBackup();
                if ($zip === false) {
                    $this->pushMsg('Could not create the zip archive, delete process was cancelled.');
                    return;
                }
                //else
                $this->pushMsg($zip);
            }

            File::delete(config_path('backslash4.php'));

            //remove JS
            $this->deleteJS();

            //remove SCSS
            $this->deleteSCSS();

            //modules
            File::delete(app_path("Http/Controllers/Admin/DashboardController.php"));
            File::delete(app_path("Http/Controllers/Admin/AdminController.php"));
            File::delete(app_path("Models/Admin.php"));
            File::delete(app_path("Models/Block.php"));

            foreach ($this->getModules() as $key => $item) {
                $singular = Str::studly(Str::singular($key));

                if ($singular != 'User')
                    File::delete(app_path("Models/{$singular}.php"));

                File::delete(app_path("Http/Controllers/{$singular}Controller.php"));
                File::delete(app_path("Http/Controllers/Admin/{$singular}Controller.php"));
            }

            if (File::exists(app_path("Http/Controllers/Admin/")) && empty(File::allFiles(app_path("Http/Controllers/Admin/"), true)))
                File::deleteDirectory(app_path("Http/Controllers/Admin/"));

        }
    }
    private function deleteTables($ask = true): void
    {
        $this->info('Delete the backslash4 Database tables...');
        if ($ask)
            $this->info('-- This cannot be undone');
        if (!$ask || $this->confirm('Do you wish to continue?')) {
            if ($ask) {
                $this->newLine();
                $this->info('You probably want to backup the database before you delete the tables...');
            }
            if (!$ask || $this->confirm('Do You Want to Make a ZIP backup of the database first?', true)) {
                $msgs = $this->backupDatabase();
                foreach ($msgs as $msg)
                    $this->info($msg);
            }foreach ($this->getModules() as $key => $item) {
                if ($key != 'Users' && Schema::hasTable(Str::snake($key)))
                    Schema::drop(Str::snake($key));

                //remove tables for beta versions
                if ($key != 'Users' && Schema::hasTable($key))
                    Schema::drop($key);

            }

            //remove tables for beta versions
            if (Schema::hasTable('Blocks'))
                Schema::drop('Blocks');

            if (Schema::hasTable('blocks'))
                Schema::drop('blocks');

            if (Schema::hasTable('media_managers'))
                Schema::drop('media_managers');

            if (Schema::hasTable('users'))
                $this->removeUsers('users');
        }
    }

    private function deleteAll($ask = true): void
    {
        $this->deleteFiles($ask);
        $this->deleteTables($ask);
    }

    private function removeModules(): void
    {
        $arr = ['-cancel-'];
        foreach ($this->getModules() as $key => $item) {
            if ($item[3] == 0)
                array_push($arr, $key);
        }
        $name = $this->choice('Remove which module?', $arr, 0, 1, false);
        $this->pushMsg($name);
        if ($name != '-cancel-') {
            if ($this->confirm('Do You Want to remove the module, ' . $name . '?')) {
                $this->newLine();
                $this->info('You probably want to backup the project before you delete stuff...' . $name);

                if ($this->confirm('Do You Want to Make a ZIP backup of the backslash4 files and database first?', true)) {
                    $zip = $this->zipBackup();
                    if ($zip === false) {
                        $this->info('Could not create the zip archive, delete process was cancelled.');
                        return;
                    }
                    //else
                    $this->info($zip);
                    $msgs = $this->backupDatabase();
                    foreach ($msgs as $msg)
                        $this->info($msg);
                }

                $config = $this->getTenantConfig(); //getConfig();
                $cmsMenu = Backslash4::getDashboardFlatCMS($config);
                if (isset($cmsMenu[$name])) {
                    unset($config['cmsMenu'][$name]);
                    $this->saveConfig($config);
                }

                if (method_exists(self::class, "remove" . $name))
                    $this->{"remove" . $name}($name);
                else
                    Schema::drop($name);

                $singular = Str::singular($name);

                if ($singular != 'User')
                    File::delete(app_path("Models/{$singular}.php"));

                File::delete(app_path("Http/Controllers/{$singular}Controller.php"));
                File::delete(app_path("Http/Controllers/Admin/{$singular}Controller.php"));

                $this->pushMsg('Module, ' . $name . ' removed.');
            }
        }
    }

    private function removeAdmins($thisTable = 'Admins'): void
    {
        if ($thisTable == null)
            $thisTable = 'Admins';

        DB::table($thisTable)->truncate();

        $this->pushMsg('Admins Removed');
    }

    private function removeUsers($thisTable = 'users'): void
    {
        if ($thisTable == null)
            $thisTable = 'users';

        DB::table($thisTable)->truncate();
        Schema::table($thisTable, function (Blueprint $table) {
            $table->removeColumn('firstName');
            $table->removeColumn('lastName');
            $table->removeColumn('active');
        });
        $this->pushMsg('Users Removed and Table Reset');
    }



    /**
     * ----------------------------------------------------------------------
     *  File Manipulation functions
     * ----------------------------------------------------------------------
     */

    private function fileInsertBefore($filepath, $newItem, $before): bool
    {
        $content = file_get_contents($filepath);
        if (!Str::contains($content, $newItem)) {
            $content = Str::replaceFirst($before, $newItem . $before, $content);

            file_put_contents($filepath, $content);
            return true;
        }
        return false;
    }

    private function fileInsertAfter($filepath, $newItem, $after, $after2 = null): bool
    {
        $content = file_get_contents($filepath);
        if (!Str::contains($content, $newItem)) {
            if ($after2 == null) {
                $content = Str::replaceFirst($after, $after . $newItem, $content);
            } else {
                $pos = strpos($content, $after) + strlen($after);
                $s1 = substr($content, 0, $pos);
                $s2 = substr($content, $pos);
                $s2 = Str::replaceFirst($after2, $after2 . $newItem, $s2);

                $content = $s1 . $s2;
            }
            file_put_contents($filepath, $content);
            return true;
        }
        return false;
    }

    private function writeEnvFile($arrayValues = [])
    {
        //$envfile = file_get_contents(base_path('.env'));
        $env = [];
        $handle = fopen(base_path('.env'), "r");
        if ($handle) {
            while (($line = fgets($handle)) !== false) {
                $env[] = $line;
            }
            fclose($handle);
        }

        $lastkey = null;
        $lastEKey = null;
        foreach ($arrayValues as $key => $value) {
            $found = false;
            foreach ($env as $eKey => $line) {
                if (Str::startsWith($line, [$key . '=', $key . ' ='])) {
                    $found = true;
                    $lastEKey = $eKey;
                    $env[$eKey] = $key . '=' . $value . "\n";
                    break;
                }
            }
            if (!$found) {
                if ($lastEKey == null)
                    $env[] = $key . '=' . $value . "\n";
                else { //echo ">".$lastEKey." ".$key.'='.$value."\n";
                    array_splice($env, $lastEKey, 0, $key . '=' . $value . "\n");
                }
            }
        }

        $handle = fopen(base_path('.env'), "w");
        if ($handle) {
            foreach ($env as $eKey => $line) {
                fputs($handle, $line);
            }

            fclose($handle);
        }
    }

    /**
     * Update the ENV and filesystems.php file to work with Digital Ocean S3
     */
    private function s3Config()
    {
        if ($this->ask("Do You want to add the default Backslash Digital Ocean Credentials to the .ENV file?", false)) {
            $this->writeEnvFile([
                'AWS_ACCESS_KEY_ID' => 'DO00E8TPAARDVE3KNKDZ',
                'AWS_SECRET_ACCESS_KEY' => 'p11m9oBZWM6BynpaKYN+NDF/jXmpr/gt+1PB3JcgUNE',
                'AWS_DEFAULT_REGION' => 'us-east-1',
                'AWS_ENDPOINT' => 'https://fra1.digitaloceanspaces.com',
                'AWS_BUCKET' => 'backslash',
                'AWS_URL' => 'https://backslash.fra1.digitaloceanspaces.com',
                'MEDIASYSTEM_DISK' => 's3',
            ]);

            /* we want to leave the config intact with ENV variables
            so we're NOT going to read and write the actual file as data using config
            */

            //update the filesystems.php config with the extra setting
            // $filesConfig = file_get_contents(config_path('filesystems.php'));
            // if(!Str::contains($filesConfig,"'visibility' => env('AWS_VISIBILITY')," ))
            // {
            //     $filesConfig = Str::replaceFirst("'use_path_style_endpoint' =>", 
            //         "'visibility' => env('AWS_VISIBILITY'),\n            'use_path_style_endpoint' =>",
            //         $filesConfig);

            //     file_put_contents(config_path('filesystems.php'), $filesConfig);
            // }
        }
    }


    /**
     * ----------------------------------------------------------------------
     *  Tennants
     * ----------------------------------------------------------------------
     */

    /**
     * Add composer package requirements, register services and create necessary classes
     */
    private function setupTenants()
    {
        $answer = strtolower($this->askNotEmpty("Adding Tenants is irreversible automatically,\n  Are you sure? (y)"));
        if (in_array($answer, $this->yesArray)) {
            $this->updateTenants(true);

            $this->info(' ');
            $this->info('The database user in the .env file must be able to GRANT privileges');
            $this->info('ensure that this is done with a command such as ...');
            $this->info('> GRANT ALL PRIVILEGES ON *.* TO ' . env('DB_USERNAME') . '@' . env('DB_HOST') . ' WITH GRANT OPTION;');
            $this->info(' ');
        }

    }
    /**
     * Update Tenants include creating things
     * @return void
     */
    private function updateTenants($noask = false): void
    {
        if (!$noask)
            $answer = strtolower($this->askNotEmpty("Updating Tenants irreversible automatically,\n  Are you sure? (y/n)", 'no'));
        if ($noask || in_array($answer, $this->yesArray)) {
            $this->setConfigBaseValue('tenants-on', 1);
            $this->setConfigBaseValue('tenants-aliases', []);
            if (config('backslash4.tenants-db-prefix', null) === null)
                $this->setConfigBaseValue('tenants-db-prefix', '');
            if (config('backslash4.tenants-path') == null)
                $this->setConfigBaseValue('tenants-path', 'storage/app/tenants');
            File::ensureDirectoryExists(base_path(config('backslash4.tenants-path')));

            if (empty(config('backslash4.tenants', null))) { //no tenants
                $this->addTenant();
            }
        }
    }

    private function reLinkTenants()
    {
        foreach (config('backslash4.tenants') as $key => $tenantInfo) {
            File::ensureDirectoryExists(Backslash4::tenant_path($tenantInfo['paths']['build-path'], $tenantInfo));
            File::ensureDirectoryExists(public_path('builds'));

            $link = public_path($tenantInfo['paths']['public-build-path']);
            $target = Backslash4::tenant_path($tenantInfo['paths']['build-path'], $tenantInfo);

            if (file_exists($link) && !is_link($link)) {
                $this->error("The [$link] folder/link already exists.");
                return;
            }
            //else
            if (is_link($link)) {
                $this->laravel->make('files')->delete($link);
            }
            $this->laravel->make('files')->link($target, $link);
            $this->info("$key : The [$link] link has been connected to [$target].");
        }

    }

    /**
     * Create a new tanant and all the base files needed, if tenant already exists will double check
     * @return void
     */
    private function addTenant(): void
    {
        if (empty(config('backslash4.tenants-on', null))) {
            $this->info('Tenancy is not turned on, setup Tenants first');
            return;
        }

        $url = '';
        while (empty($url)) {
            $url = trim(strtolower($this->askNotEmpty("Enter full URL for new tenant:")));
            if (empty($url))
                $this->info('url cannot be blank');
        }
        $key = Backslash4::tidyTenantKey($url);
        if (!empty(config('backslash4.tenants.' . $key, null))) {
            $answer = trim(
                strtolower(
                    $this->askNotEmpty(
                        "That tenant key '{$key}' already exists...\n"
                        . " Confirm you want to overwrite / recreate this same Tenant,\n"
                        . " Are you sure? (y/n)",
                        'no'
                    )
                )
            );
            if (!in_array($answer, $this->yesArray))
                return;
        }
        $db = '';
        $default = $key;
        foreach (['-local', 'cherrytest-com', '-com', '-co-uk'] as $end)
            if (Str::endsWith($default, $end))
                $default = Str::replaceLast($end, '', $default);
        $defaultPath = $default;
        $default = config('backslash4.tenants-db-prefix', '') . Str::replace('-', '_', $default) . '_webdb';

        $db = trim(strtolower($this->askNotEmpty("Enter a database name for the new tenant,\n  '{$key}' ", $default)));
        if (empty($db))
            $db = $default;

        $path = trim(strtolower($this->askNotEmpty("Enter directory name for the new tenant,\n  '{$key}' ", $defaultPath)));
        if (empty($path))
            $path = $defaultPath;

        $tenant = [
            'database' => $db,
            'paths' => [
                'base-path' => config('backslash4.tenants-path') . '/' . $path,
                'route-file' => 'routes/web.php',
                'views' => 'resources/views',
                'scss-file' => 'resources/scss/app.scss',
                'js-file' => 'resources/js/app.js',
                'config-file' => 'config/tenant.php',
                'build-path' => 'build',
                'public-build-path' => 'builds/' . $path,
                'path-name' => $path,
            ],
            'settings' => [],
        ];

        $this->info('Tenant Key: ' . $key . "\n");
        echo json_encode($tenant, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
        $this->info("\n");

        $answer = strtolower($this->askNotEmpty("Confirm you want to create this new Tenant,\n  Are you sure? (y)", 'no'));
        if (in_array($answer, $this->yesArray)) {
            $this->createTenantVite($key, $tenant);

            $config = Config::get('backslash4');
            $config['tenants'][$key] = $tenant;

            File::ensureDirectoryExists(Backslash4::tenant_path('routes', $tenant));
            $rpath = Backslash4::tenant_path($tenant['paths']['route-file'], $tenant);
            $this->info('rpath:' . $rpath);
            if (!fileExists($rpath)) {
                $handle = fopen($rpath, "w");
                if ($handle) {
                    fputs($handle, "<?PHP\n\nuse Illuminate\Support\Facades\Route;\n\n");
                    fclose($handle);
                }
            }

            File::ensureDirectoryExists(Backslash4::tenant_path('resources/views', $tenant));
            File::ensureDirectoryExists(Backslash4::tenant_path('resources/views/components', $tenant));
            File::ensureDirectoryExists(Backslash4::tenant_path('resources/scss', $tenant));
            touch(Backslash4::tenant_path($tenant['paths']['scss-file'], $tenant));

            File::ensureDirectoryExists(Backslash4::tenant_path('resources/js', $tenant));
            touch(Backslash4::tenant_path($tenant['paths']['js-file'], $tenant));

            File::ensureDirectoryExists(Backslash4::tenant_path('config', $tenant));

            //create database
            DB::statement('CREATE DATABASE IF NOT EXISTS ' . $tenant['database']);
            if (empty(config('database.connections.' . $key, null)))
                $this->addDataBaseConnection($key, $db);

            //this is used by the command line to know we're dealing with a specific tenant
            config(['backslash4.tenants-on' => $key]); //actually obsolete

            $this->usingConnection($key, function () use ($config, $tenant, $key) {
                //Config::set('database.connections.mysql.database', $tenant['database']);
                $this->info('db2::> ' . $key . ', ' . $tenant['database']);
                try {
                    \Artisan::call('migrate:install', ['--database' => $key]);
                    $this->info(\Artisan::output());
                } catch (\Throwable $th) {
                    $this->info('  [INFO] migrate install already exists, OK');
                }
                //Config::set('database.connections.mysql.database', $tenant['database']);
                //$this->callSilently('migrate'); //to run laravel's migrations -- extra dev ones may fail
                try {
                    \Artisan::call('migrate', ['--database' => $key, '--force' => true]);
                    $this->info(\Artisan::output());
                } catch (\Throwable $th) {
                    $this->info("First migrate " . json_encode($th) . ", OK");
                }
                $this->info('Upgrade BS4 modules/tables');
                $this->upgradeTables($config, $key);
                //Config::set('database.connections.mysql.database', $tenant['database']);
                \Artisan::call('migrate', ['--database' => $key, '--force' => true]);
                $this->info(\Artisan::output());
                //$this->call('migrate'); // to run the migrations that failed first time because they needed databases from backslash which doesn't use migrations    
            });

            //don't save the main backslash4 config until after we have done the switching database stuff
            $this->saveConfig($config);

            unset($config['tenants']);
            unset($config['tenants-on']);
            unset($config['tenants-path']);

            //now save it with out tenant info for the tenant
            $this->saveTenantConfig($config, $key);

            config(['backslash4.tenants-on' => 1]);
        }
    }

    /**
     * Create the package.json, vite file and support files for building app scripts (scss and js) for tenants build:tenant-key dev:tennant-key
     * @param  Array $tenantInfo
     * @return void
     */
    private function createTenantVite($tenantKey, $tenantInfo): void
    {
        File::ensureDirectoryExists(Backslash4::tenant_path($tenantInfo['paths']['build-path'], $tenantInfo));
        File::ensureDirectoryExists(public_path('builds'));

        $link = public_path($tenantInfo['paths']['public-build-path']);
        $target = Backslash4::tenant_path($tenantInfo['paths']['build-path'], $tenantInfo);

        if (file_exists($link) && !is_link($link)) {
            $this->error("The [$link] folder/link already exists.");
            return;
        }
        //else
        if (is_link($link)) {
            $this->laravel->make('files')->delete($link);
        }
        $this->laravel->make('files')->link($target, $link);
        $this->info("The [$link] link has been connected to [$target].");

        //create tennat vite.config.js if it doesn't already exist
        $filename = Backslash4::tenant_path("vite.config.js", $tenantInfo);
        if (!file_exists($filename)) {
            //create vite file
            $content = "import { defineConfig } from 'vite';"
                . "\nimport laravel from 'laravel-vite-plugin';"
                . "\n\nlaravel.tenantname = '{$tenantKey}';"
                . "\n\nexport default defineConfig({"
                . "\n  plugins: ["
                . "\n    laravel({"
                . "\n      input: ['{$tenantInfo['paths']['base-path']}/{$tenantInfo['paths']['scss-file']}',"
                . "\n              '{$tenantInfo['paths']['base-path']}/{$tenantInfo['paths']['js-file']}',"
                . "\n             ],"
                . "\n      buildDirectory: '{$tenantInfo['paths']['public-build-path']}',"
                . "\n      refresh: true,"
                . "\n    }),"
                . "\n    ],"
                . "\n});\n";

            $handle = fopen($filename, "w");
            fputs($handle, $content);
            fclose($handle);
        }

        //add commands to package.json - but checks they don't already exist
        $filename = base_path('package.json');
        if (!Str::contains(file_get_contents($filename), "build:{$tenantInfo['paths']['path-name']}")) {
            $insert = ",\n\"dev:{$tenantInfo['paths']['path-name']}\": \"vite --config {$tenantInfo['paths']['base-path']}/vite.config.js\","
                . "\n\"build:{$tenantInfo['paths']['path-name']}\": \"vite build --config {$tenantInfo['paths']['base-path']}/vite.config.js\"";
            $this->fileInsertAfter($filename, $insert, '"build": "vite build"');
        }
    }

    // Switching Database Connection functions
    // ----------------------------------------------------------------------

    /**
     * wrapper function for DB::usingConnection($tenantKey, $callback); that ensures the database connection exists for the call
     * @param  String   $tenantKey         the key for the config('database.connections') setting to use
     * @param  function $callback          the function to call back using the given connection
     * @return any
     */
    public function usingConnection($tenantKey = 'mysql', $callback = null)
    {
        if($callback==null) { 
            return null;
        }

        if (empty($tenantKey))
            $tenantKey = 'mysql';

        if (empty(config("database.connections." . $tenantKey))) {
            $this->addDataBaseConnection($tenantKey, config('backslash4.tenants.' . $tenantKey . '.database'));

            //config(['database.connections.'.$tenantKey => $dbconfig]);
        }
        return DB::usingConnection($tenantKey, $callback);
    }

    /**
     * Create a copy of the msql entry for this $key (tenant) given with the specified database
     * @param  String $key      the key for the config('database.connections') entry to be added
     * @param  String | Array   String - the database name to replace the stabdard mysql connection that picks up data from the ENV file
     *                          Array - keys of optional items to replace the standard connection settings
     * @return void
     */
    private function addDataBaseConnection($key, $databaseInfo): void
    {
        if (is_string($databaseInfo)) {
            $newConn = "'{$key}' => [ 
            'driver' => 'mysql',
            'url' => env('DATABASE_URL'),
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => '{$databaseInfo}',
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => true,
            'engine' => null,
            'options' => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
            ]) : [],
        ],\n\n        ";
        } else {
            $newConn = "'{$key}' => [ 'notstr'=>'',
            'driver' => " . (@$databaseInfo['driver'] ?? "'mysql'") . ",
            'url' => " . (@$databaseInfo['url'] ?? "env('DATABASE_URL')") . ",
            'host' => " . (@$databaseInfo['host'] ?? "env('DB_HOST', '127.0.0.1')") . ",
            'port' => " . (@$databaseInfo['port'] ?? "env('DB_PORT', '3306')") . ",
            'database' => " . (@$databaseInfo['database'] ?? "env('DB_DATABASE', 'forge')") . ",
            'username' => " . (@$databaseInfo['username'] ?? "env('DB_USERNAME', 'forge')") . ",
            'password' => " . (@$databaseInfo['password'] ?? "env('DB_PASSWORD', '')") . ",
            'unix_socket' => " . (@$databaseInfo['unix_socket'] ?? "env('DB_SOCKET', '')") . ",
            'charset' => " . (@$databaseInfo['charset'] ?? "'utf8mb4'") . ",
            'collation' => " . (@$databaseInfo['collation'] ?? "'utf8mb4_unicode_ci'") . ",
            'prefix' => " . (@$databaseInfo['prefix'] ?? "''") . ",
            'prefix_indexes' => " . (@$databaseInfo['prefix_indexes'] ?? "true") . ",
            'strict' => " . (@$databaseInfo['strict'] ?? "true") . ",
            'engine' => " . (@$databaseInfo['engine'] ?? "null") . ",
            'options' => " . (@$databaseInfo['options'] ?? "extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA')") . ",
            ]) : [],
        ],\n\n        ";
        }

        $this->fileInsertBefore(config_path('database.php'), $newConn, "'pgsql' => [");
        $this->clearConfigCache();
    }

    // Tenant Migration Functions
    // ----------------------------------------------------------------------
    private function doMigrationsRollback(): void
    {
        $this->info("Rollback Migrations against a tenant");
        $tenantkey = '';
        if (!empty(config('backslash4.tenants-on', 0))) {
            $pickTenant = $this->askWhichTenant('default mysql','all tenants', true);
            $this->info('Tenant: ' . $pickTenant);
        }
        if($pickTenant==='')
        {
            $this->info('Migrations cancelled');
            return;
        }

        if($pickTenant=='default mysql' || $pickTenant=='all tenants' )
        {
            try {
                \Artisan::call('migrate:rollback', ['--database' => 'mysql', '--force' => true]);
                $this->info(\Artisan::output());

            } catch (\Throwable $th) {
                $this->info('Migration Rollback, threw an error...');
                $this->info($th->getMessage());
            }
        }

        //for each tenant
        foreach (config('backslash4.tenants') as $key => $tenant) {

            if($pickTenant==$key || $pickTenant=='all tenants' )
            {
                try {
                    \Artisan::call('migrate:rollback', ['--database' => $key, '--force' => true]);
                    $this->info(\Artisan::output());
                } catch (\Throwable $th) {
                    $this->info('Migration Rollback on ' . $key . ', threw an error...');
                    $this->info($th->getMessage());
                }
            }
        }

    }

    private function doMigrations(): void
    {
        $this->info("Run Migrations against a tenant");
        $tenantkey = '';
        if (!empty(config('backslash4.tenants-on', 0))) {
            $pickTenant = $this->askWhichTenant('default mysql','all tenants', true);
            $this->info('Tenant: ' . $pickTenant);
        }
        if($pickTenant==='')
        {
            $this->info('Migrations cancelled');
            return;
        }

        // $upgrade = $this->askNotEmpty('Do you want to upgrade BS4 Tables from the Dashboard [no]/yes');
        // $this->info('Upgrade Tables: ' . $upgrade);
        $upgrade = 'no';    
        
        if($pickTenant=='default mysql' || $pickTenant=='all tenants' )
        {
            $this->info("Tenant: mysql, main database");
            $this->info("----------------------------");
            if(in_array($upgrade, $this->yesArray))
            {
                $this->info('Upgrade BS4 modules/tables');            
                $this->upgradeTables(null, 'mysql');
            }

            try {
                \Artisan::call('migrate', ['--database' => 'mysql',  '--step'=>'', '--force' => true]);
                $this->info(\Artisan::output());
            } catch (\Throwable $th) {
                $this->info('Migration on "mysql", threw an error...');
                $this->info($th->getMessage());
            }
        }

        if (!empty(config('backslash4.tenants-on', 0))) {

            //for each tenant
            foreach (config('backslash4.tenants') as $key => $tenant) {

                if($pickTenant==$key || $pickTenant=='all tenants' )
                {
                    $this->info("Tenant: {$key}, {$tenant['database']}");
                    $this->info("----------------------------------");

                    $this->clearConfigCache();
                    $this->usingConnection($key, function () use ($key, $upgrade) {
                        
                        if(in_array($upgrade, $this->yesArray))
                        {            
                            $this->info('Upgrade BS4 modules/tables');
                            $this->upgradeTables(null, $key);
                        }

                        try {

                            /**
                             * Note: using $this->call('migrate',[ '--database' => $key, '--force' => true]);
                             * did not honour the datbase patrameter after it's first call
                             * use \Artisan::Call
                             */
                            \Artisan::call('migrate', ['--database' => $key, '--step'=>'',  '--force' => true]);
                            $this->info(\Artisan::output());
                        } catch (\Throwable $th) {
                            $this->info("Migration on {$key}, " . config('database.connections.' . $key . '.database', '??') . ", threw an error...");
                            $this->info($th->getMessage());
                        }
                    });
                }
            }
        }

    }

    // Developer Test function  php artisan backslash 909
    // ----------------------------------------------------------------------
    private function test909(): void
    { // use for dev testing

    }

}