import sys
import math
import json
import copy
import time
import queue
import random
import datetime
import threading
import traceback
import importlib
import tools_str_sim
import tools_language
import multiprocessing
from dateutil.relativedelta import *
from acrcloud_record import recordWorker
from acrcloud_callback import postManager
from tools_data_filter_local import ResultFilter

sys.path.append('../')
import acrcloud_logger

NORESULT = 'noResult'

class Acrcloud_Result:
    def __init__(self, mainQueue, resultQueue, config):
        self._config = config
        self._log_dir = self._config['log']['dir']
        self._mainQueue = mainQueue
        self._resultQueue = resultQueue
        self._callbackQueue = multiprocessing.Queue()
        self._recordQueue = multiprocessing.Queue()
        self.init_logger()
        self.data_backup = Backup(self._config, self._recordQueue, self._callbackQueue, self.dlog)
        self.init_record()
        self.init_callback_manager()
        self.dlog.logger.warning('Warn@Acrcloud_Result Init Success!')

    def init_logger(self):
        self.dlog = acrcloud_logger.AcrcloudLogger('LocalResult')
        self.dlog.addFilehandler('local_result.log', logdir=self._log_dir)
        self.dlog.addStreamHandler()

    def init_record(self):
        try:
            self.record_proc = multiprocessing.Process(target = recordWorker,
                                                       args = (self._config, self._recordQueue))
            self.record_proc.daemon = True
            self.record_proc.start()
            if not self.record_proc.is_alive():
                self.dlog.logger.error('Error@Acrcloud_Result.record worker run failed, exit')
                sys.exit(1)
            else:
                self.dlog.logger.warning('Warn@Acrcloud_Result.init_record success')
        except Exception as e:
            self.dlog.logger.error('Error@Acrcloud_Result.init_record', exc_info=True)

    def init_callback_manager(self):
        try:
            self.callback_manager_proc = multiprocessing.Process(target=postManager, args=(self._callbackQueue,))
            self.callback_manager_proc.daemon = True
            self.callback_manager_proc.start()
            if not self.callback_manager_proc.is_alive():
                self.dlog.logger.error('Error@Acrcloud_Result.init_callback_manager run failed, exit')
                sys.exit(1)
            else:
                self.dlog.logger.warning('Warn@Acrcloud_Result.init_callback_manager success')
        except Exception as e:
            self.dlog.logger.error('Error@Acrcloud_Result.init_callback_manager', exc_info=True)


    def deal_result(self, resinfo):
        try:
            self.data_backup.save_one(resinfo)
        except Exception as e:
            self.dlog.logger.error('Error@Acrcloud_Result.deal_result', exc_info=True)

    def start(self):
        self._running = True
        while 1:
            if not self._running:
                break
            try:
                maininfo = self._mainQueue.get(block = False)
                if maininfo and maininfo[0] == 'stop':
                    self.stop()
            except queue.Empty:
                time.sleep(0.01)

            try:
                resinfo = self._resultQueue.get(block = False)
            except queue.Empty:
                if random.random() < 0.1:
                    time.sleep(0.02)
                continue

            self.deal_result(resinfo)

            if random.random() < 0.1:
                self.dlog.logger.warning('Warn@resultQueue.qsize.{0}'.format(self._resultQueue.qsize()))

    def stop(self):
        self._running = False
        self.dlog.logger.warning('Warn@Acrcloud_Result_Stop')
        sys.exit(1)



class Backup:

    def __init__(self, config, recordQueue, callbackQueue, dlog):
        self._config = config
        db_config = self._config['database']
        self._recordQueue = recordQueue
        self._callbackQueue = callbackQueue
        self._redis_map = {}
        self.dlog = dlog
        self._mdb = None
        if db_config.get('enabled', 1) and db_config.get('host'):
            if db_config.get('type') == 'mysql':
                acrdb = importlib.import_module('acrcloud_mysql')
                MysqlManager = acrdb.MysqlManager
                self._mdb = MysqlManager(host=db_config['host'],
                                     port=db_config['port'],
                                     user=db_config['user'],
                                     passwd=db_config['passwd'],
                                     dbname=db_config['db'])
            elif db_config.get('type') == 'sqlite3':
                acrdb = importlib.import_module('acrcloud_sqlite3')
                Sqlite3Manager = acrdb.Sqlite3Manager
                self._mdb = Sqlite3Manager(db_config.get('sqlite3_db_fpath', './sqlite3_db/results.db'))
            else:
                acrdb = importlib.import_module('acrcloud_psql')
                PsqlManager = acrdb.PsqlManager
                self._mdb = PsqlManager(host=db_config['host'],
                                     port=db_config['port'],
                                     user=db_config['user'],
                                     passwd=db_config['passwd'],
                                     dbname=db_config['db'])
        self._log_dir = self._config['log']['dir']
        self._result_filter = ResultFilter(self.dlog, self._log_dir)
        self._tools_lan = tools_language.tools_language()

    def filter_chinese(self, stream_id, result):
        try:
            if not result:
                return ''
            if 'metainfos' in result:
                return result
            if 'metadata' in result and 'custom_files' in result['metadata']:
                return result

            music = result['metadata']['music'][0]
            if self._tools_lan.has_chinese(music['title']):
                self.dlog.logger.warning('Lan@StreamID: {0}, Title Has Chinese: {1}'.format(stream_id,music['title']))
                return ''
            if self._tools_lan.has_chinese(music['artists'][0]['name']):
                self.dlog.logger.warning('Lan@StreamID: {0}, Artists Has Chinese: {1}'.format(stream_id,music['artists'][0]['name']))
                return ''
            if music.get('label') and self._tools_lan.has_chinese(music['label']):
                result['metadata']['music'][0]['label'] = ''
            if self._tools_lan.has_chinese(music['album']['name']):
                result['metadata']['music'][0]['album']['name'] = ''
            return result
        except Exception as e:
            self.dlog.logger.error('Error@filter_chinese, error data:{0}'.format(json.dumps(result)), exc_info=True)
        return result

    def format_timestamp(self, timestr):
        return datetime.datetime.strptime(timestr, '%Y-%m-%d %H:%M:%S').strftime('%Y%m%d%H%M%S')

    def save_one_uniq(self, old_data, isCustom=0):
        filter_chinese = old_data.get('filter_chinese', 0)
        if 'response' in old_data['result']:
            old_data['result'] = old_data['result']['response']

        old_data['pem_file'] = ''

        data = None
        if isCustom:
            data, is_new = self._result_filter.deal_real_custom(old_data)
        else:
            data, is_new = self._result_filter.deal_real_history(old_data)
        if data is None:
            return False

        access_key = data.get('access_key', 'unknow')
        stream_url = data.get('stream_url', 'unknow')
        stream_id = data.get('stream_id', 'unknow')

        if isCustom:
            title = self._result_filter.get_mutil_result_acrid(data, 'custom')[0]
        else:
            title = self._result_filter.get_mutil_result_title(data, 'music', 1)[0]

        if isCustom:
            redis_name = 'monitor_custom_{0}_{1}'.format(access_key, stream_id)
        else:
            redis_name = 'monitor_{0}_{1}'.format(access_key, stream_id)
        if redis_name not in self._redis_map:
            self._redis_map[redis_name] = {'title':'', 'info': ''}

        redis_info = self._redis_map[redis_name]
        redis_title = redis_info['title']
        if title == NORESULT:
            pass

        if redis_title != title :
            self._redis_map[redis_name]['title'] = title
            self._redis_map[redis_name]['info'] = json.dumps(data)

        if is_new and NORESULT != title:
            result = data.get('result', {})
            if result and filter_chinese:
                result = self.filter_chinese(stream_id, result)

            if result:
                data['result'] = result

                #post result to callback url
                try:
                    postdata = copy.deepcopy(data)
                    self._callbackQueue.put(json.dumps(postdata))
                except Exception as e:
                    self.dlog.logger.error('Error@save_one_uniq.send_to_callback', exc_info=True)

                if self._mdb:
                    now_date = datetime.datetime.utcnow().strftime('%Y-%m-%d')
                    params = (access_key,
                              stream_url,
                              stream_id,
                              json.dumps(result),
                              data.get('timestamp'),
                              now_date)
                    try:
                        self._mdb.insert_results(params)
                        return True
                    except Exception as e:
                        self.dlog.logger.error('Error@save_one_uniq.db_execute', exc_info=True)
        return False

    def save_one_delay(self, old_data, isCustom=0):
        filter_chinese = old_data.get("filter_chinese", 0)
        if 'response' in old_data['result']:
            old_data['result'] = old_data['result']['response']

        if (isCustom==0 and old_data.get('record', [0,0,0])[0] in [2,3]) or (isCustom==1 and old_data.get('record', [0,0,0])[0]==1):
            record_add_data = copy.deepcopy(old_data)
            self._recordQueue.put(('add', record_add_data))

        old_data['pem_file'] = ''

        data = None
        if isCustom:
            data = self._result_filter.deal_delay_custom(old_data)
        else:
            data = self._result_filter.deal_delay_history(old_data)
        if data is None:
            return False

        access_key = data.get('access_key', 'unknow')
        stream_url = data.get('stream_url', 'unknow')
        stream_id = data.get('stream_id', 'unknow')

        result = data.get('result', {})
        if result and filter_chinese:
            result = self.filter_chinese(stream_id, result)
        if result:
            data['result'] = result
            try:
                if (isCustom==0 and old_data.get('record', [0,0,0])[0] in [2,3]) or (isCustom==1 and old_data.get('record', [0,0,0])[0] in [1,3]):
                    self._recordQueue.put(('save', data))
                    result['metadata']['record_timestamp'] = self.format_timestamp(data['timestamp'])
                    self.dlog.logger.info('Send To Record (streamID:{0}, isCustom:{1}, record:{2})'.format(stream_id, isCustom, ','.join([ str(i) for i in data.get('record', [0,0,0])])))
            except Exception as e:
                self.dlog.logger.error('Error@save_one_delay.record.save_audio', exc_info=True)

            #post result to callback url
            try:
                postdata = copy.deepcopy(data)
                self._callbackQueue.put(json.dumps(postdata))
            except Exception as e:
                self.dlog.logger.error('Error@save_one_uniq.send_to_callback', exc_info=True)

            if self._mdb:
                now_date = datetime.datetime.utcnow().strftime('%Y-%m-%d')
                params = (
                        access_key,
                        stream_url,
                        stream_id,
                        json.dumps(result),
                        data.get('timestamp'),
                        now_date
                    )
                try:
                    self._mdb.insert_results(params)
                    return True
                except Exception as e:
                    self.dlog.logger.error('Error@save_one_delay.db_execute', exc_info=True)
        return False

    def utc2local(self, utc_str):
        try:
            local_str = ''
            utc = datetime.datetime.strptime(utc_str, '%Y-%m-%d %H:%M:%S')
            epoch = time.mktime(utc.timetuple())
            offset = datetime.datetime.fromtimestamp (epoch) - datetime.datetime.utcfromtimestamp (epoch)
            local = utc + offset
            local_str = local.strftime('%Y-%m-%d %H:%M:%S')
        except Exception as e:
            self.dlog.logger.error('Error@utc2local, utc:{0}'.format(utc_str), exc_info=True)
        return local_str

    def save_one(self, jsondata):
        try:
            timestamp = jsondata['timestamp']
            local_timestamp = self.utc2local(timestamp)
            if jsondata['result']['status']['code'] != 0:
                jsondata['result']['metadata'] = {'timestamp_utc':timestamp}
                jsondata['result']['metadata'] = {'timestamp_local':local_timestamp}
            elif 'metadata' in jsondata['result']:
                jsondata['result']['metadata']['timestamp_utc'] = timestamp
                jsondata['result']['metadata']['timestamp_local'] = local_timestamp

            ret = False
            if jsondata.get('delay') == 0 or jsondata.get('delay') == False:
                custom_data = copy.deepcopy(jsondata)
                if jsondata['result']['status']['code'] != 0:
                    ret = self.save_one_uniq(jsondata, 0)
                    ret = self.save_one_uniq(custom_data, 1)
                elif 'metadata' in jsondata['result'] and 'custom_files' in jsondata['result']['metadata']:
                    if 'music' in jsondata['result']['metadata']:
                        del custom_data['result']['metadata']['music']
                        del jsondata['result']['metadata']['custom_files']
                        ret = self.save_one_uniq(jsondata, 0)
                    else:
                        jsondata['result'] = { 'status': { 'msg': 'No result', 'code': 1001, 'version': '1.0'}}
                        ret = self.save_one_uniq(jsondata, 0)
                    ret = self.save_one_uniq(custom_data, 1)
                elif 'metadata' in jsondata['result'] and 'music' in jsondata['result']['metadata']:
                    custom_data['result'] = {'status': {'msg': 'No result', 'code': 1001, 'version': '1.0'}}
                    ret = self.save_one_uniq(custom_data, 1)
                    ret = self.save_one_uniq(jsondata, 0)

            elif jsondata.get('delay') == 1 or jsondata.get('delay') == True:
                custom_data = copy.deepcopy(jsondata)
                if jsondata['result']['status']['code'] != 0:
                    ret = self.save_one_delay(jsondata, 0)
                    ret = self.save_one_delay(custom_data, 1)
                elif 'metadata' in jsondata['result'] and 'custom_files' in jsondata['result']['metadata']:
                    if 'music' in jsondata['result']['metadata']:
                        del custom_data['result']['metadata']['music']
                        del jsondata['result']['metadata']['custom_files']
                        ret = self.save_one_delay(jsondata, 0)
                    else:
                        jsondata['result'] = {'status': {'msg': 'No result', 'code': 1001, 'version': '1.0'}}
                        ret = self.save_one_delay(jsondata, 0)
                    ret = self.save_one_delay(custom_data, 1)
                elif 'metadata' in jsondata['result'] and 'music' in jsondata['result']['metadata']:
                    custom_data['result'] = {'status': {'msg': 'No result', 'code': 1001, 'version': '1.0'}}
                    ret = self.save_one_delay(custom_data, 1)
                    ret = self.save_one_delay(jsondata, 0)
            if ret:
                self.dlog.logger.info('MSG@Result_Save_Success({0})'.format(jsondata.get('stream_id')))
        except Exception as e:
            self.dlog.logger.error('Error@save_one', exc_info=True)
            self.dlog.logger.error('Error_Data:{0}, {1}'.format(jsondata.get('stream_id'), jsondata.get('result')))
        return ret

