<?php

class CallProtection
{

    const SCORELIST_ENTRY_NUMBER = 'number';
    const SCORELIST_ENTRY_SCORE = 'score';
    const SCORELIST_ENTRY_COMMENTS = 'comments';
    const SCORELIST_ENTRY_COUNTRY = 'country';
    const SCORELIST_ENTRY_NUMBER_PREFIX = 'numberPrefix';
    const SCORELIST_ENTRY_SEARCH_REQUEST = 'searchRequest';
    const SCORELIST_ENTRY_CALLER_TYPE = 'callerType';
    const SCORELIST_ENTRY_CALLER_NAME = 'callerName';
    const SCORELIST_ENTRY_PREFIX_NAME = 'prefixName';
    const SCORELIST_ENTRY_DEEP_LINK = 'deepLink';
    const SCORELIST_ENTRY_CALLER_TYPE_ID = 'callerTypeId';
    const SCORELIST_ENTRY_LAST_COMMENT = 'lastComment';
    const MAX_IMPORT_LIMIT = 100;
    const API_MIN_SCORE = 7;

    public static $headerValues = array();
    private static $runtimePath = '';
    private static $importDatetime = '';
    private static $importProgress = 0;
    private static $deleteProgress = 0;
    private static $maxImportEntries = 0;
    private static $maxDeleteEntries = 0;
    private static $deleteEntriesLeft = 0;
    private static $currentImportLines = array();
    private static $logAttribute = '';

    public static function getCurlData()
    {
        $apiKey = self::getAPIKey();
        if ($apiKey) {
            $lang = self::getLangKey();
            $url = 'https://www.tellows.de/stats/partnerscoredata';
            $params = array(
                'partner' => app()->params->tellowsApiName,
                'apikey' => $apiKey,
                'country' => $lang,
                'lang' => 'en',
                'mosttagged' => 1,
                'minscore' => self::getminScore(),
                'mincomments' => 2,
                'showdeeplink' => 1,
                'showcallertypeid' => 1,
                'showcallername' => 1,
                'showprefixname' => 1,
                'showlastcomment' => 1
            );
            $response = CurlHelper::sendGetRequest($url, $params);
            if (isset($response['httpCode']) && $response['httpCode'] != 200) {
                return false;
            }
            return $response['content'];
        }
        Log::w("call protection api key is empty", 'CallProtectionCronJob');
        return false;
    }

    public static function getAPIKey()
    {
        $cfg = cfg('CALL_PROTECTION_API_KEY');
        if (!empty($cfg)) {
            return $cfg;
        }
        return false;
    }

    private static function getLangKey()
    {
        $language = substr(cfg('GUI_LANGS'), 0, 2);
        return $language;
    }

    private static function getminScore()
    {
        $minScore = cfg('CALL_PROTECTION_SCORE');
        if (!empty($minScore)) {
            return $minScore;
        }
        return self::API_MIN_SCORE;
    }

    public static function importScoreEntries($logAttribute)
    {
        self::$runtimePath = app()->runtimePath . '/tellows.csv';
        self::$logAttribute = $logAttribute;
        $job = Job::model()->findByAttributes(array('type' => Job::CallProtectionImport, 'status' => Job::Inserted));

        if ($job) {
            self::$importDatetime = PhonalisaDate::newServerDate();
            self::$maxDeleteEntries = ScoreListEntry::model()->countAllByAttributes(array());
            self::$deleteEntriesLeft = self::$maxDeleteEntries;
            self::removeTempFile();

            $data = self::getCurlData();

            if (!$data) {
                self::saveJobModel($job, Job::Failed);
                return self::writeImportError();
            }

            if (self::requestHasError($data)) {
                return self::saveJobModel($job, Job::Failed);
            }

            if (!self::writeCurlDataToFile($data)) {
                self::createAlert(__('CALL_PROTECTION_ALERT_SHORT_DESCR'), __('CALL_PROTECTION_IMPORT_ERROR'));
                self::saveJobModel($job, Job::Failed);
                return self::finnishScoreListEntryImport("error", 0);
            }

            self::setCurrentImportLines();
            self::createScorelistEntries();
            self::updateImportJob($job);
            self::writeRunningInProgressToScoreListEntryStatus();
        } else {
            $job = Job::model()->findByAttributes(array('type' => Job::CallProtectionImport));
            if (!$job) {
                self::createAlert(__('CALL_PROTECTION_ALERT_SHORT_DESCR'), __('CALL_PROTECTION_IMPORT_ERROR'));
                return self::finnishScoreListEntryImport("error", 0);
            }

            self::setDefaultValuesFromJob($job);
            if (self::$importProgress < 100) {
                self::setCurrentImportLines();
                self::createScorelistEntries();
            } else {
                self::deleteOldScorelistEntries();
            }
            self::updateImportJob($job);
        }
        Log::v('CallProtectionImport status: ' . self::$importProgress . '% imported, ' . self::$deleteProgress . '% deleted.', 'CronController');

        self::removeSpecificLinesFromFile();
    }

    private static function removeTempFile()
    {
        if (file_exists(self::$runtimePath)) {
            unlink(self::$runtimePath);
        }
    }

    private static function saveJobModel($job, $status)
    {
        $meta = json_encode(array(
            'importProgress' => self::$importProgress,
            'createdDatetime' => self::$importDatetime,
            'headerValues' => self::$headerValues,
            'maxImportEntries' => self::$maxImportEntries,
            'deleteProgress' => self::$deleteProgress,
            'maxDeleteEntries' => self::$maxDeleteEntries,
            'deleteEntriesLeft' => self::$deleteEntriesLeft
        ));

        $job->type = Job::CallProtectionImport;
        $job->meta = $meta;
        $job->status = $status;
        $job->save();
    }

    private static function writeImportError()
    {
        $errorMsg = __('CALL_PROTECTION_IMPORT_ERROR');
        self::writeErrorToScoreListEntryStatus($errorMsg);
        Log::e("No data from curl-request!", self::$logAttribute);
        self::finnishScoreListEntryImport("error", 0);
        self::createAlert(__('CALL_PROTECTION_ALERT_SHORT_DESCR'), $errorMsg);
    }

    private static function writeCurlDataToFile($data)
    {
        return file_put_contents(self::$runtimePath, $data);
    }

    private static function createAlert($shortDescr, $descr)
    {
        $alert = new Alert();
        $alert->created = PhonalisaDate::newServerDate();
        $alert->level = Alert::LEVEL_WARNING;
        $alert->component = Alert::COMPONENT_SYSTEM;
        $alert->shortDescr = $shortDescr;
        $alert->descr = $descr;
        if (!$alert->save()) {
            Log::e('Could not save Alert-Model!', self::$logAttribute);
        }
    }

    private static function finnishScoreListEntryImport($status, $entryCount)
    {
        $messageText = 'SCORELIST_ENTRY_STATUS_IMPORT_ENTRY_COUNT';
        $model = self::getScoreListEntryStatusModel();
        $model->status = $status;
        $model->message = __($messageText, true, 'messages', array("{entry}" => $entryCount));
        $model->datetime = self::$importDatetime;
        $model->save();
        Log::v("Add $entryCount ScorelistEntry-Model(s)", self::$logAttribute);
    }

    private static function setCurrentImportLines()
    {
        $resultData = array();
        if (file_exists(self::$runtimePath)) {
            $data = file_get_contents(self::$runtimePath);
            $formatedData = str_replace(array("\r", "\r\n", "\n"), "\\n", $data);
            $explodedData = explode(';\\n', $formatedData);
            self::setProgress(count($explodedData));
            foreach ($explodedData as $key => $value) {
                if ((int) $key >= self::MAX_IMPORT_LIMIT) {
                    break;
                }
                $resultData[$key] = $value;
            }
        } else {
            self::$importProgress = 100;
        }
        self::$currentImportLines = $resultData;
    }

    private static function createScorelistEntries()
    {
        foreach (self::$currentImportLines as $line) {
            if (empty(self::$headerValues)) {
                $headerLine = substr($line, 0, strpos($line, "\\n\\n"));
                self::setHeaderValues($headerLine);
                $line = substr($line, strpos($line, "\\n\\n"));
            }
            $csvPart = explode(';', trim($line, "\\n"));
            if (!isset($csvPart[1])) {
                continue;
            }

            $number = NumberCanonizer::canonize(self::getCsvPartByModelKey($csvPart, self::SCORELIST_ENTRY_NUMBER));
            $scorelistEntry = ScoreListEntry::model()->findByAttributes(array('number' => $number));
            if (!$scorelistEntry) {
                $scorelistEntry = new ScoreListEntry();
            }

            $scorelistEntry->number = $number;
            $scorelistEntry->score = self::getCsvPartByModelKey($csvPart, self::SCORELIST_ENTRY_SCORE);
            $scorelistEntry->comments = self::getCsvPartByModelKey($csvPart, self::SCORELIST_ENTRY_COMMENTS);
            $scorelistEntry->country = self::getCsvPartByModelKey($csvPart, self::SCORELIST_ENTRY_COUNTRY);
            $scorelistEntry->numberPrefix = self::getCsvPartByModelKey($csvPart, self::SCORELIST_ENTRY_NUMBER_PREFIX);
            $scorelistEntry->searchRequest = self::getCsvPartByModelKey($csvPart, self::SCORELIST_ENTRY_SEARCH_REQUEST);
            $scorelistEntry->callerType = self::getCsvPartByModelKey($csvPart, self::SCORELIST_ENTRY_CALLER_TYPE);
            $scorelistEntry->callerName = self::getCsvPartByModelKey($csvPart, self::SCORELIST_ENTRY_CALLER_NAME);
            $scorelistEntry->prefixName = self::getCsvPartByModelKey($csvPart, self::SCORELIST_ENTRY_PREFIX_NAME);
            $scorelistEntry->deepLink = self::getCsvPartByModelKey($csvPart, self::SCORELIST_ENTRY_DEEP_LINK);
            $scorelistEntry->callerTypeId = self::getCsvPartByModelKey($csvPart, self::SCORELIST_ENTRY_CALLER_TYPE_ID);
            $scorelistEntry->lastComment = self::getCsvPartByModelKey($csvPart, self::SCORELIST_ENTRY_LAST_COMMENT);
            $scorelistEntry->created = self::$importDatetime;
            $scorelistEntry->save();
        }
    }

    private static function updateImportJob($job)
    {
        $status = Job::Running;
        if (self::$importProgress == 100 && self::$deleteProgress == 100) {
            $status = Job::Finished;
            self::finnishScoreListEntryImport("ok", self::$maxImportEntries);
        }
        self::saveJobModel($job, $status);
    }

    private static function setDefaultValuesFromJob($job)
    {
        $meta = json_decode($job->meta);
        self::$headerValues = (array) @$meta->headerValues;
        self::$importDatetime = @$meta->createdDatetime;
        self::$importProgress = @(int) $meta->importProgress;
        self::$deleteProgress = @(int) $meta->deleteProgress;
        self::$maxImportEntries = @(int) $meta->maxImportEntries;
        self::$maxDeleteEntries = @(int) $meta->maxDeleteEntries;
        self::$deleteEntriesLeft = @(int) $meta->deleteEntriesLeft;
    }

    private static function setProgress($currentCount)
    {
        if (empty(self::$maxImportEntries)) {
            self::$maxImportEntries = $currentCount;
        }
        if ($currentCount === self::$maxImportEntries) {
            self::$importProgress = 0;
        } else {
            self::$importProgress = round(((self::$maxImportEntries - $currentCount) / self::$maxImportEntries) * 100);
        }
    }

    private static function removeSpecificLinesFromFile()
    {
        $lastLine = end(self::$currentImportLines);
        $convertedSearchString = ltrim(rtrim($lastLine, ';') . ';', '\\n');
        if (strlen($convertedSearchString) > 1) {
            $oldData = file_get_contents(self::$runtimePath);
            $startPos = strpos($oldData, $convertedSearchString) + strlen($convertedSearchString) + 2;
            $newData = substr($oldData, $startPos);
            self::writeCurlDataToFile($newData);
        } else {
            self::removeTempFile();
        }
    }

    private static function requestHasError($data)
    {
        if (count($data) === 1 && endsWith($data, 'API Key not valid')) {
            $errorMsg = substr($data, strpos($data, "NOT"));
            self::writeErrorToScoreListEntryStatus($errorMsg);
            self::createAlert(__('CALL_PROTECTION_ALERT_SHORT_DESCR'), $errorMsg);
            Log::e($errorMsg, self::$logAttribute);
            return true;
        }
        return false;
    }

    private static function writeErrorToScoreListEntryStatus($errorMsg)
    {
        $model = self::getScoreListEntryStatusModel();
        $model->status = 'error';
        $model->message = $errorMsg;
        $model->datetime = self::$importDatetime;
        $model->save();
    }

    private static function writeRunningInProgressToScoreListEntryStatus()
    {
        $model = self::getScoreListEntryStatusModel();
        $model->status = 'runs';
        $model->message = __('CALL_PROTECTION_IMPORT_RUNNING_TEXT');
        $model->datetime = self::$importDatetime;
        $model->save();
    }

    private static function getScoreListEntryStatusModel()
    {
        $oldModel = ScoreListEntryStatus::model()->findByAttributes(array());
        if ($oldModel) {
            return $oldModel;
        }
        return new ScoreListEntryStatus();
    }

    private static function deleteOldScorelistEntries()
    {
        $models = ScoreListEntry::model()->find(array(), 'created', self::MAX_IMPORT_LIMIT);
        self::$deleteEntriesLeft = self::$deleteEntriesLeft - count($models);
        foreach ($models as $model) {
            if ($model->created == self::$importDatetime) {
                self::$deleteProgress = 100;
                return;
            }
            $model->delete();
        }
        self::$deleteProgress = round((self::$deleteEntriesLeft / self::$maxDeleteEntries) * 100);
    }

    private static function setHeaderValues($headerLine)
    {
        $header = explode(';', trim($headerLine, "\\n"));
        foreach ($header as $key => $value) {
            $keyName = self::getModelKeyFromHeaderKeys($value);
            self::$headerValues[$keyName] = $key;
        }
    }

    private static function getModelKeyFromHeaderKeys($headerKey)
    {
        $keys = self::getHeaderKeys();
        $result = '';
        $headerKey = str_replace(' ', '', strtolower($headerKey));
        if (isset($keys[$headerKey])) {
            $result = $keys[$headerKey];
        }
        return $result;
    }

    private static function getHeaderKeys()
    {
        return array(
            'phonenumber' => self::SCORELIST_ENTRY_NUMBER,
            'score' => self::SCORELIST_ENTRY_SCORE,
            'comments' => self::SCORELIST_ENTRY_COMMENTS,
            'country' => self::SCORELIST_ENTRY_COUNTRY,
            'areacode' => self::SCORELIST_ENTRY_NUMBER_PREFIX,
            'searchrequests' => self::SCORELIST_ENTRY_SEARCH_REQUEST,
            'anruftyp' => self::SCORELIST_ENTRY_CALLER_TYPE,
            'anrufname' => self::SCORELIST_ENTRY_CALLER_NAME,
            'vorwahlname' => self::SCORELIST_ENTRY_PREFIX_NAME,
            'deeplink' => self::SCORELIST_ENTRY_DEEP_LINK,
            'callertypeid' => self::SCORELIST_ENTRY_CALLER_TYPE_ID,
            'lastcomment' => self::SCORELIST_ENTRY_LAST_COMMENT
        );
    }

    private static function getCsvPartByModelKey($csvPart, $modelKey)
    {
        if (isset(self::$headerValues[$modelKey])) {
            $headerKey = self::$headerValues[$modelKey];
            return $csvPart[$headerKey];
        }
        return '';
    }
}
