<?php

namespace Extension\TrafficLoadBalancerNew;

use Application\Config;
use Application\Request;
use Application\Session;
use Application\Logger;
use Application\CrmPayload;
use Application\CrmResponse;
use Database\Connectors\ConnectionFactory;

class BinScrapper
{

    public function __construct()
    {
        $this->pageType = Session::get('steps.current.pageType');
        $this->stepId   = Session::get('steps.current.id');
        $this->scrapperStepId = $this->stepId > 1 ? 2 : 1;
        $this->fileName = BASE_DIR . DS . 'storage/productOrderFilter';
        $this->binTableName = 'bins';
        $this->remoteEnable = Config::extensionsConfig('TrafficLoadBalancerNew.default_settings.enable_remote');
    }

    public function scrapFlow()
    {
        if (Request::attributes()->get('action') === 'prospect' || $this->pageType == 'thankyouPage') {
            return;
        }
        $binList = Session::get('extensions.trafficLoadBalancer.orderFilterDetials.binList');
        $isBlacklist = Session::get('extensions.trafficLoadBalancer.1.blacklist');
        if(empty($binList) || !empty($isBlacklist)) {
            return;
        }
        
        $matched = false;
        $formCardNumber = Request::form()->get('creditCardNumber');
        $cardDetails = empty($formCardNumber) ? Session::get('customer.cardNumber') : $formCardNumber;
        $formattedCardDetails = str_replace(
                ['-', ' '], '', $cardDetails
            );
        $cardPrefix = substr(trim($formattedCardDetails), 0, 6);
        
        foreach ($binList as $key => $value)
        {
            $binRange = $value['bin_range'];
            if(strpos($binRange, '-') != false) {
                $binRangeArray = explode('-', $binRange);
                $lowestBin = $binRangeArray[0];
                $largestBin = $binRangeArray[1];
                if($cardPrefix >= $lowestBin && $cardPrefix <= $largestBin) {
                    $mainPercentage = $value['action']['route']['step1_route_percentage'];
                    $upsellPercentage = $value['action']['route']['upsell_route_percentage'];
                    $matched = true;
                    break;
                }
            } elseif(strpos($binRange, ',') != false) {
            	$binRangeArray = explode(',', $binRange);
            	if(in_array($cardPrefix, $binRangeArray)) {
            		$mainPercentage = $value['action']['route']['step1_route_percentage'];
                    $upsellPercentage = $value['action']['route']['upsell_route_percentage'];
                    $matched = true;
                    break;
            	}
            } else {
                if($cardPrefix == $value['bin_range']) {
                    $mainPercentage = $value['action']['route']['step1_route_percentage'];
                    $upsellPercentage = $value['action']['route']['upsell_route_percentage'];
                    $matched = true;
                    break;
                }
            }
        }
        
        if(empty($matched)) {
            return;
        }
        
        if($mainPercentage == $upsellPercentage) {
            $upsellPercentage = 0;
        }
        
        $binArray = array(
            'bin' => $cardPrefix,
            'main_percentage' => $mainPercentage,
            'upsell_percentage' => $upsellPercentage
        );
        
        $this->dbConnection = $this->getDatabaseConnection();
        
        $this->rule = $this->getCurrentStepRuleForBin($binArray);        
        
        if (empty($this->rule))
        {
            return;
        }
        
        $isScrapped = $this->isScrapped($settings);

        Session::set(
            sprintf('extensions.trafficLoadBalancer.bin.%d', $this->scrapperStepId), array(
                'binChecked' => true,
                'binRuleId' => $this->rule['id'],
                'binScrapped' => $isScrapped
            )
        );
        if(!empty($isScrapped)) {
            Session::set('steps.meta.isScrapFlow', true);
            Session::set('extensions.trafficLoadBalancer.' . $this->stepId . '.scrapped', 1);
            Session::set('extensions.trafficLoadBalancer.' . $this->stepId . '.binRoute', true);
        } else {
            Session::set('steps.meta.isScrapFlow', false);
            Session::set('extensions.trafficLoadBalancer.' . $this->stepId . '.scrapped', 0);
            Session::set('extensions.trafficLoadBalancer.' . $this->stepId . '.binRoute', false);
        }
        
    }
    
    private function isScrapped($settings)
    {
        if ($this->scrapperStepId > 1)
        {
            if (Session::get('extensions.trafficLoadBalancer.bin.1.binScrapped'))
            {
                return true;
            }
        }
        
        $this->setEngine();

        if (empty($this->scrappingMethod))
        {
            return;
        }

        switch ($this->scrappingMethod)
        {
            case 'flat':return $this->determineByFlatScrapper();
            case 'random':
            default:return $this->determineByRandomScrapper();
        }
    }
    
    protected function determineByFlatScrapper()
    {
        $upperLimit = (int) $this->getUpperLimit($this->rule['percentage']);
        
        /* Fallback Error Calculation*/
        $newPercentage = $this->determineFlatPercentageBuffer($upperLimit, $this->rule['percentage']);
        if(!empty($newPercentage)) {
            $this->rule['percentage'] = $newPercentage;
        }
        /* Fallback Error Calculation*/
        
        if ((int) $this->rule['percentage'] === 0)
        {
            return false;
        }

        $scrappedStep = (int) ceil($upperLimit / $this->rule['percentage']);
        for ($step = $scrappedStep; $step <= 100; $step = $step + $scrappedStep)
        {
            if ((int) $this->rule['hitsCount'] === $step - 1)
            {
                return true;
            }
        }
        return false;
    }
    
    private function determineFlatPercentageBuffer($upperLimit, $percentage)
    {
        $scrappedStep = (int) ceil($upperLimit / $percentage);
        if ((int) $this->rule['hitsCount'] < $scrappedStep) {
            return false;
        }
        
        $thresholdLowerPencentage = (int) ((1 / (($scrappedStep * 2) - 1)) * 100);
        $actualPercentage = (int) (($this->rule['scrappedCount'] / $this->rule['hitsCount']) * 100);
        if($actualPercentage < $thresholdLowerPencentage) {
            $newPercentage = $percentage + ($percentage - $actualPercentage);
            return $newPercentage;
        }
        return false;
    }
    
    protected function determineByRandomScrapper()
    {
        if (empty($this->rule['percentage']))
        {
            return;
        }

        $scrappedCount = $this->rule['scrappedCount'];
        $hitsCount = $this->rule['hitsCount'];
        $upperLimit = $this->getUpperLimit($this->rule['percentage']);
        Logger::write('Upper Limit', $upperLimit);
        $maxScrapLimit = (float) (($upperLimit * $this->rule['percentage']) / 100.00);
        Logger::write('Max Scrap Limit', $maxScrapLimit);
        $pendingScrap = (float) ($maxScrapLimit - $scrappedCount);
        Logger::write('Pending Scrap', $pendingScrap);
        $currentCounter = (float) $hitsCount;
        Logger::write('Current Counter', $currentCounter);
        $pr = (float) ($pendingScrap / ($upperLimit - $currentCounter));
        Logger::write('Probability', $pr);

        if (((float) mt_rand(1, $upperLimit) / (float) $upperLimit) <= $pr)
        {
            return true;
        }
        else
        {

            return false;
        }
    }

    public function setEngine()
    {
        $engine = Session::get('extensions.trafficLoadBalancer.orderFilterDetials.engine');
        if ($this->remoteEnable)
        {
            switch ($engine)
            {
                case 'round_robin':
                    $scrappingMethod = 'flat';
                    break;
                case 'randomize':
                case 'random':
                case 'conversion_rate':
                default:
                    $scrappingMethod = 'random';
            }
        }
        
        $this->scrappingMethod = $scrappingMethod;
    }
    
    protected function getUpperLimit($percentage)
    {
        $this->setEngine();
        
        switch ($this->scrappingMethod)
        {
            case 'timestamp':return 100;
            case 'flat':return 100;
            case 'random':
            default:break;
        }

        if ((int) $percentage === 100 || (int) $percentage === 0)
        {
            return 100;
        }
        $number2 = 100 - (int) $percentage;
        $number1 = (int) $percentage;
        $gcd = $this->gcd($number1, $number2);
        return (int) ($number1 / $gcd) + (int) ($number2 / $gcd);
    }
    
    protected function gcd($number1, $number2)
    {
        while ($number2)
        {
            $temp = $number2;
            $number2 = $number1 % $number2;
            $number1 = $temp;
        }
        return $number1;
    }
    
    private function getCurrentStepRuleForBin($binArray)
    {
        $this->percent = $binArray['main_percentage'];
        if($this->scrapperStepId > 1) {
            $this->percent = $binArray['upsell_percentage'];
        }
        $rule = $this->dbConnection->table($this->binTableName)
                ->select('id', 'percentage')
                ->where('scrapStep', $this->scrapperStepId)
                ->where('bin', $binArray['bin'])
                ->first();
        
        if ($rule !== null && is_array($rule))
        {
            $rule['percentage'] = (int) $rule['percentage'];
        }

        if ($rule === null)
        {
            $this->insertRule(array(
                    'percentage' => $this->percent,
                    'bin' => $binArray['bin'],
                    )
                );
        }
        else if (
                $rule['percentage'] !== $this->percent)
        {
            $this->updateRuleForBin($rule['id'], array(
                'scrappedCount' => 0,
                'hitsCount' => 0,
                'percentage' => $this->percent
            ));
        }
        
        $rule = $this->dbConnection->table($this->binTableName)
                ->select(
                        'id', 'scrappedCount', 'hitsCount', 'percentage', 'scrapped', 'hits', 'bin'
                )
                ->where('scrapStep', $this->scrapperStepId)
                ->where('bin', $binArray['bin'])
                ->first();
        
        $rule['percentage'] = (int) $rule['percentage'];

        return $rule;
    }
    
    private function updateRuleForBin($id, $data = array())
    {
        $this->dbConnection->table($this->binTableName)
                ->where('id', $id)->update($data);
    }
    
    private function insertRule($data = array())
    {
        $rule = array_merge(array(
            'scrappedCount' => 0,
            'hitsCount' => 0,
            'scrapped' => 0,
            'hits' => 0,
            'percentage' => 0,
            'scrapStep' => $this->scrapperStepId,
            'bin' => null
                ), $data);
        $this->dbConnection->table($this->binTableName)->insert($rule);
    }
    
    private function getDatabaseConnection()
    {
        $factory = new ConnectionFactory();
        return $factory->make(array(
                    'driver' => 'sqlite',
                    'database' => STORAGE_DIR . DS . 'trafficlbnew.sqlite',
        ));
    }
    
    public function incrementHit()
    {
        try
        {
            $binList = Session::get('extensions.trafficLoadBalancer.orderFilterDetials.binList');
            if(empty($binList)) {
                return;
            }
            
            $isBinEnabled = Session::get(
                sprintf('extensions.trafficLoadBalancer.bin.%d.binChecked', $this->scrapperStepId)
            );
            
            if (empty($isBinEnabled))
            {
                return;
            }

            if (Session::get(
                            sprintf(
                                    'extensions.trafficLoadBalancer.%d.binCommitted', $this->scrapperStepId
                            )
                    ) == true)
            {
                return;
            }
            if (CrmResponse::has('orderId') !== true)
            {
                return;
            }

            $currentLoadBalancer = Session::get(
                            sprintf('extensions.trafficLoadBalancer.bin.%d', $this->scrapperStepId)
            );

            $this->dbConnection = $this->getDatabaseConnection();

            $this->rule = $this->getRuleById($currentLoadBalancer['binRuleId']);
            
            if (empty($this->rule))
            {
                return;
            }

            $upperLimit = $this->getUpperLimit($this->rule['percentage']);

            $data = array(
                'hitsCount' => ($this->rule['hitsCount'] + 1) % $upperLimit,
                'hits' => $this->rule['hits'] + 1,
            );

            $isDisablePrepaidOrderFiler = Config::extensionsConfig('TrafficLoadBalancerNew.default_settings.disable_prepaid_orderfilter');

            if ($isDisablePrepaidOrderFiler && Session::has('steps.meta.isPrepaidFlow'))
            {
                $data = array(
                    'hitsCount' => ($this->rule['hitsCount']) % $upperLimit,
                    'hits' => $this->rule['hits'],
                );
            }

            if (
                    $currentLoadBalancer['binScrapped'] && !$isDisablePrepaidOrderFiler && Session::has('steps.meta.isPrepaidFlow') ||
                    $currentLoadBalancer['binScrapped'] && !Session::has('steps.meta.isPrepaidFlow')
            )
            {
                $data['scrappedCount'] = $this->rule['scrappedCount'] + 1;
                $data['scrapped'] = $this->rule['scrapped'] + 1;
            }

            if ($data['hitsCount'] == 0)
            {
                $data['scrappedCount'] = 0;
            }

            $this->dbConnection->table($this->binTableName)
                    ->where('id', $currentLoadBalancer['binRuleId'])
                    ->update($data);

            
            Session::set(
                    sprintf(
                            'extensions.trafficLoadBalancer.%d.binCommitted', $this->scrapperStepId
                    ), true
            );
            Session::set(
                    sprintf(
                            'extensions.trafficLoadBalancer.%d.committed', $this->scrapperStepId
                    ), true
            );
        }
        catch (Exception $ex)
        {
            
        }
    }
    
    private function getRuleById($id)
    {
        return $this->dbConnection->table($this->binTableName)
                        ->select(
                                'id', 'scrappedCount', 'hitsCount', 'percentage', 'scrapped', 'hits', 'bin'
                        )->find($id);
    }

}
