# -*- coding: utf-8 -*-
import logging

from odoo import models, tools
from odoo.http import request
from odoo.tools import config, ustr
import odoo
from core.middleware import utils
from .geoip_resolver import GeoIPResolver

_logger = logging.getLogger(__name__)

# global resolver (GeoIP API is thread-safe, for multithreaded workers)
# This avoids blowing up open files limit
odoo._geoip_resolver = None


class IrHttp(models.AbstractModel):
    _inherit = 'ir.http'

    _geoip_resolver = None  # backwards-compatibility

    @classmethod
    def _geoip_setup_resolver(cls):
        # Lazy init of GeoIP resolver
        if cls._geoip_resolver is not None:
            return
        if odoo._geoip_resolver is not None:
            cls._geoip_resolver = odoo._geoip_resolver
            return
        geofile = config.get('geoip_database')
        try:
            odoo._geoip_resolver = GeoIPResolver.open(geofile) or False
        except Exception as e:
            _logger.warning('Cannot load GeoIP: %s', ustr(e))

    @classmethod
    def _geoip_resolve(cls):
        if 'geoip' not in request.session:
            record = {}
            # get login ip
            http_headers = request.httprequest.headers
            # via nginx
            real_ip = http_headers.get('X-Real-IP', '')
            # via ip
            if not real_ip:
                real_ip = request.httprequest.remote_addr
            if odoo._geoip_resolver and real_ip:
                record = odoo._geoip_resolver.resolve(real_ip) or {}
            request.session['geoip'] = record

    @classmethod
    def _dispatch(cls):
        """ Before executing the endpoint method, validate access ip, add website params on request, such as
                - geoip dict data are added in the session
            Then follow the parent dispatching.
            Reminder :  Do not use `request.env` before authentication phase, otherwise the env
                        set on request will be created with uid=None (and it is a lazy property)
        """
        try:
            func, arguments = cls._find_handler()
            allow_ip = func.routing.get('ip', False)
            allow_ip_name = func.routing.get('ip_name', False)
            remote_ip = cls._get_remote_ip()
            is_validate_ip = cls._validate_remote_ip(allow_ip, allow_ip_name, remote_ip)
            if not is_validate_ip:
                raise odoo.exceptions.AccessDenied()
        except Exception as e:
            return cls._handle_exception(e)
        
        # geoip

        cls._geoip_setup_resolver()
        cls._geoip_resolve()

        resp = super(IrHttp, cls)._dispatch()
        return resp

    @classmethod
    def _validate_remote_ip(cls, allow_ip, allow_ip_name, remote_ip):
        """
        如果不设置allow_ip和allow_ip_name, 不对ip做校验。
        :param allow_ip:
        :param allow_ip_name:
        :param remote_ip:
        :return:
        """
        if not utils.is_prod():
            _logger.debug('DEV/QA env, NOT CHECK REMOTE IP')
            return True
        if not allow_ip and not allow_ip_name:
            return True
        if not remote_ip:
            _logger.warning("cat not get remote ip, please check nginx configuration item:X-Remote-IP")
            return False
        _logger.info('remote_ip:%s, allow_ip:%s' % (remote_ip, allow_ip))
        _logger.info('remote_ip:%s, allow_ip_name:%s' % (remote_ip, allow_ip_name))
        allow_ip_name_list = []
        if allow_ip_name and isinstance(allow_ip_name, basestring):
            allow_ip_name_list.append(allow_ip_name)
        if allow_ip_name and isinstance(allow_ip_name, list):
            allow_ip_name_list.extend(allow_ip_name)
        if allow_ip_name and isinstance(allow_ip_name, tuple):
            allow_ip_name_list.extend(list(allow_ip_name))
        allow_ip_list = cls._get_ip_list_by_name(allow_ip_name_list)
        if allow_ip and isinstance(allow_ip, basestring):
            allow_ip_list.append(allow_ip)
        if allow_ip and isinstance(allow_ip, list):
            allow_ip_list.extend(allow_ip)
        if allow_ip and isinstance(allow_ip, tuple):
            allow_ip_list.extend(list(allow_ip))

        remote_dot_ip = [int(item) for item in remote_ip.split(".")]

        def validate(cfg_ip):
            dot_ip = cfg_ip.strip().split(".")
            index = 0
            for int_num in remote_dot_ip:
                if len(dot_ip) > index:
                    if dot_ip[index] == '*':
                        continue
                    if int(dot_ip[index]) != int_num:
                        return False
                index += 1
            return True

        for ip in allow_ip_list:
            if validate(ip):
                return True
        return False

    @classmethod
    def _get_ip_list_by_name(cls, ip_names):
        """
        从配置文件获取ip列表，配置文件格式: name = ip1,ip2,ip3
        :param ip_names:
        :return:
        """
        result = []
        for cfg_name in ip_names:
            cfg_value = tools.config.get(cfg_name, '')
            if cfg_value:
                tmp = cfg_value.split(",")
                if tmp:
                    result.extend(tmp)
        return result

    @classmethod
    def _get_remote_ip(cls):
        """
        nginx中的配置
        location / {
            proxy_set_header X-Remote-IP  $http_x_real_ip;
            proxy_set_header X-args     $args;
            proxy_pass http://odoo-sz-01;
        }
        """
        http_headers = request.httprequest.headers
        real_ip = http_headers.get('X-Remote-IP', '')
        if not real_ip:
            real_ip = http_headers.get('X-Real-IP', '')
        x_args = http_headers.get('X-args', '')
        _logger.debug('X-Remote-IP:X-args:' + real_ip + ':' + x_args)
        # 如果不经过nginx，直接返回remote_addr
        if not real_ip:
            real_ip = request.httprequest.remote_addr
        return real_ip