"""
DKIM密钥生成 + SSL证书管理
"""
import os
import base64
import subprocess
import threading
from typing import Optional, Callable

from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend


class DKIMManager:

    @staticmethod
    def generate_keypair(domain: str, selector: str = 'default',
                         key_length: int = 2048) -> dict:
        private_key = rsa.generate_private_key(
            public_exponent=65537,
            key_size=key_length,
            backend=default_backend()
        )

        public_key = private_key.public_key()

        private_pem = private_key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.TraditionalOpenSSL,
            encryption_algorithm=serialization.NoEncryption()
        ).decode('utf-8')

        public_der = public_key.public_bytes(
            encoding=serialization.Encoding.DER,
            format=serialization.PublicFormat.SubjectPublicKeyInfo
        )
        pub_key_b64 = base64.b64encode(public_der).decode('utf-8')

        dkim_record = f"v=DKIM1; k=rsa; p={pub_key_b64}"

        return {
            'selector': selector,
            'domain': domain,
            'private_key': private_pem,
            'public_key': pub_key_b64,
            'dns_record': dkim_record,
            'dns_name': f'{selector}._domainkey.{domain}',
        }

    @staticmethod
    def save_keypair(keypair: dict, output_dir: str):
        os.makedirs(output_dir, exist_ok=True)
        domain = keypair['domain']
        selector = keypair['selector']

        priv_path = os.path.join(output_dir, f'{domain}_{selector}.pem')
        with open(priv_path, 'w', encoding='utf-8') as f:
            f.write(keypair['private_key'])

        dns_path = os.path.join(output_dir, f'{domain}_{selector}_dns.txt')
        with open(dns_path, 'w', encoding='utf-8') as f:
            f.write(keypair['dns_record'])

        return priv_path, dns_path


class CertbotManager:

    def __init__(self, log_callback: Callable[[str, str], None] = None):
        self.log_callback = log_callback

    def log(self, level, msg):
        if self.log_callback:
            self.log_callback(level, msg)

    @staticmethod
    def _safe_arg(s: str) -> str:
        """参数白名单校验: 域名/选择器/路径只允许安全字符, 杜绝 shell 注入"""
        import re as _re
        s = str(s or '')
        if not _re.fullmatch(r'[A-Za-z0-9._*~/=-]+', s):
            raise ValueError('参数含非法字符: ' + s[:50])
        return s

    def request_cert(self, domain: str, dns_provider: str = 'cloudflare',
                     dns_credentials: dict = None, timeout: int = 120) -> dict:
        try:
            domain = self._safe_arg(domain)
            dns_provider = self._safe_arg(dns_provider)
            # 参数化执行 (不经过shell), 天然免疫命令注入
            cmd = [
                os.path.expanduser('~/.acme.sh/acme.sh'), '--issue', '--dns', f'dns_{dns_provider}',
                '-d', domain, '-d', f'*.{domain}',
                '--keylength', 'ec-256'
            ]

            if dns_provider == 'cloudflare' and dns_credentials:
                env = os.environ.copy()
                env['CF_Key'] = dns_credentials.get('api_key', '')
                env['CF_Email'] = dns_credentials.get('api_email', '')
            else:
                env = os.environ.copy()

            self.log('info', f'申请证书: {domain}')
            result = subprocess.run(cmd, shell=False, capture_output=True,
                                    text=True, timeout=timeout, env=env)

            if result.returncode == 0:
                self.log('success', f'证书申请成功: {domain}')
                return {'success': True, 'domain': domain, 'output': result.stdout}
            else:
                self.log('error', f'证书申请失败: {result.stderr[:200]}')
                return {'success': False, 'domain': domain, 'error': result.stderr}

        except subprocess.TimeoutExpired:
            self.log('error', f'证书申请超时: {domain}')
            return {'success': False, 'domain': domain, 'error': '超时'}
        except Exception as e:
            self.log('error', f'证书申请异常: {e}')
            return {'success': False, 'domain': domain, 'error': str(e)}

    def install_cert(self, domain: str, key_file: str, cert_file: str,
                     reload_cmd: str = None) -> bool:
        try:
            domain = self._safe_arg(domain)
            key_file = self._safe_arg(key_file)
            cert_file = self._safe_arg(cert_file)
            # 参数化执行 (不经过shell); reload_cmd 允许受限字符集
            cmd = [
                os.path.expanduser('~/.acme.sh/acme.sh'), '--installcert', '-d', domain,
                '--key-file', key_file,
                '--fullchain-file', cert_file,
            ]
            if reload_cmd:
                import re as _re
                reload_cmd = str(reload_cmd)
                if not _re.fullmatch(r'[A-Za-z0-9 ._~/=&-]+', reload_cmd):
                    return False
                cmd.extend(['--reloadcmd', reload_cmd])

            result = subprocess.run(cmd, shell=False, capture_output=True,
                                    text=True, timeout=60)
            return result.returncode == 0
        except:
            return False
