<?php

namespace Velis\Bpm\CronJob;

use Velis\CronJob;
use Velis\Output;
use Velis\Bpm\Currency\RateType;
use Exception;

/**
 * NBP currency rate import job
 * @author Olek Procki <olo@velis.pl>
 */
class CurrencyImport extends CronJob
{
    /**
     * NBP API base url
     */
    const API_URL   = 'http://api.nbp.pl/api/exchangerates/tables/';

    /**
     * Query parameters
     */
    const API_QUERY = '/last/7?format=json';


    const REQUEST_BREAK     = 10;
    const REFRESH_BREAK     = 30;
    const MAX_REFRESH_COUNT = 5;


    /**
     * Currency rate fields used in currency rate tables
     * @var array
     */
    protected static $_rateTableFields = [
        RateType::NBP_MEAN  => 'mid',
        RateType::NBP_SALES => 'ask'
    ];

    /**
     * Supported currencies (may be overloaded to support more currencies)
     * @var array
     */
    protected static $_supportedCurrencies = [
        'EUR',
        'USD'
    ];


    /**
     * Number of refresh responses
     * @var int
     */
    protected $_refreshCount = 0;


    /**
     * Get currency rates from NBP API
     * @return CurrencyImport
     */
    protected function _getData()
    {
        $rates = [];

        $requestCount = 0;

        foreach (RateType::listCached() as $rateTypeId => $rateType) {
            $rateTables = explode(',', $rateType['nbp_table']);
            foreach ($rateTables as $table) {
                loopBegin:
                if ($requestCount++) {
                    sleep(self::REQUEST_BREAK);
                }
                $response = file_get_contents(self::API_URL . trim($table) . self::API_QUERY);
                if ($response === false) {
                    throw new Exception("Couldn't fetch data or connect to NBP API");
                }
                try {
                    $responseData = Output::jsonDecode($response);
                } catch (Exception $e) {
                    if ($this->_refreshCount > self::MAX_REFRESH_COUNT) {
                        throw $e;
                    }
                    sleep(self::REFRESH_BREAK);
                    $this->_refreshCount++;
                    goto loopBegin;
                }
                if (!count($responseData)) {
                    throw new Exception("NBP API response has no data");
                }

                foreach ($responseData as $key => $dailyRates) {
                    foreach ($dailyRates['rates'] as $rate) {
                        if (in_array($rate['code'], static::$_supportedCurrencies)) {
                            $rates[] = [
                                'currency_code'         => $rate['code'],
                                'valid_from'            => $dailyRates['effectiveDate'],
                                'currency_rate'         => str_replace(',', '.', $rate[self::$_rateTableFields[$rateTypeId]]),
                                'currency_rate_type_id' => $rateTypeId,
                                'is_current'            => $key == 6 ? 1 : 0
                            ];
                        }
                    }
                }
            }
        }

        return $rates;
    }

    /**
     * Import currency
     * @return CurrencyImport
     */
    protected function _import()
    {
        self::$_db->checkConnection();
        self::$_db->startTrans();

        $sql = "BEGIN;
                TRUNCATE TABLE app.currency_rate_import_tab;
                TRUNCATE TABLE app.currency_rate_current_tab;\n";

        $values = [];

        $ratesStmt = self::$_db->prepare(
            "INSERT INTO app.currency_rate_tab (
                currency_code,
                valid_from,
                currency_rate,
                currency_rate_type_id
            ) VALUES (
                :currency_code,
                :valid_from,
                :currency_rate,
                :currency_rate_type_id
            )"
        );

        $currentStmt = self::$_db->prepare(
            "UPDATE app.currency_rate_current_tab SET currency_rate = :currency_rate
             WHERE currency_code = :currency_code
                   AND currency_rate_type_id = :currency_rate_type_id"
        );

        foreach ($this->_getData() as $rate) {
            $isCurrent = $rate['is_current'];
            unset($rate['is_current']);

            $ratesStmt->execute($rate);
            $values[] = "('{$rate['currency_code']}', '{$rate['valid_from']}', {$rate['currency_rate']}, '{$rate['currency_rate_type_id']}', {$isCurrent})";

            if ($isCurrent) {
                unset($rate['valid_from']);
                $currentStmt->execute($rate);
            }
        }

        $sql .= "INSERT INTO app.currency_rate_import_tab VALUES\n";
        $sql .= implode(",\n", $values) . ";\n";

        $sql .= "INSERT INTO app.currency_rate_tab
                 SELECT cri.currency_code,
                        cri.valid_from,
                        cri.currency_rate,
                        cri.currency_rate_type_id
                 FROM app.currency_rate_import_tab cri JOIN app.currency_tab c USING(currency_code);\n";

        $sql .= "INSERT INTO app.currency_rate_current_tab
                 SELECT cri.currency_code,
                        cri.currency_rate_type_id,
                        cri.currency_rate
                 FROM app.currency_rate_import_tab cri JOIN app.currency_tab c USING(currency_code)
                 WHERE is_current = 1;\n";

        $sql .= "COMMIT;";

        // Save SQL script into file
        file_put_contents(DATA_PATH . 'currency-rate-import.sql', $sql);

        self::$_db->commit();
    }

    /**
     * test
     * @return string
     */
    public function test()
    {
        print_r($this->_getData());
    }


    /**
     * Executes cron job
     */
    public function run()
    {
        try {
            $this
            ->_initMutex()
            ->_import();

            if ($this->_refreshCount) {
                $this->_log(self::STATUS_SUCCESS, 'Currency rates imported, refresh count: ' . $this->_refreshCount);
            } else {
                $this->_log(self::STATUS_SUCCESS, 'Currency rates imported');
            }
        } catch (\Velis\Mutex\Exception $me) {
            $this->_log(self::STATUS_WARNING, (string)$me);
        } catch (\Exception $e) {
            $this->_log(self::STATUS_FAILED, (string)$e);
        }
    }
}
