import re
import urllib2
import os

import aps2.function
import aps2.uLogging
import aps2.printer


STRING = "string"
INTEGER = "integer"
BOOLEAN = "boolean"
NUMBER = "number"

os.environ['APP_PATH'] = '/usr/local/share/applications/pycloud/'


class Obj(object):
    pass


class PathVerb(object):
    def __init__(self, path, verb):
        self.path = path
        self.verb = verb


def get_valid_pair_path_verbs(tree_schemas):
    list_valid_paths = []

    for oper in tree_schemas.schema[0].get("operations", []):
        path = tree_schemas.schema[0]["operations"][oper]["path"]
        verb = tree_schemas.schema[0]["operations"][oper]["verb"]
        list_valid_paths.append(PathVerb(path, verb))

    for impl in tree_schemas.list_schema:
        list_valid_paths.extend(get_valid_pair_path_verbs(impl))

    return list_valid_paths


def is_match(path_verb, REQUEST_METHOD, PATH_INFO, name_service):
    if path_verb.verb != REQUEST_METHOD:
        aps2.uLogging.debug("False")
        return False

    aps2.uLogging.debug("path_verb.path = " + path_verb.path)
    aps2.uLogging.debug("path_verb.verb = " + path_verb.verb)

    aps2.uLogging.debug("REQUEST_METHOD = " + REQUEST_METHOD)
    aps2.uLogging.debug("PATH_INFO = " + PATH_INFO)

    aps2.uLogging.debug("is_match(REQUEST_METHOD = " + REQUEST_METHOD + ", PATH_INFO = " + PATH_INFO + ", name_service = " + name_service + "):")

    if re.match("/" + name_service, PATH_INFO):
        PATH_INFO = PATH_INFO[len(name_service)+1:]

    aps2.uLogging.debug("PATH_INFO = " + PATH_INFO)

    if re.match("/[\w-]{36}", PATH_INFO):
        PATH_INFO = PATH_INFO[37:]

    aps2.uLogging.debug("PATH_INFO = " + PATH_INFO)
    if PATH_INFO == "":
        PATH_INFO = "/"

    aps2.uLogging.debug("PATH_INFO = " + PATH_INFO)

    pattern = get_pattern_by_path(path_verb.path)

    if pattern.match(PATH_INFO):
        aps2.uLogging.debug("True!!!")
        aps2.uLogging.debug("path_verb.path = " + path_verb.path)
        aps2.uLogging.debug("path_verb.verb = " + path_verb.verb)
        return True
    else:
        aps2.uLogging.debug("False")
        return False


def find_appropriate_pair_path_verbs(list_valid_path_verbs, parsed_environ, service_description):
    name_service = service_description.name
    aps2.uLogging.debug("name_service = " + name_service)
    for pv in list_valid_path_verbs:
        if is_match(pv, parsed_environ.get("REQUEST_METHOD"), parsed_environ.get("PATH_INFO"), name_service):
            return pv
    raise Exception("Not found appropriate path and verb for PATH_INFO: " + parsed_environ.get("PATH_INFO")
                    + ", REQUEST_METHOD = " + parsed_environ.get("REQUEST_METHOD"))


def get_pattern_by_path(path):
    if path.find('{') != -1:
        name_in_path = path[:path.find('{')-1]         # name_in_path = "/calculateSomething"
        params_in_path = path[len(name_in_path)+1:]       # params_in_path = "{pathparam}/{ps}"
        re_valid_pattern = name_in_path
        for param_i in re.split("/", params_in_path):
            param_i = param_i[1:len(param_i)-1]
            re_valid_pattern += "/(?P<" + param_i + ">[0-9a-zA-Z_]*)"  # TODO fix [a-zA-Z][0-9a-zA-Z_]*
    else:
        re_valid_pattern = path

    re_valid_pattern = re_valid_pattern

    aps2.uLogging.debug("pattern = " + re_valid_pattern)
    return re.compile(re_valid_pattern)


def get_function_description(parsed_environ, service_description):
    aps2.uLogging.debug("get_function_description in units")

    list_valid_paths = get_valid_pair_path_verbs(service_description.tree_schemas)
    path_verb = find_appropriate_pair_path_verbs(list_valid_paths, parsed_environ, service_description)

    return get_FuncDesc_by_path_verb(path_verb, service_description.tree_schemas)


def get_FuncDesc_by_path_verb(path_verb, tree_schemas):
    for oper in tree_schemas.schema[0].get("operations", []):
        path = tree_schemas.schema[0]["operations"][oper]["path"]
        verb = tree_schemas.schema[0]["operations"][oper]["verb"]

        if verb == path_verb.verb and path == path_verb.path:
            return aps2.function.FunctionDescription(tree_schemas.schema[0]["operations"][oper], oper)

    for impl in tree_schemas.list_schema:
        func_desc = get_FuncDesc_by_path_verb(path_verb, impl)
        if func_desc:
            return func_desc


def get_constructor(controller_client, service):

    class_name = service.tree_schemas.schema[0]["name"]
    appmeta_namespace = re.search('\{(.*)\}', controller_client.appmeta.tag).group(1)
    services = controller_client.appmeta.findall(".//{" + appmeta_namespace + "}service")

    script_path = ""
    for serv in services:
        if serv.get('id') == service.name:
            script_path = serv.findall(".//{" + appmeta_namespace + "}code")[0].get('path')  # TODO
            script_path = script_path[script_path.find('/')+1:script_path.rfind('.')]
            break
    aps2.uLogging.debug("script_path = " + script_path)
    import_script = __import__(script_path)
    constructor = getattr(import_script, class_name)

    return constructor


class Property(object):
    def __init__(self, name, json):
        self.name = name
        self.json = json


def get_properties(schema):
    properties = []
    json_properties = schema[0]["properties"]
    for prop in json_properties.items():
        properties.append(Property(prop[0], json_properties[prop[0]]))
    return properties


def get_aps_id(name_service, PATH_INFO):
    len_service_id = len('/' + name_service + '/')
    if len_service_id > len(PATH_INFO):
        return "NOT_SET"
    string_without_service_id = PATH_INFO[len_service_id:]
    pattern = re.compile('[\w-]{36}')
    if pattern.match(string_without_service_id):
        aps_id = string_without_service_id[:36]
        return aps_id
    else:
        return "NOT_SET"


def load_application_object(parsed_environ, controller_client, service_description, function_description):
    constructor = get_constructor(controller_client, service_description)

    if function_description.json.get("static", "false") == "true":
        return constructor

    resource = constructor()
    properties = get_properties(service_description.tree_schemas.schema)

    # if verb == POST: content not exist. Content should loud from body,
    # else : loud content with APS_API.GET(aps_id) and full resource
    if function_description.json["verb"] == "POST" and function_description.json["path"] == "/":
        content = parsed_environ.get("BODY")
    else:
        content = controller_client.get_old_json(get_aps_id(service_description.name, parsed_environ.get("PATH_INFO")))

    for prop in properties:
        setattr(resource, prop.name, get_obj_by_type(content[prop.name], prop.json["type"]))

    return resource


def str2bool(v):
    return v.lower() in ("true", "t", "1")


def get_obj_by_complex_type(value, type_param, current_structures):  # TODO FIX
    return "NOT SET STRING"


def get_obj_by_type(value, type_param, current_structures=None):
    if type_param == STRING:
        return urllib2.unquote(value)
    elif type_param == INTEGER:
        return int(value)
    elif type_param == BOOLEAN:
        return str2bool(value)
    elif type_param == NUMBER:
        return float(value)
    else:
        obj = get_obj_by_complex_type(value, type_param, current_structures)
        return obj


def save_certificate_if_necessary(parsed_environ, controller_client):
    """
    The certificate is stored if the call is POST with path: "/".
    """
    aps2.uLogging.debug("save_certificate_if_necessary in units")

    service_name = find_name_service(parsed_environ.get("PATH_INFO"), controller_client.list_services)
    cur_path = get_current_path(parsed_environ, service_name)

    aps2.uLogging.debug("cur_path = " + cur_path)
    if parsed_environ.get("REQUEST_METHOD") == "POST" and cur_path == "/":
        aps_self = parsed_environ.get("BODY")["aps"]["x509"]["self"]
        aps_controller = parsed_environ.get("BODY")["aps"]["x509"]["controller"]

        path_certificate_pem = \
            os.environ['APP_PATH'] + '/config/' + parsed_environ.get("HTTP_APS_INSTANCE_ID") + '.pem'
        path_certificate_apsc_pem = \
            os.environ['APP_PATH'] + '/config/' + parsed_environ.get("HTTP_APS_INSTANCE_ID") + '.apsc.pem'

        try:
            dir_config = os.environ['APP_PATH'] + 'config/'
            if not os.path.exists(dir_config):
                os.makedirs(dir_config)
            with open(path_certificate_pem, 'w') as outfile:
                outfile.write(aps_self)
            with open(path_certificate_apsc_pem, 'w') as outfile:
                outfile.write(aps_controller)
        except OSError:
            raise Exception("Cannot make directory 'config/'")
        except IOError:
            raise Exception("Certificate cannot open")


def find_name_service(path_info, list_services):
    for item in list_services:
        pattern = re.compile('/' + item)
        if pattern.match(path_info):
            return item

    raise Exception("Error: service_id in PATH_INFO not exist!")


def get_current_path(parsed_environ, name_service):
    path_info = parsed_environ.get("PATH_INFO")
    if re.match("/" + name_service, path_info):
        path_info = path_info[len(name_service)+1:]

    if re.match("/[\w-]{36}", path_info):
        path_info = path_info[37:]

    if path_info == "":
        path_info = "/"

    return path_info
