<?php
namespace Plugin\RedeemCode;

class RedeemCode {
    private $_codesTable = WE_PREFIX.'WEBENGINE_REDEEMCODE';
    private $_logsTable = WE_PREFIX.'WEBENGINE_REDEEMCODE_LOGS';
    private $_codeTypes = array('regular','limited','account');
    private $_modulesPath = 'modules';
    private $_sqlPath = 'sql';
    private $_realm;
    private $_code;
    private $_codeType;
    private $_limit = null;
    private $_user = null;
    private $_configId;
    private $_reward;
    private $_checkIp = 0;
    private $_requiredLevel = 0;
    private $_requiredResets = 0;
    private $_id;
    private $db;
    private $configFilePath;
    private $_usercpmenu = array(
        array('active'=>true,'type'=>'internal','phrase'=>'redeemcode_title','link'=>'redeem/code','icon'=>'usercp_default.png','visibility'=>'user','newtab'=>false,'order'=>121)
    );

    public function __construct() {
        $this->_realm = class_exists('NexoraRealm') ? \NexoraRealm::active() : null;
        $this->db = \Connection::Database('Me_MuOnline', $this->_realm);
        $this->configFilePath = self::configPath($this->_realm);
        $this->loadConfig();
        $this->checkTables();
    }

    public static function configPath($realm=null) {
        if(class_exists('NexoraRealm')) {
            try {
                $realm = \NexoraRealm::validate($realm === null ? \NexoraRealm::active() : $realm);
                $candidate = __PATH_REDEEMCODE_ROOT__.'config.'.$realm.'.xml';
                if(is_file($candidate)) return $candidate;
            } catch(\Throwable $e) {}
        }
        return __PATH_REDEEMCODE_ROOT__.'config.xml';
    }

    public function getRealm() { return $this->_realm; }
    public function getCodesTable() { return $this->_codesTable; }
    public function getLogsTable() { return $this->_logsTable; }

    public function loadModule($module) {
        if(!\Validator::Alpha($module) || !is_file(__PATH_REDEEMCODE_ROOT__.$this->_modulesPath.'/'.$module.'.php')) throw new \Exception(lang('redeemcode_error_4', true));
        if(!@include_once(__PATH_REDEEMCODE_ROOT__.$this->_modulesPath.'/'.$module.'.php')) throw new \Exception(lang('redeemcode_error_4', true));
    }

    public function setId($id) {
        if(!\Validator::UnsignedNumber($id)) return;
        $this->_id=(int)$id;
    }

    public function setCode($code) {
        $code = strtoupper(trim((string)$code));
        if(strlen($code) < 3 || strlen($code) > 50 || !preg_match('/^[A-Z0-9_-]+$/D',$code)) throw new \Exception(lang('redeemcode_error_5',true));
        $this->_code=$code;
    }

    public function setCodeType($type) {
        $type=strtolower(trim((string)$type));
        if(!in_array($type,$this->_codeTypes,true)) throw new \Exception(lang('redeemcode_error_6',true));
        $this->_codeType=$type;
    }

    public function setLimit($limit) {
        if(!\Validator::UnsignedNumber($limit) || (int)$limit<1) return;
        $this->_limit=(int)$limit;
    }

    public function setUser($user) {
        $user=trim((string)$user);
        if(!\Validator::AlphaNumeric($user) || !\Validator::UsernameLength($user)) throw new \Exception(lang('redeemcode_error_7',true));
        $this->_user=$user;
    }

    public function setCreditsConfigId($id) {
        if(!\Validator::UnsignedNumber($id) || (int)$id<1) return;
        $this->_configId=(int)$id;
    }

    public function setReward($reward) {
        if(!\Validator::UnsignedNumber($reward) || (int)$reward<1) return;
        $this->_reward=(int)$reward;
    }

    public function addRewardCode() {
        if(!$this->_code || !$this->_codeType || !$this->_configId || !$this->_reward) throw new \Exception(lang('redeemcode_error_7',true));
        if($this->_codeType==='limited' && !$this->_limit) throw new \Exception(lang('redeemcode_error_7',true));
        if($this->_codeType==='account' && !$this->_user) throw new \Exception(lang('redeemcode_error_7',true));
        $this->validateCreditConfig($this->_configId);
        if($this->codeExists($this->_code)) throw new \Exception(lang('redeemcode_error_8',true));
        $affected=$this->db->query_affected(
            'INSERT INTO '.$this->_codesTable.' (redeem_code,redeem_type,redeem_limit,redeem_user,redeem_credit_config_id,redeem_credit_amount,status) VALUES (?,?,?,?,?,?,0)',
            array($this->_code,$this->_codeType,$this->_codeType==='limited'?$this->_limit:null,$this->_codeType==='account'?$this->_user:null,$this->_configId,$this->_reward)
        );
        if($affected!==1) throw new \Exception('No se pudo crear el código.');
        if(class_exists('NexoraAudit')) \NexoraAudit::log('redeem.code_create','redeem_code',$this->_code,null,array('type'=>$this->_codeType,'reward'=>$this->_reward),$this->_realm);
    }

    public function redeemCode() {
        if(!$this->_code) throw new \Exception(lang('redeemcode_error_9',true));
        if($this->_requiredLevel>0 && !$this->meetsLevelRequirement()) throw new \Exception(langf('redeemcode_error_13',array($this->_requiredLevel)));
        if($this->_requiredResets>0 && !$this->meetsResetsRequirement()) throw new \Exception(langf('redeemcode_error_14',array($this->_requiredResets)));

        $preview=$this->db->query_fetch_single('SELECT redeem_credit_config_id FROM '.$this->_codesTable.' WHERE redeem_code = ?',array($this->_code));
        if(!is_array($preview)) throw new \Exception(lang('redeemcode_error_9',true));
        $configId=(int)$preview['redeem_credit_config_id'];
        $identifier=$this->resolveIdentifier($configId);

        $economy=new \NexoraEconomy($this->_realm);
        $economy->redeemCodeCredits(
            $configId,
            $identifier,
            (string)$_SESSION['username'],
            $this->_code,
            $this->_codesTable,
            $this->_logsTable,
            (bool)$this->_checkIp
        );
    }

    public function getRedeemCodesList() {
        $rows=$this->db->query_fetch('SELECT * FROM '.$this->_codesTable.' ORDER BY id DESC');
        return is_array($rows)?$rows:array();
    }

    public function disableCode($id) {
        if(!\Validator::UnsignedNumber($id)) throw new \Exception('ID inválido.');
        $affected=$this->db->query_affected('UPDATE '.$this->_codesTable.' SET status=1 WHERE id=? AND status=0',array((int)$id));
        if($affected===false) throw new \Exception('No se pudo deshabilitar el código.');
        if(class_exists('NexoraAudit')) \NexoraAudit::log('redeem.code_disable','redeem_code',(int)$id,null,array('disabled'=>true),$this->_realm);
    }

    public function getLogs() {
        if($this->_id) $rows=$this->db->query_fetch('SELECT * FROM '.$this->_logsTable.' WHERE code_id=? ORDER BY id DESC',array($this->_id));
        else $rows=$this->db->query_fetch('SELECT * FROM '.$this->_logsTable.' ORDER BY id DESC');
        return is_array($rows)?$rows:array();
    }

    public function checkPluginUsercpLinks() {
        $cfg=loadConfig('usercp'); if(!is_array($cfg)) $cfg=array();
        $links=array(); foreach($cfg as $row) if(isset($row['link'])) $links[]=$row['link'];
        foreach($this->_usercpmenu as $row) if(!in_array($row['link'],$links,true)) $cfg[]=$row;
        usort($cfg,function($a,$b){return ((int)$a['order'])<=>((int)$b['order']);});
        $json=json_encode($cfg,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
        if($json===false || file_put_contents(__PATH_CONFIGS__.'usercp.json',$json,LOCK_EX)===false) throw new \Exception('No se pudo actualizar el menú UserCP.');
    }

    private function loadConfig() {
        if(!is_file($this->configFilePath)) throw new \Exception(lang('redeemcode_error_2',true));
        $xml=simplexml_load_file($this->configFilePath);
        if(!$xml) throw new \Exception(lang('redeemcode_error_2',true));
        $this->_checkIp=(int)$xml->check_ip;
        $this->_requiredLevel=max(0,(int)$xml->required_level);
        $this->_requiredResets=max(0,(int)$xml->required_resets);
    }

    private function validateCreditConfig($id) {
        $credit=new \CreditSystem(); $credit->setConfigId((int)$id); $cfg=$credit->showConfigs(true);
        if(!is_array($cfg) || !in_array($cfg['config_user_col_id'],array('userid','username','email'),true)) throw new \Exception(lang('redeemcode_error_15',true));
        return $cfg;
    }

    private function resolveIdentifier($id) {
        $cfg=$this->validateCreditConfig($id);
        switch($cfg['config_user_col_id']) {
            case 'userid': return (int)$_SESSION['userid'];
            case 'username': return (string)$_SESSION['username'];
            case 'email':
                $account=(new \Account())->accountInformation($_SESSION['userid']);
                if(!is_array($account) || !isset($account[_CLMN_EMAIL_])) throw new \Exception(lang('redeemcode_error_9',true));
                return (string)$account[_CLMN_EMAIL_];
        }
        throw new \Exception(lang('redeemcode_error_15',true));
    }

    private function codeExists($code) {
        return is_array($this->db->query_fetch_single('SELECT id FROM '.$this->_codesTable.' WHERE redeem_code=?',array($code)));
    }

    private function checkTables() {
        $map=array('WEBENGINE_REDEEMCODE'=>$this->_codesTable,'WEBENGINE_REDEEMCODE_LOGS'=>$this->_logsTable);
        foreach($map as $file=>$table) {
            $exists=$this->db->query_fetch_single("SELECT name FROM sysobjects WHERE xtype='U' AND name = ?",array($table));
            if(!$exists) {
                $path=__PATH_REDEEMCODE_ROOT__.$this->_sqlPath.'/'.$file.'.txt';
                $sql=is_file($path)?file_get_contents($path):'';
                if(!$sql || !$this->db->query(str_replace('{TABLE_NAME}',$table,$sql))) throw new \Exception(lang('redeemcode_error_3',true));
            }
        }
        $this->ensureLogAccountColumn();
    }

    private function ensureLogAccountColumn() {
        $column=$this->db->query_fetch_single("SELECT c.name FROM sys.columns c INNER JOIN sys.objects o ON c.object_id=o.object_id WHERE o.name=? AND c.name='account_username'",array($this->_logsTable));
        if(!$column) {
            if(!$this->db->query('ALTER TABLE '.$this->_logsTable.' ADD account_username varchar(50) NULL')) throw new \Exception(lang('redeemcode_error_3',true));
            $this->db->query('UPDATE '.$this->_logsTable.' SET account_username=user_identifier WHERE account_username IS NULL');
            $this->db->query('CREATE NONCLUSTERED INDEX IX_'.$this->_logsTable.'_code_account ON '.$this->_logsTable.' (code_id,account_username)');
        }
    }

    private function meetsLevelRequirement() {
        $Character=new \Character(); $chars=$Character->AccountCharacter($_SESSION['username']);
        if(!is_array($chars)) return false;
        foreach($chars as $name) { $d=$Character->CharacterData($name); if(is_array($d) && (int)$d[_CLMN_CHR_LVL_] >= $this->_requiredLevel) return true; }
        return false;
    }

    private function meetsResetsRequirement() {
        $Character=new \Character(); $chars=$Character->AccountCharacter($_SESSION['username']);
        if(!is_array($chars)) return false;
        foreach($chars as $name) { $d=$Character->CharacterData($name); if(is_array($d) && (int)$d[_CLMN_CHR_RSTS_] >= $this->_requiredResets) return true; }
        return false;
    }
}
