#!/usr/bin/env python
# coding:utf-8

import json
import os
import sys
import threading
import socket
import time
import re
import select
import collections
import random

current_path = os.path.dirname(os.path.abspath(__file__))
root_path = os.path.abspath(os.path.join(current_path, os.pardir, os.pardir))
top_path = os.path.abspath(os.path.join(root_path, os.pardir, os.pardir))
data_path = os.path.join(top_path, "data", 'smart_router')

python_path = os.path.join(root_path, 'python27', '1.0')
noarch_lib = os.path.join(python_path, 'lib', 'noarch')
sys.path.append(noarch_lib)

import simple_queue
import utils
from dnslib import DNSRecord, DNSHeader, A, AAAA, RR, DNSQuestion, QTYPE

import global_var as g
from xlog import getLogger
xlog = getLogger("smart_router")


def remote_query_dns(domain, type=None):
    if not g.x_tunnel:
        return []

    content, status, response = g.x_tunnel.front_dispatcher.request(
        "GET", "dns.xx-net.net", path="/query?domain=%s" % (domain), timeout=5)

    if status != 200:
        xlog.warn("remote_query_dns fail status:%d", status)
        return []

    if isinstance(content, memoryview):
        content = content.tobytes()

    try:
        rs = json.loads(content)
        ips = rs["ip"]
        g.domain_cache.set_ips(domain, ips)
        return ips
    except Exception as e:
        xlog.warn("remote_query_dns json:%s parse fail:%s", content, e)
        return []


allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$")


def is_valid_hostname(hostname):
    hostname = hostname.upper()
    if len(hostname) > 255:
        return False
    if hostname.endswith("."):
        hostname = hostname[:-1]

    return all(allowed.match(x) for x in hostname.split("."))


class DnsServerList(object):
    def __init__(self):
        self.local_list = self.get_dns_server()

        if g.config.country_code == "CN":
            self.public_list = ['114.114.114.114', "180.76.76.76", "198.15.67.245", "202.46.32.19", "64.214.116.84"]
        else:
            self.public_list = ['8.8.8.8', "208.67.222.222", "209.244.0.3", "8.26.56.26", "37.235.1.174", "91.239.100.100"]

        self.i = 0

    def get_dns_server(self):
        iplist = []
        if os.name == 'nt':
            import ctypes, ctypes.wintypes, struct, socket
            DNS_CONFIG_DNS_SERVER_LIST = 6
            buf = ctypes.create_string_buffer(2048)
            ctypes.windll.dnsapi.DnsQueryConfig(DNS_CONFIG_DNS_SERVER_LIST, 0, None, None, ctypes.byref(buf),
                                                ctypes.byref(ctypes.wintypes.DWORD(len(buf))))
            ipcount = struct.unpack('I', buf[0:4])[0]
            iplist = [socket.inet_ntoa(buf[i:i + 4]) for i in xrange(4, ipcount * 4 + 4, 4)]
        elif os.path.isfile('/etc/resolv.conf'):
            with open('/etc/resolv.conf', 'rb') as fp:
                iplist = re.findall(r'(?m)^nameserver\s+(\S+)', fp.read())

        out_list = []
        for ip in iplist:
            if ip == "127.0.0.1":
                continue
            out_list.append(ip)
            xlog.debug("use local DNS server:%s", ip)

        return out_list

    def get_local(self):
        return self.local_list[self.i]

    def reset_server(self):
        self.i = 0

    def next_server(self):
        self.i += 1
        if self.i >= len(self.local_list):
            self.i = self.i % len(self.local_list)

        xlog.debug("next dns server:%s", self.get_local())

    def get_public(self):
        return random.choice(self.public_list)


class DnsClient(object):
    def __init__(self):
        self.start()

    def start(self):
        self.waiters = {}

        self.dns_server = DnsServerList()

        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.sock.settimeout(1)

        self.running = True
        self.th = threading.Thread(target=self.recv_worker)
        self.th.start()

    def stop(self):
        self.running = False

    def query_over_tcp(self, domain, type=None):
        if type is None:
            types = [1, 28]
        else:
            types = [type]

        ips = []
        for t in types:
            query_time = 0
            for server_ip in self.dns_server.public_list:
                query_time += 1
                if query_time > 3:
                    break

                try:
                    d = DNSRecord(DNSHeader())
                    d.add_question(DNSQuestion(domain, t))
                    a_pkt = d.send(server_ip, 53, tcp=True, timeout=1)
                    p = DNSRecord.parse(a_pkt)
                    if len(p.rr) == 0:
                        xlog.warn("query_over_tcp for %s type:%d server:%s return none",
                                  domain, t, server_ip)
                        continue

                    for r in p.rr:
                        ip = str(r.rdata)

                        if "." in ip and g.ip_region.check_ip(ip):
                            cn = g.ip_region.cn
                        else:
                            cn = "XX"
                        ips.append(ip+"|"+cn)

                    break
                except Exception as e:
                    xlog.warn("query_over_tcp %s type:%s server:%s except:%r", domain, type, server_ip,e)

        if ips:
            g.domain_cache.set_ips(domain, ips, type)

        return ips

    def recv_worker(self):
        while self.running:
            try:
                try:
                    response, server = self.sock.recvfrom(8192)
                    server, port = server
                except Exception as e:
                    # xlog.exception("sock.recvfrom except:%r", e)
                    continue

                if not response:
                    continue

                try:
                    p = DNSRecord.parse(response)
                except Exception as e:
                    xlog.exception("dns client parse response fail:%r", e)
                    continue

                if len(p.questions) == 0:
                    xlog.warn("received response without question")
                    continue

                id = p.header.id

                if id not in self.waiters:
                    continue

                que = self.waiters[id]
                org_domain = que.domain
                domain = str(p.questions[0].qname)
                xlog.debug("recev %s from:%s domain:%s org:%s", len(p.rr), server, domain, org_domain)
                ips = []
                for r in p.rr:
                    ip = str(r.rdata)
                    if r.rtype == 5:
                        # CNAME
                        if ip.endswith("."):
                            ip = ip[:-1]

                        if ip == domain:
                            xlog.warn("recv domain[%s] == ip[%s]", domain, ip)
                            continue

                        query_count = g.domain_cache.get_query_count(domain)
                        if query_count >= 50:
                            xlog.warn("%s ip:%s query_count:%d", domain, ip, query_count)
                            continue

                        g.domain_cache.add_query_count(domain)

                        xlog.debug("local dns %s recv %s cname:%s from:%s", org_domain, domain, ip, server)
                        d = DNSRecord(DNSHeader(id))
                        d.add_question(DNSQuestion(ip, QTYPE.A))
                        req_pack = d.pack()

                        self.sock.sendto(req_pack, (server, 53))

                        d = DNSRecord()
                        d.add_question(DNSQuestion(ip, QTYPE.AAAA))
                        req_pack = d.pack()

                        self.sock.sendto(req_pack, (server, 53))
                        continue

                    if "." in ip and g.ip_region.check_ip(ip):
                        cn = g.ip_region.cn
                    else:
                        cn = "XX"
                    ips.append(ip+"|"+cn)

                if len(ips):
                    g.domain_cache.set_ips(org_domain, ips)
                que.notify_all()
            except Exception as e:
                xlog.exception("dns recv_worker except:%r", e)

        xlog.info("DNS Client recv worker exit.")
        self.sock.close()

    def send_request(self, id, domain, server):
        try:
            d = DNSRecord(DNSHeader(id))
            d.add_question(DNSQuestion(domain, QTYPE.A))
            req4_pack = d.pack()

            d = DNSRecord()
            d.add_question(DNSQuestion(domain, QTYPE.AAAA))
            req6_pack = d.pack()

            self.sock.sendto(req4_pack, (server, 53))
            # xlog.debug("send req:%s to:%s", domain, server)

            self.sock.sendto(req6_pack, (server, 53))
            # xlog.debug("send req:%s to:%s", domain, server)
        except Exception as e:
            xlog.warn("send_request except:%r", e)

    def query(self, domain, timeout=3, use_local=False):
        end_time = time.time() + timeout
        while True:
            id = random.randint(0, 65535)
            if id not in self.waiters:
                break

        que = simple_queue.Queue()
        que.domain = domain

        ips = []
        if use_local:
            server_list = self.dns_server.local_list
        else:
            server_list = self.dns_server.public_list

        for ip in server_list:
            if time.time() > end_time:
                break