# -*- coding: utf-8 -*-

# Copyright 2012-2016 Mir Calculate. http://www.calculate-linux.org
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
from calculate.core.server.local_call import Display, Methods, has_force_arg
from calculate.lib.utils.tools import unpack_single_opts

import calculate.contrib
from suds import WebFault
from suds.transport import TransportError
from .client_class import Client_suds
import traceback as tb
import time
import logging
import os
import sys
import threading
import urllib.request as urllib2
from urllib.error import URLError
from .pid_information import client_list_methods
from .cert_func import (client_post_auth, client_post_request, client_get_cert,
                       client_post_cert, get_password_from_daemon,
                       clear_password)
from .sid_func import session_clean, client_session_info, client_session_list
from .cert_verify import get_CRL, VerifyError

import M2Crypto
import OpenSSL
from calculate.core.datavars import DataVarsCore
from .client_class import HTTPSClientCertTransport
from .methods_func import call_method, get_method_argparser, parse, get_view
from .function import (MessageReceiver, MessageDispatcher, clear, _print,
                      get_view_params, parse_error)
from calculate.lib.utils.files import makeDirectory, readLinesFile
from calculate.lib.cl_lang import setLocalTranslate

_ = lambda x: x
setLocalTranslate('cl_console3', sys.modules[__name__])


def get_message_receiver(client):
    return MessageReceiver(client, MessageDispatcher(Methods()), Display())


def get_entire_message_receiver(client, pid):
    return MessageReceiver.from_entire(client, pid,
                                       MessageDispatcher(Methods()), Display())


def client_signal(client):
    Vars = DataVarsCore()
    Vars.importCore()
    Vars.flIniFile()
    try:
        client_active = Vars.Get('core.cl_core_client_active_period')
    except:
        client_active = 15
    while True:
        sid = int(client.get_sid())
        try:
            client.service.active_client(sid)
        except:
            print(_('no connection to the server!'))
            raise Exception(1)
        time.sleep(float(client_active))


class StoppableThread(threading.Thread):
    def __init__(self):
        super().__init__()
        self._thread_stopped = threading.Event()
        self._pause = threading.Event()
        self._paused = threading.Event()

    def run(self):
        l = ['|', '/', '-', '\\', '|', '/', '-', '\\']
        while True:
            for i in l:
                sys.stdout.write("\r\r" + i)
                sys.stdout.flush()
                time.sleep(.1)
                while self.paused() and not self.stopped():
                    self._paused.set()
                if self.stopped():
                    sys.stdout.write("\b")
                    sys.stdout.flush()
                    return 0

    def pause(self):
        self._pause.set()
        while not self._paused.is_set():
            self._paused.clear()
            sys.stdout.write("\r")
            sys.stdout.flush()

    def resume(self):
        self._pause.clear()

    def stop(self):
        self._thread_stopped.set()

    def stopped(self):
        return self._thread_stopped.isSet()

    def paused(self):
        return self._pause.isSet()


def connect_with_cert(cert, path_to_cert, url, args, wait_thread, clVarsCore,
                      crypto_Error, Connect_Error):
    flag_thread_start = False
    cert_name = cert
    CERT_FILE = os.path.join(path_to_cert, cert_name + '.crt')
    CERT_KEY = os.path.join(path_to_cert, cert_name + '.key')
    if not os.path.isfile(CERT_FILE) or not os.path.isfile(CERT_KEY):
        Connect_Error = 1
        return None, 1, crypto_Error, False, None
    client = None

    bio = M2Crypto.BIO.openfile(CERT_KEY)
    rsa = M2Crypto.m2.rsa_read_key(bio._ptr(), lambda *unused: "")
    store_passwd = None
    if not rsa:
        port = args.port or clVarsCore.Get('core.cl_core_port')
        store_passwd = get_password_from_daemon(args.host, port, wait_thread)
    key_passwd = store_passwd
    er = None
    try:
        ca_certs = os.path.join(path_to_cert, 'ca/ca_root.crt')
        client = Client_suds(
            url, transport=HTTPSClientCertTransport(
                CERT_KEY, CERT_FILE, path_to_cert, password=key_passwd,
                ca_certs=ca_certs, wait_thread=wait_thread))
        if not wait_thread.is_alive():
            wait_thread = StoppableThread()
            flag_thread_start = True
            wait_thread.start()
        client.wsdl.services[0].setlocation(url)
        client.set_parameters(path_to_cert, CERT_FILE, CERT_KEY, args.host)
        wait_thread.stop()
        client_post_cert(client, clVarsCore)
        Connect_Error = 0
    except VerifyError as e:
        Connect_Error = 1
        er = e
    except OpenSSL.crypto.Error as e:
        Connect_Error = 1
        crypto_Error = 1
        er = e
    except URLError as e:
        Connect_Error = 1
        er = e
    except Exception as e:
        er = e
        if e == 3:
            wait_thread.stop()
            sys.exit(1)
        Connect_Error = 1
    if flag_thread_start:
        wait_thread.stop()
    return (client, Connect_Error, crypto_Error,
            True if store_passwd is not None else False, er)


def get_server_hostname(host, path_to_cert):
    compliance_file = os.path.join(path_to_cert, 'compliance_server_names')
    if not os.path.isfile(compliance_file):
        fd = open(compliance_file, 'w')
        fd.close()
    for line in readLinesFile(compliance_file):
        adress, server_hostname = line.split(' ', 1)
        if adress == host:
            return server_hostname
    return None


def add_server_hostname(host, path_to_cert, server_hostname):
    try:
        compliance_file = os.path.join(path_to_cert, 'compliance_server_names')
        if not os.path.isfile(compliance_file):
            fd = open(compliance_file, 'w')
            fd.close()
        temp_file = ''
        find_flag = False
        for line in readLinesFile(compliance_file):
            adress, temp_server_hostname = line.split(' ', 1)
            if adress == host:
                temp_file += "%s %s\n" % (adress, server_hostname)
                find_flag = True
            else:
                temp_file += line + '\n'
        if not find_flag:
            temp_file += "%s %s\n" % (host, server_hostname)
        fd = open(compliance_file, 'w')
        fd.write(temp_file)
        fd.close()
        return True
    except Exception as e:
        print(e)
        return False


def https_server(client, args, unknown_args, url, clVarsCore, wait_thread):
    client_post_auth(client)

    if args.stop_consoled:
        wait_thread.stop()
        os.system('cl-consoled --stop')
        return 0

    if args.session_clean:
        wait_thread.stop()
        session_clean(client)

    if args.session_info or args.session_num_info:
        wait_thread.stop()
        client_session_info(client, args.session_num_info)
        return 0

    if args.session_list:
        wait_thread.stop()
        client_session_list(client)
        return 0

    if args.pid_res == "list":
        from .pid_information import client_pid_info
        wait_thread.stop()
        client_pid_info(client, wait_thread)
        return 0

    try:
        client.frame_period = clVarsCore.Get('core.cl_core_get_frame_period')
    except:
        client.frame_period = 2

    if args.pid_res:
        wait_thread.stop()
        if not args.pid_res.isdigit():
            print(_("Wrong PID"))
            return 1
        mr = get_entire_message_receiver(client, int(args.pid_res))
        if mr:
            mr.get_messages()
            if not args.keep_result:
                client.service.clear_pid_cache(client.sid, int(args.pid_res))
            return 0
        else:
            return 1

    if args.pid_kill:
        wait_thread.stop()
        from .pid_information import client_pid_kill

        return client_pid_kill(client, args.pid_kill)

    retCode = 0
    if not args.method:
        wait_thread.stop()
        client_list_methods(client)
        return 1

    elif args.method and args.help:
        # вычислить наличие флага --force
        method_parser = None
        try:
            dispatch_usenew = has_force_arg(unknown_args)
            while True:
                view_params = get_view_params(client, args.method + '_view',
                                              step=None, expert=True,
                                              onlyhelp=True,
                                              dispatch_usenew=dispatch_usenew)
                view = get_view(client, args.method, client.sid, view_params)
                method_parser = get_method_argparser(view, args)

                _unknown_args = method_parser.fixBoolVariables(unknown_args)

                _args, _unknown_args = method_parser.parse_known_args(
                    _unknown_args)

                if dispatch_usenew == _args.no_questions:
                    break
                else:
                    client.service.clear_method_cache(client.sid, args.method)
                    dispatch_usenew = _args.no_questions
        finally:
            wait_thread.stop()
            sys.stdout.write("\b")
            sys.stdout.flush()
        client.service.clear_method_cache(client.sid, args.method)
        method_parser.print_help()
    else:
        method_result = call_method(client, args, unknown_args, wait_thread)
        mr = get_message_receiver(client)
        if method_result:
            client.no_progress = args.no_progress
            try:
                mr.analysis(method_result)
                # analysis(client, client.sid, method_result)
            except URLError as e:
                _print(parse_error(e))
            except KeyboardInterrupt:
                try:
                    print()
                    mess = method_result[0][0]
                    pid = int(mess.message)
                    result = client.service.pid_kill(pid, client.sid)
                    if result in [0, 2]:
                        print(_('Process terminated'))
                    elif result == -1:
                        print(_("Certificate not found on the server"))
                    elif result == -2:
                        print(_("Session not matching your certificate"))
                    elif result == 1:
                        print(_("Failed to terminate the process"))
                    mr.analysis(method_result)
                except Exception as e:
                    _print(parse_error(e))

            try:
                mess = method_result[0][0]
                pid = int(mess.message)
            except:
                return 1
            retCode = 1 if int(
                client.service.pid_info(client.sid, pid)[0][1]) else 0
            if not args.keep_result:
                client.service.clear_pid_cache(client.sid, pid)
        client.service.clear_method_cache(client.sid, args.method)
    wait_thread.stop()
    return retCode


def main(wait_thread):
    parser = parse()
    args, unknown_args = parser.parse_known_args(
        list(unpack_single_opts(sys.argv[1:])))
    wait_thread.start()

    sym_link = os.path.basename(sys.argv[0])
    if sym_link != 'cl-console':
        import calculate.core.server.loaded_methods as loaded_methods

        ob = DataVarsCore()
        ob.importCore()

        # set var env
        if not ob.flIniFile():
            sys.exit(1)
        # cl_wsdl = ob.Get('cl_wsdl')
        cl_wsdl = ob.Get('cl_wsdl_available')
        # создать симлинки на команды
        from calculate.core.server.func import initialization

        initialization(cl_wsdl)
        ob.close()
        if sym_link in loaded_methods.LoadedMethods.conMethods.keys():
            args.method = loaded_methods.LoadedMethods.conMethods[sym_link][0]
        args.host = 'localhost'

    if not args.method and args.help:
        wait_thread.stop()
        sys.stdout.write('\r')
        sys.stdout.flush()
        parser.print_help()
        return 0

    if not args.method:
        if unknown_args:
            wait_thread.stop()
            sys.stdout.write('\r')
            sys.stdout.flush()
            args = parser.parse_args(
                list(unpack_single_opts(sys.argv[1:])))

    logging.basicConfig(level=logging.FATAL)
    logging.getLogger('suds.client').setLevel(logging.FATAL)
    logging.getLogger('suds.transport').setLevel(logging.FATAL)
    logging.getLogger('suds.transport.http').setLevel(logging.FATAL)
    logging.getLogger('suds.umx.typed').setLevel(logging.ERROR)

    clVarsCore = DataVarsCore()
    clVarsCore.importCore()
    clVarsCore.flIniFile()

    homePath = clVarsCore.Get('ur_home_path')

    port = args.port or clVarsCore.GetInteger('core.cl_core_port')
    host = args.host
    path_to_cert = args.path_to_cert
    
    if not path_to_cert:
        path_to_cert = clVarsCore.Get('core.cl_client_cert_dir')
    path_to_cert = path_to_cert.replace("~", homePath)

    for dirs in ['', 'ca', 'trusted']:
        dir_path = os.path.join(path_to_cert, dirs)
        if not os.path.isdir(dir_path):
            if not makeDirectory(dir_path):
                wait_thread.stop()
                sys.stdout.write('\r')
                sys.stdout.flush()
                print(_("Failed to create directory %s") % dir_path)
                return 1

    if args.update_crl:
        wait_thread.stop()
        getCRL = threading.Thread(target=get_CRL, args=(path_to_cert,))
        getCRL.start()
        getCRL.join()
        print('CRL updated')
        return 0

    if args.by_host:
        wait_thread.stop()
        client_post_request(path_to_cert, args)
        return 0
    if args.from_host:
        wait_thread.stop()
        client_get_cert(path_to_cert, args)
        return 0

    url = "https://%s:%s/?wsdl" % (host, port)
    
    try:
        from calculate.lib.utils.dbus_tools import run_dbus_core
        run_dbus_core(host, port)
    except ImportError:
        pass

    clear()
    serv_hostname = get_server_hostname(host, path_to_cert)
    get_name_flag = False
    client = None
    
    if serv_hostname:
        Connect_Error = 1
        crypto_Error = 0
        client, Connect_Error, crypto_Error, passwd_flag, e = \
            connect_with_cert(serv_hostname, path_to_cert, url, args,
                              wait_thread, clVarsCore, crypto_Error,
                              Connect_Error)
        if not wait_thread.is_alive():
            wait_thread = StoppableThread()
            wait_thread.start()
        get_name_flag = True
        if Connect_Error:
            if crypto_Error and passwd_flag:
                wait_thread.stop()
                sys.stdout.write('\r')
                sys.stdout.flush()
                print(_('Invalid password'))
                # delete password from daemon list
                clear_password(host, port)
            elif e:
                wait_thread.stop()
                if type(e) != int:
                    print(_('Error: '), e)
            get_name_flag = False

    if get_name_flag:
        if client:
            try:
                client.port = port
                return_val = 1
                try:
                    return_val = https_server(client, args, unknown_args, url,
                                              clVarsCore, wait_thread)
                except URLError as e:
                    print(_('Error: '), e)
                except KeyboardInterrupt:
                    wait_thread.stop()
                    red = '\n' + '\033[31m * \033[0m'
                    print(red + _("Manually interrupted"))
                except Exception as e:
                    wait_thread.stop()
                    if type(e) != int:
                        if e:
                            print(e)
                        elif e.args:
                            print(e)
                    return 1
                wait_thread.stop()
                return return_val
            except WebFault as f:
                print(_("Exception: %s") % f)
                _print(f.fault)
            except TransportError as te:
                print(_("Exception: %s") % te)
            except KeyboardInterrupt:
                wait_thread.stop()
                red = '\n' + '\033[31m * \033[0m'
                print(red + _("Manually interrupted"))
            except Exception as e:
                print(_("Exception: %s") % e)
                tb.print_exc()
        wait_thread.stop()

    server_host_name = ""
    try:
        client = Client_suds(
            url, transport=HTTPSClientCertTransport(None, None, path_to_cert))
        client.wsdl.services[0].setlocation(url)
        server_host_name = client.service.get_server_host_name()
        if not add_server_hostname(host, path_to_cert, server_host_name):
            print('compliance_file write error!')
        del client
    except URLError as e:
        wait_thread.stop()
        print('\b' + _('Failed to connect') + ':', e)
        return 1
    except KeyboardInterrupt:
        wait_thread.stop()
        red = '\n' + '\033[31m * \033[0m'
        print(red + _("Manually interrupted"))

    try:
        import glob

        all_cert_list = glob.glob(os.path.join(path_to_cert, '*.crt'))
        fit_cert_list = []
        for client_cert_path in all_cert_list:
            client_cert = client_cert_path.replace(path_to_cert, '')
            client_cert_name = client_cert.replace('.crt', '')
            if server_host_name.endswith(client_cert_name):
                fit_cert_list.append(client_cert_name)
        fit_cert_list.sort(key=len)
        Connect_Error = 1
        crypto_Error = 0
        e = None
        passwd_flag = False

        for i in range(0, len(fit_cert_list)):
            cert_name = fit_cert_list.pop()
            client, Connect_Error, crypto_Error, passwd_flag, e = \
                connect_with_cert(cert_name, path_to_cert, url, args,
                                  wait_thread, clVarsCore, crypto_Error,
                                  Connect_Error)
            if not wait_thread.is_alive():
                wait_thread = StoppableThread()
                wait_thread.start()

            if Connect_Error == 0:
                break

        # If the certificate file misses
        if Connect_Error:
            if crypto_Error and passwd_flag:
                wait_thread.stop()
                sys.stdout.write('\r')
                sys.stdout.flush()
                print(_('Invalid password'))
                # delete password from daemon list
                clear_password(host, port)
                return 1
            elif e:
                wait_thread.stop()
                if type(e) != int:
                    print(_('Error: '), e)
                return 1

            CERT_FILE = None
            CERT_KEY = None
            client = Client_suds(
                url, transport=HTTPSClientCertTransport(CERT_KEY, CERT_FILE,
                                                        path_to_cert))
            client.wsdl.services[0].setlocation(url)
            client.set_parameters(path_to_cert, CERT_FILE, CERT_KEY, args.host)

        client.port = port
        return_val = 1
        try:
            return_val = https_server(client, args, unknown_args, url,
                                      clVarsCore, wait_thread)
        except URLError as e:
            print(_('Error: '), e)
        except KeyboardInterrupt:
            wait_thread.stop()
            red = '\n' + '\033[31m * \033[0m'
            print(red + _("Manually interrupted"))
        except Exception as e:
            wait_thread.stop()
            if type(e) != int:
                if e:
                    print(e)
                elif e.args:
                    print(e)
                    #                tb.print_exc()
            return 1
        wait_thread.stop()
        return return_val
    # ----------------------------------------------------
    except WebFault as f:
        print(_("Exception: %s") % f)
        _print(f.fault)
    except TransportError as te:
        print(_("Exception: %s") % te)
    except KeyboardInterrupt:
        wait_thread.stop()
        red = '\n' + '\033[31m * \033[0m'
        print(red + _("Manually interrupted"))
    except Exception as e:
        print(_("Exception: %s") % e)
        tb.print_exc()
    wait_thread.stop()
