# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2014-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: yakpanel
# -------------------------------------------------------------------
# ------------------------------
# aaDNS api
# ------------------------------
import json
import sys
import threading

if not "class/" in sys.path:
    sys.path.insert(0, "class/")
if not "class_v2/" in sys.path:
    sys.path.insert(0, "class_v2/")

from public.exceptions import HintException
from public.validate import Param
from ssl_domainModelV2.model import DnsDomainProvider
from ssl_domainModelV2.service import DomainValid
from .dns_manager import DnsManager, MailManager
from .helper import *


class DnsApiObject:
    def __init__(self):
        pass

    def install_bind(self, get):
        log = f"{public.get_panel_path()}/logs/install_bind.log"
        if os.path.exists(log):
            public.ExecShell(f"rm -f {log}")
        install_script = os.path.join(
            os.path.dirname(os.path.abspath(__file__)), "bind_script.sh"
        )
        public.ExecShell("chmod +x {}".format(install_script))
        public.ExecShell("nohup bash {} install >>{} 2>&1 &".format(install_script, log))
        return public.success_v2(public.lang("Installing..."))

    def install_pdns(self, get):
        try:
            get.validate([
                Param("act").String("in", ["install", "uninstall"]).Require(),
                Param("clean").Integer(),
            ], [public.validate.trim_filter(), ])
            if not hasattr(get, "clean"):
                get.clean = 1
            else:
                get.clean = int(get.clean)

            if get.act == "install" and os.path.exists("/www/server/panel/plugin/syssafe/config.json"):
                cf = public.readFile("/www/server/panel/plugin/syssafe/config.json")
                if cf:
                    jcf = None
                    try:
                        import json
                        jcf = json.loads(cf)
                    except:
                        pass
                    if jcf and jcf.get("open") is True:
                        raise HintException(public.lang(
                            "[System hardening] is enabled, please disable it first before installing aaDNS."
                        ))
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.fail_v2(str(ex))
        log = f"{public.get_panel_path()}/logs/install_pdns.log"
        public.writeFile(log, f"Starting {get.act}...\n")
        install_script = os.path.join(
            os.path.dirname(os.path.abspath(__file__)), "pdns_script.sh"
        )

        public.ExecShell("chmod +x {}".format(install_script))
        public.ExecShell(f"nohup bash {install_script} {get.act} {get.clean} >>{log} 2>&1 &")
        if get.act == "install":
            public.set_module_logs(
                "sys_domain", "Install_YakPanelDns", 1
            )
        return public.success_v2(public.lang("Success!"))

    def get_status(self, get):
        config_obj = aaDnsConfig()
        service_name = config_obj.service_path.get("service_name")
        if not service_name:
            public.success_v2({
                "service": None,
                "status": False
            })
        a, e = public.ExecShell(f"ps -ef | grep '{service_name}' | grep -v grep")
        if e:
            raise HintException(f"Failed to get aaDNS service status: {e} please try again.")
        return public.success_v2({
            "service": config_obj.install_service,
            "status": True if a else False
        })

    def change_status(self, get):
        try:
            get.validate([
                Param("service_name").String("in", ["bind", "pdns"]),
                Param("status").String("in", [
                    "start", "stop", "restart", "reload"
                ]).Require(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.fail_v2(str(ex))
        if not hasattr(get, "service_name"):
            service_name = "pdns"
        else:
            service_name = get.service_name

        DnsManager().change_service_status(service_name, get.status)
        return public.success_v2(public.lang(f"Successfully {get.status} aaDNS service."))

    @staticmethod
    def check_base_params(func):
        def wrapper(self, get):
            check_domain = ["domain", "ns1domain", "ns2domain", "soa"]
            soa_params = ["nameserver", "admin_mail"]
            for key in check_domain + soa_params:
                if hasattr(get, key) and not DomainValid.is_valid_domain(getattr(get, key)):
                    raise HintException("invalid {}: {}".format(key, getattr(get, key)))
            # ip
            check_ip = ["ip", "domain_ip"]
            for key2 in check_ip:
                if hasattr(get, key2):
                    if not DomainValid.is_ip4(getattr(get, key2)) and not DomainValid.is_ip6(getattr(get, key2)):
                        raise HintException("invalid ip address: {}".format(getattr(get, key2)))
            # ttl, priority
            check_int_type = ["ttl", "priority"]
            soa_int_params = ["serial", "refresh", "retry", "expire", "minimum"]
            for key3 in check_int_type + soa_int_params:
                if hasattr(get, key3):
                    try:
                        int(getattr(get, key3))
                    except:
                        raise HintException("{} must be an digit.".format(key3))
            return func(self, get)

        return wrapper

    @staticmethod
    def init_provider(func):
        def wrapper(self, get):
            provider = DnsDomainProvider.objects.filter(name="YakPanelDns").first()
            if not provider:
                raise HintException(public.lang(
                    "YakPanelDns provider not found. Please install aaDNS first."
                ))
            return func(self, get, provider)

        return wrapper

    @check_base_params
    def add_zone(self, get):
        """添加zone信息"""
        try:
            get.validate([
                Param("domain").String().Require(),
                Param("ns1domain").String(),
                Param("ns2domain").String(),
                Param("soa").String(),
                Param("domain_ip").String(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.fail_v2(str(ex))

        manager = DnsManager()
        domain = get.domain.rstrip(".")  # 去点
        soa = "ns1.{}.".format(domain)
        ip = "127.0.0.1"
        ns1 = "ns1.{}.".format(domain)
        ns2 = "ns2.{}.".format(domain)

        if hasattr(get, "ns1") and get.ns1:
            ns1 = get.ns1
        if hasattr(get, "ns2") and get.ns2:
            ns2 = get.ns2
        if hasattr(get, "soa") and get.soa:
            soa = get.soa
        if hasattr(get, "domain_ip") and get.domain_ip:
            ip = get.domain_ip

        # 确保FQDN格式
        for k in [ns1, ns2, soa]:
            if not k.endswith("."):
                k += "."
        res = manager.add_zone(domain, ns1, ns2, soa, ip)
        if isinstance(res, str):
            return public.success_v2(res)
        return public.success_v2(public.lang("Successfully added zone."))

    @check_base_params
    def del_zone(self, get):
        try:
            get.validate([
                Param("domain").String().Require(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.fail_v2(str(ex))

        res = DnsManager().delete_zone(get.domain)
        if isinstance(res, str):
            return public.success_v2(res)
        return public.success_v2(public.lang("Successfully deleted zone."))

    @check_base_params
    def get_zones(self, get):
        """获取所有已添加的域名列表"""
        try:
            get.validate([
                Param("domain").String(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.fail_v2(str(ex))
        domains = DnsParser().get_zones(get.domain)
        return public.success_v2(domains)

    @check_base_params
    def get_nameserver(self, get):
        return public.success_v2(DnsManager().get_default_nameserver())

    @check_base_params
    def set_nameserver(self, get):
        try:
            get.validate([
                Param("ns1domain").String().Require(),
                Param("ns2domain").String().Require(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.fail_v2(str(ex))

        manager = DnsManager()
        manager.set_default_nameserver(get.ns1domain, get.ns2domain)
        return public.success_v2(public.lang("Successfully set nameserver."))

    @check_base_params
    def get_soa(self, get):
        try:
            get.validate([
                Param("domain").String().Require(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.fail_v2(str(ex))
        soa = DnsManager().get_soa(get.domain)
        return public.success_v2(soa)

    @check_base_params
    def set_soa(self, get):
        try:
            get.validate([
                Param("domain").String().Require(),
                Param("nameserver").String().Require(),
                Param("admin_mail").String().Require(),
                Param("serial").Integer().Require(),
                Param("refresh").Integer("between", [1200, 43200]).Require(),
                Param("retry").Integer("between", [120, 7200]).Require(),
                Param("expire").Integer("between", [1209600, 2419200]).Require(),
                Param("minimum").Integer("between", [180, 86400]).Require(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.fail_v2(str(ex))

        for k, v in get.__dict__.items():
            if k in ["domain", "nameserver", "admin_mail"]:
                if v.endswith("."):
                    setattr(get, k, v.rstrip("."))
        DnsManager().set_soa(**get.__dict__)
        return public.success_v2(public.lang("Successfully set SOA record."))

    def get_logger(self, get):
        try:
            get.validate([
                Param("p").Integer(),
                Param("limit").Integer(),
                Param("search").String(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.fail_v2(str(ex))
        if not hasattr(get, "p"):
            get.p = 1
        if not hasattr(get, "limit"):
            get.limit = 20
        if hasattr(get, "search") and get.search:
            search = str(get.search)
        else:
            search = None
        return public.success_v2(
            DnsManager().get_logger(int(get.p), int(get.limit), search)
        )

    def clear_logger(self, get):
        if DnsManager().clear_logger():
            return public.success_v2(public.lang("Successfully cleared logs."))
        return public.fail_v2(public.lang("Failed to clear logs."))

    @init_provider
    def add_dmarc(self, get, provider: DnsDomainProvider):
        try:
            get.validate([
                Param("policy").String("in", ["none", "quarantine", "reject"]).Require(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.fail_v2(str(ex))

        mail_manager = MailManager()
        fails = []
        for d in provider.domains:
            try:
                setattr(mail_manager, "domain", d)
                mail_manager.add_dmarc(policy=get.policy, provider=provider)
            except Exception as ex:
                public.print_log("error info: {}".format(ex))
                fails.append(f"domain: {d} error: {ex}")
                continue
        if fails:
            return public.fail_v2(", ".join(fails))
        return public.success_v2(public.lang("Successfully added DMARC record."))

    @init_provider
    def add_dkim_spf(self, get, provider: DnsDomainProvider):
        fails = []
        for d in provider.domains:
            try:
                MailManager(d).add_spf(provider)
                MailManager(d).add_dkim(provider)
            except Exception as ex:
                public.print_log("error info: {}".format(ex))
                fails.append(f"domain: {d} error: {ex}")
                continue
        if fails:
            return public.fail_v2(", ".join(fails))
        return public.success_v2(public.lang("Successfully added DKIM/SPF records."))

    @init_provider
    def dns_checker(self, get, provider: DnsDomainProvider):
        try:
            get.validate([
                Param("act").String("in", ["start", "status"]).Require(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.fail_v2(str(ex))

        status = 1 if os.path.exists(DNS_AUTH_LOCK) else 0
        if get.act == "status":
            if status:
                msg = public.lang("DNS Checker is Running. Please Wait.")
            else:
                msg = public.lang("DNS Checker is Suspend.")

        elif get.act == "start":
            if status:
                msg = public.lang("DNS Checker is Already Running. Please Wait.")
            else:
                task = threading.Thread(
                    target=DnsManager().builtin_dns_checker, args=(provider,)
                )
                task.start()
                status = 1
                msg = public.lang("DNS Checker Run Successfully.")
        else:
            raise HintException(public.lang("Invalid action."))

        return public.success_v2({
            "checker_status": status,
            "msg": msg
        })

    @check_base_params
    @init_provider
    def fix_zone(self, get, provider: DnsDomainProvider):
        manager = DnsManager()
        try:
            for i in provider.domains:
                manager.fix_zone(i)
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.fail_v2(str(ex))
        return public.success_v2(public.lang("Successfully fixed all zones."))

    @check_base_params
    @init_provider
    def set_ttl_batch(self, get, provider: DnsDomainProvider):
        try:
            get.validate([
                Param("ttl").String().Require(),
                Param("domains").String().Require(),
                Param("record_type").String().Require(),
            ], [
                public.validate.trim_filter(),
            ])
            get.domains = json.loads(get.domains)
            if not get.record_type:
                raise HintException(public.lang("Record type is required."))
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.fail_v2(str(ex))

        fails = []
        manager = DnsManager()
        for d in get.domains:
            if d not in provider.domains:
                fails.append(f"domain: {d} error: not found in provider domains.")
                continue
            try:
                if not manager.domian_record_type_ttl_batch_set(
                        domain=d, record_type=get.record_type, ttl=get.ttl
                ):
                    fails.append(f"domain: {d} error: failed to set ttl.")
            except Exception as ex:
                public.print_log("error info: {}".format(ex))
                fails.append(f"domain: {d} error: {ex}")
                continue
        if fails:
            return public.fail_v2(", ".join(fails))
        return public.success_v2(public.lang("Successfully set TTL for all domains."))
