# -*- coding: utf-8 -*-
"""Resolvedor DNS alternativo conservador para o OnePlayVIP.

Baseado no dns.py enviado pelo usuário, mas adaptado para não instalar patch global
automaticamente no import. O patch só entra quando a configuração do addon permitir.
"""
import socket
import struct
import random
import logging
import sys
import json
import time
import os

try:
    from kodi_six import xbmc, xbmcaddon, xbmcvfs
except ImportError:
    import xbmc
    import xbmcaddon
    import xbmcvfs

PY2 = sys.version_info[0] == 2
ADDON_ = xbmcaddon.Addon()
TRANSLATE_ = xbmc.translatePath if PY2 else xbmcvfs.translatePath
profile = TRANSLATE_(ADDON_.getAddonInfo('profile'))
try:
    if not os.path.exists(profile):
        os.makedirs(profile)
except Exception:
    pass
CACHE_FILE = os.path.join(profile, 'dns_cache.json')

_DNS_PATCH_STATE = {
    'installed': False,
    'original_getaddrinfo': socket.getaddrinfo,
    'resolver': None,
}


def _safe_log(message, level=None):
    try:
        xbmc.log('[plugin.video.oneplayvip][dns] %s' % message, level if level is not None else xbmc.LOGDEBUG)
    except Exception:
        try:
            logging.debug(message)
        except Exception:
            pass


class customdns:
    def __init__(self, cache_file=CACHE_FILE, cache_ttl=3600, mode_logger=False):
        # DNS neutros primeiro; DNS filtrantes ficam no fim para reduzir falso bloqueio.
        self.dns_server = [
            '1.1.1.1',        # Cloudflare
            '1.0.0.1',        # Cloudflare
            '8.8.8.8',        # Google DNS
            '8.8.4.4',        # Google DNS
            '208.67.222.222', # OpenDNS
            '208.67.220.220', # OpenDNS
            '94.140.14.140',  # AdGuard
            '94.140.14.141',  # AdGuard
        ]
        self.original_getaddrinfo = _DNS_PATCH_STATE.get('original_getaddrinfo', socket.getaddrinfo)
        self.cache_file = cache_file
        self.cache_ttl = cache_ttl
        self.cache = self._load_cache()
        self.mode_logger = bool(mode_logger)

    def _load_cache(self):
        try:
            if os.path.exists(self.cache_file):
                with open(self.cache_file, 'r') as f:
                    cache = json.load(f)
                current_time = time.time()
                clean = {}
                for domain, data in cache.items():
                    try:
                        if float(data.get('expires', 0)) > current_time and data.get('ip'):
                            clean[domain] = data
                    except Exception:
                        pass
                return clean
        except Exception as e:
            _safe_log('Erro ao carregar cache DNS: %s' % e, xbmc.LOGDEBUG)
        return {}

    def _save_cache(self):
        try:
            with open(self.cache_file, 'w') as f:
                json.dump(self.cache, f, indent=2)
        except Exception as e:
            _safe_log('Erro ao salvar cache DNS: %s' % e, xbmc.LOGDEBUG)

    def is_valid_ipv4(self, ip):
        try:
            socket.inet_aton(ip)
            return True
        except Exception:
            return False

    def is_valid_ipv6(self, ip):
        try:
            if hasattr(socket, 'inet_pton'):
                socket.inet_pton(socket.AF_INET6, ip)
                return True
        except Exception:
            pass
        return False

    def _build_dns_query(self, domain):
        transaction_id = random.randint(0, 65535)
        flags = 0x0100
        header = struct.pack('>HHHHHH', transaction_id, flags, 1, 0, 0, 0)
        if PY2:
            qname = b''.join(chr(len(part)) + part for part in domain.split('.')) + b'\x00'
        else:
            qname = b''.join(bytes([len(part)]) + part.encode('utf-8') for part in domain.split('.')) + b'\x00'
        return header + qname + struct.pack('>HH', 1, 1)  # A / IN

    def _parse_dns_response(self, data):
        try:
            answer_count = struct.unpack('>H', data[6:8])[0]
            offset = 12
            while offset < len(data) and data[offset] != 0:
                offset += 1
            offset += 5
            for _ in range(answer_count):
                if offset + 12 > len(data):
                    return None
                offset += 2
                rtype, _rclass, ttl, rdlength = struct.unpack('>HHIH', data[offset:offset + 10])
                offset += 10
                if rtype == 1 and rdlength == 4 and offset + 4 <= len(data):
                    return '.'.join(map(str, struct.unpack('>BBBB', data[offset:offset + 4])))
                offset += rdlength
        except Exception as e:
            if self.mode_logger:
                _safe_log('Erro ao interpretar resposta DNS: %s' % e, xbmc.LOGDEBUG)
        return None

    def resolve(self, domain, dns_custom):
        domain_clean = (domain or '').strip().strip('.')
        if not domain_clean or domain_clean.lower() in ('localhost',):
            return None
        if domain_clean in self.cache:
            try:
                data = self.cache[domain_clean]
                if float(data.get('expires', 0)) > time.time() and data.get('ip'):
                    return data.get('ip')
                del self.cache[domain_clean]
                self._save_cache()
            except Exception:
                pass
        s = None
        try:
            query = self._build_dns_query(domain_clean)
            if self.is_valid_ipv6(dns_custom):
                s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
                addr = (dns_custom, 53, 0, 0)
            else:
                s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                addr = (dns_custom, 53)
            s.settimeout(2.5)
            s.sendto(query, addr)
            data, _addr = s.recvfrom(512)
            ip = self._parse_dns_response(data)
            if ip:
                self.cache[domain_clean] = {'ip': ip, 'expires': time.time() + self.cache_ttl}
                self._save_cache()
                if self.mode_logger:
                    _safe_log('DNS alternativo: %s -> %s via %s' % (domain_clean, ip, dns_custom), xbmc.LOGDEBUG)
                return ip
        except Exception as e:
            if self.mode_logger:
                _safe_log('Falha DNS %s via %s: %s' % (domain_clean, dns_custom, e), xbmc.LOGDEBUG)
        finally:
            try:
                if s:
                    s.close()
            except Exception:
                pass
        return None

    def _resolver(self, host, port, family=0, socktype=0, proto=0, flags=0):
        try:
            host_text = str(host or '').strip()
            if not host_text:
                return self.original_getaddrinfo(host, port, family, socktype, proto, flags)
            if self.is_valid_ipv4(host_text) or self.is_valid_ipv6(host_text):
                return self.original_getaddrinfo(host, port, family, socktype, proto, flags)
            if host_text.lower() in ('localhost',) or host_text.endswith('.local'):
                return self.original_getaddrinfo(host, port, family, socktype, proto, flags)
            # Só responde IPv4/AF_UNSPEC. Para chamadas exigindo IPv6, preserva o sistema.
            if family not in (0, getattr(socket, 'AF_UNSPEC', 0), socket.AF_INET):
                return self.original_getaddrinfo(host, port, family, socktype, proto, flags)
            for dns_server in self.dns_server:
                ip = self.resolve(host_text, dns_server)
                if ip:
                    stype = socktype or socket.SOCK_STREAM
                    pr = proto or 6
                    return [(socket.AF_INET, stype, pr, '', (ip, port))]
        except Exception as e:
            if self.mode_logger:
                _safe_log('Erro no resolvedor para %s: %s' % (host, e), xbmc.LOGDEBUG)
        return self.original_getaddrinfo(host, port, family, socktype, proto, flags)


def install(cache_ttl=3600, mode_logger=False):
    """Instala o resolvedor alternativo no runtime Python atual."""
    try:
        if _DNS_PATCH_STATE.get('installed'):
            return True
        _DNS_PATCH_STATE['original_getaddrinfo'] = socket.getaddrinfo
        resolver = customdns(cache_file=CACHE_FILE, cache_ttl=cache_ttl, mode_logger=mode_logger)
        _DNS_PATCH_STATE['resolver'] = resolver
        socket.getaddrinfo = resolver._resolver
        _DNS_PATCH_STATE['installed'] = True
        if mode_logger:
            _safe_log('Resolvedor DNS alternativo ativado.', xbmc.LOGDEBUG)
        return True
    except Exception as e:
        _safe_log('Falha ao ativar resolvedor DNS alternativo: %s' % e, xbmc.LOGERROR)
        return False


def uninstall():
    """Restaura socket.getaddrinfo quando o recurso estiver desligado."""
    try:
        if _DNS_PATCH_STATE.get('installed'):
            socket.getaddrinfo = _DNS_PATCH_STATE.get('original_getaddrinfo', socket.getaddrinfo)
            _DNS_PATCH_STATE['installed'] = False
            _DNS_PATCH_STATE['resolver'] = None
        return True
    except Exception as e:
        _safe_log('Falha ao desativar resolvedor DNS alternativo: %s' % e, xbmc.LOGDEBUG)
        return False


def is_installed():
    return bool(_DNS_PATCH_STATE.get('installed'))
