<?php

namespace Accounting;

use App\Http\Controllers\Controller;
use Base\DBQueries;
use Illuminate\Support\Facades\DB;

abstract class ParamSetup extends Controller
{
    protected string $action;
    protected ?string $table;
    protected ?array $values;
    protected ?array $conditions;
    protected ?string $sort;
    protected ?int $limit;
    protected ?string $key;
    protected $response;
    protected string $filterTable;
    public abstract function setParams();

    /** get and set parameters based from derived class request **/
    public function generateParams() : bool {
        try {
            $request = $this->setParams();
            $params = $request['request'];
            $this->table = $request['table'];
            $this->action = DBActions($params['type']);
            $this->values = $params['values'] ?? null;
            $this->conditions = $params['conditions'] ?? null;
            $this->sort = $params['sort'] ?? null;
            $this->limit = $params['limit'] ?? null;
            $this->key = $params['key'] ?? null;
            return true;
        }
        catch (\Exception){return false;}

    }

    public function execQuery() : void
    {
        try{
            $action = $this->action;
            $this->action = $action;
            $query = new DBQueries($this->table,$this->values,$this->conditions,$this->sort,$this->limit,$this->key);
            $this->response = $query->$action();
        }
        catch (\Exception $ex){
            $this->response = ['msg' => $ex->getMessage()];
            throw $ex;
        }

    }

    public function DBActions($action) : string {
        $actions = [
            'add' => 'dbInsert',
            'update' => 'dbUpdate',
            'get' => 'dbGet',
            'all' => 'dbGetAll',
            'list' => 'dbGetAll',
            'delete' => 'dbDelete',
        ];
        return $actions[$action];
    }

    /** Auto generate account code based on node, level, and order **/
    public function codeGenerator($parent, $level) : ?string {
        $generatedCode = null;
        if((int)$level === 2){
            $baseCode = explode('-',$parent);
            $code = (int)$baseCode[0] + 1;
            $generatedCode = $code.'-'.$baseCode[1];
            if(isset($baseCode[2])) $generatedCode = $code.'-'.$baseCode[1].'-'.$baseCode[2];
        }
        else if((int)$level === 3){
            $baseCode = explode('-',$parent);
            $code1 = mb_substr($baseCode[1],0,2);
            $code2 = mb_substr($baseCode[1],2,2);
            $code1 = (int)$code1 + 1;
            $generatedCode = $baseCode[0].'-'.($code1 < 10 ? '0'.$code1 : $code1).$code2;
            if(isset($baseCode[2])) $generatedCode = $baseCode[0].'-'.($code1 < 10 ? '0'.$code1 : $code1).$code2.'-'.$baseCode[2];
        }
        else if((int)$level === 4){
            $baseCode = explode('-',$parent);
            $code1 = mb_substr($baseCode[1],0,2);
            $code2 = mb_substr($baseCode[1],2,2);
            $code2 = (int)$code2 + 1;
            $generatedCode = $baseCode[0].'-'.$code1.($code2 < 10 ? '0'.$code2 : $code2);
            if(isset($baseCode[2])) $generatedCode = $baseCode[0].'-'.$code1.($code2 < 10 ? '0'.$code2 : $code2).'-'.$baseCode[2];
        }
        else if((int)$level === 5){
            $baseCode = explode('-',$parent);
            $code1 = mb_substr($baseCode[2],0,2);
            $code2 = mb_substr($baseCode[2],2,2);
            $code1 = (int)$code1 + 1;
            $generatedCode = $baseCode[0].'-'.$baseCode[1].'-'.($code1 < 10 ? '0'.$code1 : $code1).$code2;
        }
        else if((int)$level === 6){
            $baseCode = explode('-',$parent);
            $code1 = mb_substr($baseCode[2],0,2);
            $code2 = mb_substr($baseCode[2],2,2);
            $code2 = (int)$code2 + 1;
            $generatedCode = $baseCode[0].'-'.$baseCode[1].'-'.$code1.($code2 < 10 ? '0'.$code2 : $code2);
        }
        return $generatedCode;
    }

    /** Selection for main account type **/
    public function parentCodeSelector($AccType) : ?array {
        $AccCode = ['code' => null, 'node' => 0, 'level' => 0, 'status' => 1, 'title' => ucfirst($AccType)];
        if($AccType === 'assets') {
            $AccCode['code'] = '100-0000-0000';
            $AccCode['normal_balance'] = 'Dr';
            $AccCode['order'] = 1;
        }
        else if($AccType === 'liabilities') {
            $AccCode['code'] = '200-0000-0000';
            $AccCode['normal_balance'] = 'Cr';
            $AccCode['order'] = 2;
        }
        else if($AccType === 'equity') {
            $AccCode['code'] = '300-0000-0000';
            $AccCode['normal_balance'] = 'Cr';
            $AccCode['order'] = 3;
        }
        else if($AccType === 'income') {
            $AccCode['code'] = '400-0000-0000';
            $AccCode['normal_balance'] = 'Cr';
            $AccCode['order'] = 4;
        }
        else if($AccType === 'expense') {
            $AccCode['code'] = '500-0000-0000';
            $AccCode['normal_balance'] = 'Dr';
            $AccCode['order'] = 5;

        }
        return $AccCode;
    }

    public function accountTypeSelector($AccType) : ?string {
        $accountType = [
            'assets' => 'Dr',
            'liabilities' => 'Cr',
            'equity' => 'Cr',
            'income' => 'Cr',
            'expense' => 'Dr',
        ];
        return $accountType[strtolower($AccType)];
    }

    /** Update an account node when it became a parent **/
    public function updateParentNode($code) : void {
        DB::table('tbl_chart_of_accounts')->where('code',$code)->update(['node' => 0]);
    }

    /** Remove unnecessary fields on response **/
    public function filterResponse() : void {
        $response = $this->response;
        if($response){
            if($this->action === 'dbGet'){
                foreach($response as $k => $v) if(in_array($k, filterResponseFields($this->filterTable))) unset($response->$k);
                $this->response = $response;
            }
            else if($this->action === 'dbGetAll'){
                $response = $response->map(function ($res){
                    foreach($res as $key => $value) if(in_array($key, filterResponseFields($this->filterTable))) unset($res->$key);
                    return $res;
                });
                $this->response = $response;
            }
        }
    }

    /** Remove values not included on the table **/
    public function filterRequest() : ?array {
        $request = $this->values;
        $response = array();
        if($request){
            if($this->table === 'tbl_entities' || $this->table === 'tbl_branch' || $this->table === 'tbl_department'
                || $this->table === 'tbl_transaction_settings' || $this->table === 'tbl_operations' || $this->table === 'tbl_templates'
                || $this->table === 'tbl_journal'){
                foreach($request as $k => $v) if(in_array($k, filterRequestFields($this->table))) {
                    $response[$k] = $v;
                    unset($request[$k]);
                }
                $this->values = $request;
            }
        }
        return $response;

    }

    public function addValues($col, $val) : void{
        $values = $this->values;
        $values[$col] = $val;
        $this->values = $values;
    }

    public function getValue($col) {
        $values = $this->values;
        if(isset($values[$col])) return $values[$col];
        return null;
    }

    public function removeValue($col) : void{
        $values = $this->values;
        unset($values->$col);
        $this->values = $values;
    }

    public function removeValueArray($col) : void{
        $values = $this->values;
        unset($values[$col]);
        $this->values = $values;
    }

    public function checkDuplicates($table, $col, $colValue) : bool {
        $query = DB::table($table)->where($col, $colValue)->get();
        if(!$query->isEmpty()) {
            return false;
        }
        return true;
    }

    public function checkDuplicatesInDetails($table_details, $type, $typeValue, $category) {
        $query = DB::table($table_details)
        ->where([
            "category" => $category,
            "type" => $type,
            "value" => $typeValue
        ])->get();
        if (!$query->isEmpty()) {
            return false;
        }

        return true;
    }

    public function generateTransactionCode($type) : string {
        $currentDate = date('Ym');
        $types = ['billings' => 'B', 'collections' => 'C', 'purchases' => 'P', 'payments' => 'D'];
        $code = $types[$type].$currentDate."00001";
        $transNo = DB::table('tbl_transaction_codes')->where('type', $type)->orderByDesc('code')->first();
        if($transNo){
            $date = substr($transNo->code, 1,6);
            $innerCode = substr($transNo->code, 7,5);
            if($date === $currentDate){
                $count = (int)$innerCode + 1;
                if($count < 10) $innerCode = "0000".$count;
                else if($count < 100) $innerCode = "000".$count;
                else if($count < 1000) $innerCode = "00".$count;
                else if($count < 10000) $innerCode = "0".$count;
                else $innerCode = $count;
            }
            else $innerCode = "00001";
            $code = $types[$type].$date.$innerCode;
        }
        return $code;
    }

    public function saveTransactionCode($code, $type) : bool {
        $isExist = DB::table('tbl_transaction_codes')->where('code', $code)->first();
        if($isExist) $code = $this->generateTransactionCode($type);
        $add['code'] = $code;
        $add['type'] = $type;
        $add['status'] = 'draft';
        $add['created_at'] = date('Y-m-d H:i:s');
        $add['updated_at'] = date('Y-m-d H:i:s');
        return DB::table('tbl_transaction_codes')->insert($add);
    }

    public function updateTransactionCode($code, $status) : bool {
        return DB::table('tbl_transaction_codes')->where('code', $code)->update(['status' => $status]);
    }

    public function generateCode($type, $length) : string {
        $year = date('Y');
        $tables = ["GL" => "tbl_general_ledgers", "JE" => "tbl_journal"];
        $series = $type.$year."000001";
        $code = DB::table($tables[$type])->orderByDesc('code')->first();
        if($code){
            $code = $code->code;
            $currentYear = substr($code, 2 ,4);
            if($currentYear === $year){
                $series = substr($code, $length);
                $series = (int) $series + 1;
                $value = str_pad($series, $length, '0', STR_PAD_LEFT);
                $series = $type.$currentYear.$value;
            }
            return $series;
        }
        return $series;
    }
}
