<?php
namespace Clevercherry\Backslash4\Controllers\Admin;

use Illuminate\Http\Request;
use Clevercherry\Backslash4\Controllers\Controller;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Storage;
use Clevercherry\Backslash4\Models\Block;
use Illuminate\Support\Carbon;
use App\Models\MediaManager;
use Clevercherry\Backslash4\Backslash4;
use Clevercherry\Backslash4\Traits\checkControllerSecurityAbort;
use Illuminate\Support\Facades\Session;

class BkAdminController extends Controller
{
    use checkControllerSecurityAbort;

    public $adminView = "backslash4::admin.generic";
    public $storeReturn = 'view';
    public $updateReturn = 'view';

    /**
     * Returns the index page view
     * @return type
     */
    public function index(Request $request)
    {
        $this->checkControllerSecurityAbort($this->AppModel);

        $viewName = $this->adminView.'.index'; //default backslash4
        $indexView = 'admin.'.Str::plural(strtolower(basename(str_replace('\\','/',$this->AppModel)))).'.index';
        if((config('backslash4.tenants-on',0)!=0) && view()->exists('tenant::'.$indexView ))
            $viewName = 'tenant::'.$indexView;
        else if(view()->exists($indexView))
                $viewName = $indexView;
        //else remains as default

        return $this->indexWithView($request, $viewName);
    }

    public function indexTable(Request $request)
    {
        $this->checkControllerSecurityAbort($this->AppModel);

        $viewName = $this->adminView.'.no-dashboard.table'; //default backslash4
        $indexView = 'admin.'.Str::plural(strtolower(basename(str_replace('\\','/',$this->AppModel)))).'.no-dashboard.table';
        if((config('backslash4.tenants-on',0)!=0) && view()->exists('tenant::'.$indexView ))
            $viewName = 'tenant::'.$indexView;
        else if(view()->exists($indexView))
                $viewName = $indexView;
        //else remains as default

        return $this->indexWithView($request, $viewName);
    }

    public function indexWithView(Request $request, $viewName)
    {
        $this->checkControllerSecurityAbort($this->AppModel);

        $q = @$request->q;

        $dropDownSearches = ($this->AppModel)::getAdminSearchDropDowns();

        $items=($this->AppModel)::adminListing();

        $dropDownSearches = ($this->AppModel)::getAdminSearchDropDowns();
        $dropDownLists =[];
        if(!empty($dropDownSearches))
            foreach($dropDownSearches as $key => $text)
            {
                $dropDownLists[$key]= ($this->AppModel)::getAdminSearchDropDownList($key);
            }

        return view($viewName)
            ->with('appModel', $this->AppModel)
            ->with('showAdminSearch', ($this->AppModel)::showAdminSearch())
            ->with('showDropDownSearches', !empty($dropDownSearches))
            ->with('dropDownSearches', $dropDownSearches)
            ->with('dropDownSearchLists', $dropDownLists)
            ->with('tableName', $this->getTableName() )
            ->with('showChildren', ($this->AppModel)::showChildren())
            ->with('type', Str::of(Str::replace('\\','/',$this->AppModel))->basename())
            ->with('adminRoute', $this->adminRoute)
            ->with('items', $items);
    }

    public function create(Request $request)
    {
        $this->checkControllerSecurityAbort($this->AppModel);

        $item = new ($this->AppModel)();

        if(Schema::hasColumn($this->getTableName(),'date_published'))
            $item->date_published = Carbon::now();
        if(Schema::hasColumn($this->getTableName(),'date'))
            $item->date = Carbon::now();

        if(Schema::hasColumn($this->getTableName(),'associated_with'))
            $item->associated_with = json_encode([]);


        $viewName = $this->adminView.'.edit'; //default backslash4
        $editView = 'admin.'.Str::plural(strtolower(basename(str_replace('\\','/',$this->AppModel)))).'.edit';
        if((config('backslash4.tenants-on',0)!=0) && view()->exists('tenant::'.$editView))
            $viewName = 'tenant::'.$editView;
        else if(view()->exists($editView))
            $viewName = $editView;
        //else remains as default

        if(property_exists($this, 'formActionURL') && !empty($this->formActionURL))
        {   $action = $this->formActionURL;
        } else
        {   //default behaviour
            $action = $this->getAdminRoute($this->adminRoute, 'store');
        }
        if(property_exists($this, 'formCancelURL') && !empty($this->formCancelURL))
        {   $cancel = $this->formCancelURL;
        } else
        {   //default behaviour
            if(!empty(session('lastAdminModel',null)) && $item->isSubmodule())
                $cancel = route('admin.' . Str::plural(strtolower(session('lastAdminModel',null))) . '.edit', ['id' => session('lastAdminModelId',null)]);
            else //default
                $cancel = $this->getAdminRoute($this->adminRoute, 'index');
        }

        return view($viewName)
                    ->with('action', $action)
                    ->with('cancel', $cancel)
                    ->with('item', $item);
    }

    /**
     * return a route from $base and $suffix
     * in case you have to override the route with parameters
     */
    public function getAdminRoute($base, $suffix, $params=[])
    {
        return route($this->adminRoute . '.' .$suffix, $params);
    }

    public function download($id, $field_name)
    {
        $this->checkControllerSecurityAbort($this->AppModel);

        $item = ($this->AppModel)::where('id',$id)->first();

        if(! $item) {
            return response(['error' => 'Record with id ' . $id . ' not found'], 404);
        }
        
        $filename = $item->{$field_name};
        $fullpath = MediaManager::getStoragePath($filename);

        if(Str::startsWith($filename, 's3:'))
        {
            if(Storage::drive('s3')->exists($fullpath)){
                return Storage::drive('s3')->response($fullpath);
            }

        } else {
            if(File::exists($fullpath)) {
                return Storage::response($fullpath);
            }
        }

        return response(['error' => 'File not found'], 404);
    }

    /**
     * Duplicates a record
     * @param Request $request
     * @return type
     */
    public function duplicate(Request $request)
    {
        $this->checkControllerSecurityAbort($this->AppModel);

        $item = ($this->AppModel)::where('id', $request->id)->first();
        $itemBlocks = $item->getBlocks();

        for ($i = 0; $i < $request->amount; $i++) { 
            $newItem = $item->replicate();

            $nameColumns = [
                "name",
                "title",
                "slug",
            ];

            foreach($nameColumns AS $nameColumn) {
                if(Schema::hasColumn($item->getTable(),$nameColumn)) {
                    $item->{$nameColumn} .= '-duplicate';
                }
            }

            if(Schema::hasColumn($item->getTable(),'active')) {
                $newItem->active = 0;
            }

            if(Schema::hasColumn($item->getTable(),'status')) {
                $newItem->status = ($this->AppModel)::STATUS_DRAFT;
            }

            if($request->development){
                $newItem->development = $request->development;
            }

            $newItem->created_at = \Carbon\Carbon::now();
            $newItem->updated_at = \Carbon\Carbon::now();

            $newItem->save();

            foreach($itemBlocks AS $key => $block) {     
                $newBlock = $block->replicate();
                $newBlock->modelId = $newItem->id;
                $newBlock->created_at = \Carbon\Carbon::now();
                $newBlock->updated_at = \Carbon\Carbon::now();
                $newBlock->save();
            }
        }

        return  redirect()->back()->with('bs4response', [
                    'status' => 'OK', 
                    'title' => $i . ' ' . Str::of('item')->plural($i) . ' duplicated.',
                    'message' => 'All duplicated records have been saved and had their active status set to inactive where applicable',
                ]);
    }

    public function edit(Request $request, $id)
    {
        $this->checkControllerSecurityAbort($this->AppModel);

        $item = ($this->AppModel)::where('id',$id)->first();

        if(!$item->isSubmodule())
        {
            //for use by sub models
            Session::put('lastAdminModel', $item->basename());
            Session::put('lastAdminModelId', $item->id);
        }

        $viewName = $this->adminView.'.edit'; //default backslash4
        $editView = 'admin.'.Str::plural(strtolower(basename(str_replace('\\','/',$this->AppModel)))).'.edit';
        if((config('backslash4.tenants-on',0)!=0) && view()->exists('tenant::'.$editView))
            $viewName = 'tenant::'.$editView;
        else if(view()->exists($editView))
            $viewName = $editView;
        //else remains as default

        if(property_exists($this, 'formActionURL') && !empty($this->formActionURL))
        {   $action = $this->formActionURL;
        } else
        {   //default behaviour
            $action = $this->getAdminRoute($this->adminRoute, 'update');
        }
        if(property_exists($this, 'formCancelURL') && !empty($this->formCancelURL))
        {   $cancel = $this->formCancelURL;
        } else
        {   //default behaviour
            if(!empty(session('lastAdminModel',null)) && $item->isSubmodule())
                $cancel = route('admin.' . Str::plural(strtolower(session('lastAdminModel',null))) . '.edit', ['id' => session('lastAdminModelId',null)]);
            else //default
                $cancel = $this->getAdminRoute($this->adminRoute, 'index');
        }

        return view($viewName)
                    ->with('action', $action)
                    ->with('cancel', $cancel)
                    ->with('item', $item);
    }

    public function orderable(int $id, string $direction)
    {
        $model = new ($this->AppModel);
        $orderableColumn = $model->orderable();

        $item = ($this->AppModel)::where('id',$id)->first();
        $swap = false;

        switch ($direction) {
            case 'up':
                $swap = ($this->AppModel)::where($orderableColumn, '<', $item->$orderableColumn)->orderBy($orderableColumn, 'desc')->first();
                break;
            
            case 'down':
                $swap = ($this->AppModel)::where($orderableColumn, '>', $item->$orderableColumn)->orderBy($orderableColumn)->first();
                break;
        }

        if($swap) {
            $itemOrder = $item->$orderableColumn;
            $swapOrder = $swap->$orderableColumn;

            $item->$orderableColumn = $swapOrder;
            $swap->$orderableColumn = $itemOrder;

            $item->save();
            $swap->save();
        }

        return redirect(URL::previous() . '?' . http_build_query(Request()->all()));
    }

    public function getTableName()
    {
        return Str::snake(Str::plural(class_basename($this->AppModel)));
    }

    public function makeSlug($str)
    {
        return Str::slug($str);;
    }

    public function store(Request $request)
    {
        $this->checkControllerSecurityAbort($this->AppModel);

        $model = new $this->AppModel;
        $fillable = $model->setDynamicFillable();

        $store = $request->except("_token");

        if(in_array('slug', $fillable) && empty(@$store['slug']))
        {    $store['slug'] = $this->makeSlug(@$store['name'] ?? @$store['title']);
        }

        if(in_array('created_at', $fillable) && empty(@$store['created_at']))
        {    $store['created_at'] = Carbon::now();
        }

        if(in_array('updated_at', $fillable) && empty(@$store['updated_at']))
        {    $store['updated_at'] = Carbon::now();
        }

        if(array_key_exists('password', $store))
        {
            if(empty($store['password']) || empty(trim($store['password'])))
                unset($store['password']);
            else
                $store['password'] = bcrypt($store['password']);
        }

        //all prices are stored in pennies/cents
        if(isset($store['price']) && !empty($store['price']))
            $store['price'] = $store['price']*100;
        if(isset($store['price2']) && !empty($store['price2']))
            $store['price2'] = $store['price2']*100;
        if(isset($store['cost']) && !empty($store['cost']))
            $store['cost'] = $store['cost']*100;

        $blocks=null;
        if(isset($store['blocks']))
            $blocks = $store['blocks'];

        $store = $this->checkBeforeSave($store);
        unset($store['blocks']);

        $item = new ($this->AppModel)();
        if(empty($item->getFillable()) && method_exists($item, 'setDynamicFillable'))
            $item->setDynamicFillable();
        // else ...
        
        $item = $item->create($store);
        //not using static creaate as it does not set the fillable on the new item it creates
        //$item = ($this->AppModel)::create($store);

        if($item->hasBlocks() && ($blocks!==null))
            Block::updateBlocks($this->AppModel, $item->id, $blocks);

        $this->postStoreProcess($item);

        if(in_array($this->storeReturn,['item','object']))
            return $item;    
        //else
        
        return $this->bs4StoreResponse($model, $item);
    }

    /**
     * provide the store response in a separate function so that conmtrollers that override the function store
     * can reuse it, ie. the ProductController
     */
    public function bs4StoreResponse($model, $item) {
        $bs4response = [
            'status' => 'OK', 
            'title' => Str::singular(trim($item->getCMSModuleDisplayName())) . ' saved.',
            'message' => 'The changes to '. trim($item->getNameTitle() . ' have been saved.')
        ];

        $CMSModuleName = $model->getCMSModuleName();
        $action = @Backslash4::getDashboardModelConfig($CMSModuleName)['afterStore'];
        if($action=='index')
        {
            //test to see if we have sub-module settings saved, ie. back to a parent model with a selected tab
            //see if its a submodel and we should redirect back to the parent edit
            $lastModel = session('lastAdminModel',null);
            $lastId = session('lastAdminModelId',null);
            //$fromTab = session('fromTab', null) // superfluous the tab is this model
            if(!empty($lastModel) && !empty($lastId))
            {
                return redirect()
                    ->route('admin.'. Str::plural(strtolower(($lastModel))) .'.edit',  ['id' => $lastId ])
                    ->with('bs4tab', $CMSModuleName)
                    ->with('bs4response', $bs4response);
            }

            return redirect()
                    ->route($this->adminRoute.'.index')
                    ->with('bs4response', $bs4response);

        } else if($action=='edit')
        {    
            return redirect()
                    ->route($this->adminRoute.'.edit', ['id' => $item->id])
                    ->with('bs4response', $bs4response);
        }

        //default 'new' return back to create another one
        return redirect()
                    ->back()
                    ->with('bs4response', $bs4response);

    }

    public function update(Request $request)
    {
        $this->checkControllerSecurityAbort($this->AppModel);

        $model = new $this->AppModel;
        $fillable = $model->setDynamicFillable();

        $store = $request->except("_token");

        if(in_array('slug', $fillable) && empty($store['slug']))
            $store['slug'] =  $this->makeSlug(@$store['name'] ?? @$store['title']);

        if(in_array('active', $fillable) && !isset($store['active']))
            $store['active']=0;

        if(in_array('updated_at', $fillable))
        {    $store['updated_at'] = Carbon::now();
        }

        if(array_key_exists('password', $store))
        {
            if(empty($store['password']) || empty(trim($store['password']))) {
                unset($store['password']);
            } else {
                $store['password'] = bcrypt($store['password']);
            }
        }

        //all prices are stored in pennies/cents
        if(isset($store['price']) && !empty($store['price']))
            $store['price'] = $store['price']*100;
        if(isset($store['price2']) && !empty($store['price2']))
            $store['price2'] = $store['price2']*100;
        if(isset($store['cost']) && !empty($store['cost']))
            $store['cost'] = $store['cost']*100;

        $blocks=null;
        if(isset($store['blocks']))
            $blocks = $store['blocks'];

        $store = $this->checkBeforeSave($store);
        unset($store['blocks']);
        $item = ($this->AppModel)::where('id', $store['id'])->first();

        if(empty($item->getFillable()) && method_exists($item, 'setDynamicFillable'))
            $item->setDynamicFillable();
        // else ...

        $item->update($store);

        if($item->hasBlocks() && ($blocks!==null))
            Block::updateBlocks($this->AppModel, $item->id, $blocks);

        $this->postUpdateProcess($item);

        if(in_array($this->updateReturn,['item','object']))
            return $item;

        return $this->bs4UpdateResponse($model, $item);        
    }

    /**
     * separate the conditional response so that overrridden update functions can use it
     * ie. in the ProductController
     */
    public function bs4UpdateResponse($model, $item) {
        $bs4response = [
            'status' => 'OK', 
            'title' => Str::singular(trim($item->getCMSModuleDisplayName())) . ' saved',
            'message' => 'The changes to '. trim($item->getNameTitle() . ' have been saved.'),
        ];


        $CMSModuleName = $model->getCMSModuleName();
        $action = @Backslash4::getDashboardModelConfig($CMSModuleName)['afterUpdate'];
        if($action=='index')
        {
            //test to see if we have sub-module settings saved, ie. back to a parent model with a selected tab
            //see if its a submodel and we should redirect back to the parent edit
            $lastModel = session('lastAdminModel',null);
            $lastId = session('lastAdminModelId',null);
            //$fromTab = session('fromTab', null) // superfluous the tab is this model
            if(!empty($lastModel) && !empty($lastId))
            {
                return redirect()
                    ->route('admin.'. Str::plural(strtolower(($lastModel))) .'.edit',  ['id' => $lastId])
                    ->with('bs4tab', $CMSModuleName)
                    ->with('bs4response', $bs4response);
            }

            return redirect()
                    ->route($this->adminRoute.'.index')
                    ->with('bs4response', $bs4response);

        } else if($action=='edit')
        {    
            return redirect()
                    ->route($this->adminRoute.'.edit', ['id' => $item->id])
                    ->with('bs4response', $bs4response);
        }
        
        //default return back to create another one
        return redirect()
                ->back()
                ->with('bs4response', $bs4response);
    }

    /**
     * After Storing a model for the first time this function is called with the Model Record
     * @Param $item     The record that has just been saved
     *                  default action is nothing
     */
    public function postStoreProcess($item)
    {

    }

    /**
     * After Updating a model this function is called with the Model Record
     * @Param $item     The record that has just been saved
     *                  default action is nothing
     */
    public function postUpdateProcess($item)
    {

    }

    /**
     * export view, almost always has to be overridden and simplified in your app so that it provides
     * the correct data to the view
     * the view will have to be provided in your app
     */
    public function export(Request $request)
    {
        $this->checkControllerSecurityAbort($this->AppModel);

        $viewName = $this->adminView.'.export'; //no default view in backslash4 -- yet
        $exportView = 'admin.'.Str::plural(strtolower(basename(str_replace('\\','/',$this->AppModel)))).'.export';
        if((config('backslash4.tenants-on',0)!=0) && view()->exists('tenant::'.$exportView ))
            $viewName = 'tenant::'.$exportView;
        else if(view()->exists($exportView))
                $viewName = $exportView;
        //else remains as default

        $q = @$request->q;
        $dropDownSearches = ($this->AppModel)::getAdminSearchDropDowns();
        if(empty($q) && empty($dropDownSearches))
            $items=($this->AppModel)::adminListing(9999999999);
        else
            $items=($this->AppModel)::indexSearch($q)->get();
        
        return view($viewName)
                    ->with('items', $items);
    }

    /**
     * any checks that need to be done before the model is saved
     * in your custom controller like setting checkbox fields
     * @param  Array $store     the $store array that is about to be saved/updated
     * @return Array            the same array returned (with any fixes that the custom controller needs to do)
     */
    public function checkBeforeSave($store) : Array
    {
        return $store;
    }

    public function delete(Request $request, $id)
    {
        $this->checkControllerSecurityAbort($this->AppModel);

        $item = ($this->AppModel)::where('id',$id)->first();
        if($item!=null)
            $item->delete();

        //see if its a submodel and we should redirect back to the parent edit
        $lastModel = session('lastAdminModel',null);
        $lastId = session('lastAdminModelId',null);
        if(!empty($lastModel) && !empty($lastId))
        {
            session(['fromTab'=> $this->AppModel]);
            
        } else
        {
            session(['fromTab'=> '']);

        }

        return redirect()->back()
                ->with('bs4response', 
                [
                    'status' => 'DELETED', 
                    'title' => Str::singular(trim($item->getCMSModuleDisplayName())) . ' deleted',
                    'message' => trim($item->getNameTitle().' deleted.'),
                ]
            );
    }

}
