import os
import subprocess
import tempfile

try:
    from .validation import is_valid_domain_name, is_valid_hostname
except ImportError:
    from validation import is_valid_domain_name, is_valid_hostname


NGINX_SSL_DIR = "/etc/nginx/ssl"

NGINX_SSL_HARDENING = """    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;
"""


def _write_text_file(path, content, mode=0o644):
    directory = os.path.dirname(path)
    os.makedirs(directory, exist_ok=True)
    fd, temp_path = tempfile.mkstemp(dir=directory, prefix=".tmp_", text=True)
    try:
        with os.fdopen(fd, "w") as handle:
            handle.write(content)
        os.chmod(temp_path, mode)
        os.replace(temp_path, path)
    except Exception:
        try:
            os.unlink(temp_path)
        except OSError:
            pass
        raise


def get_domain_ssl_status(domain):
    """
    Checks if SSL is enabled for a domain by inspecting Nginx config and certificate existence.
    Returns: (expiry_date_string_or_status, ssl_type_or_None)
    """
    if not is_valid_domain_name(domain):
        return None, "Invalid domain"

    try:
        config_path = f"/etc/nginx/sites-available/{domain}"
        if not os.path.exists(config_path):
            return None, "Config not found"

        with open(config_path, "r") as f:
            cfg = f.read()

        if "listen 443 ssl" not in cfg and "listen 443" not in cfg:
            return None, "No SSL"

        if "/etc/letsencrypt/live/" in cfg:
            cert_path = f"/etc/letsencrypt/live/{domain}/fullchain.pem"
            if os.path.exists(cert_path):
                cmd = ["openssl", "x509", "-enddate", "-noout", "-in", cert_path]
                res = subprocess.run(cmd, capture_output=True, text=True)
                if res.returncode == 0 and "=" in res.stdout:
                    return res.stdout.strip().split("=", 1)[1], "LetsEncrypt"
            return "Active", "LetsEncrypt"

        if f"{NGINX_SSL_DIR}/" in cfg:
            return "Active", "Manual/Self-Signed"

        return "Active", "Unknown"
    except Exception:
        return None, "Error checking"


def update_nginx_ssl(domain, cert_path, key_path):
    """
    Re-enables SSL for a domain using the specified certificate and key.
    Used when rewriting Nginx config during host updates.
    """
    if not is_valid_domain_name(domain):
        return False, "Invalid domain"
    if not os.path.exists(cert_path) or not os.path.exists(key_path):
        return False, "Certificate or key file not found"

    config_path = f"/etc/nginx/sites-available/{domain}"
    if not os.path.exists(config_path):
        return False, "Config not found"

    with open(config_path, "r") as f:
        cfg = f.read()

    import re
    if "listen 443 ssl" in cfg:
        return True, "SSL already present"
        
    match = re.search(r"listen\s+80(\s+default_server)?;", cfg)
    if not match:
        return False, "Could not inject SSL directives"

    is_default = bool(match.group(1) and "default_server" in match.group(1))
    ssl_default_str = " default_server" if is_default else ""

    ssl_block = f"""
    listen 443 ssl{ssl_default_str};
    ssl_certificate {cert_path};
    ssl_certificate_key {key_path};
{NGINX_SSL_HARDENING}
"""
    # Replace the first listen 80... with itself + ssl_block
    new_cfg = re.sub(r"(listen\s+80(\s+default_server)?;)", r"\1\n" + ssl_block, cfg, count=1)

    try:
        _write_text_file(config_path, new_cfg)
        test = subprocess.run(["nginx", "-t"], capture_output=True, text=True)
        if test.returncode != 0:
            _write_text_file(config_path, cfg)
            return False, test.stderr.strip() or "nginx configuration test failed"
        subprocess.run(["systemctl", "reload", "nginx"], check=True)
        return True, "SSL restored"
    except Exception as e:
        try:
            _write_text_file(config_path, cfg)
        except OSError:
            pass
        return False, str(e)


def enable_ssl_manual(domain, crt_content, key_content):
    if not is_valid_domain_name(domain):
        return False, "Invalid domain"
    if not crt_content or not key_content:
        return False, "Certificate and key are required"
    try:
        cert_path = f"{NGINX_SSL_DIR}/{domain}.crt"
        key_path = f"{NGINX_SSL_DIR}/{domain}.key"
        _write_text_file(cert_path, crt_content, mode=0o644)
        _write_text_file(key_path, key_content, mode=0o600)
        return update_nginx_ssl(domain, cert_path, key_path)
    except Exception as e:
        return False, str(e)


def generate_ca(ca_name, domain):
    try:
        os.makedirs(NGINX_SSL_DIR, exist_ok=True)
        cert_path = f"{NGINX_SSL_DIR}/{ca_name}.crt"
        key_path = f"{NGINX_SSL_DIR}/{ca_name}.key"
        cmd = [
            "openssl", "req", "-x509", "-nodes", "-days", "3650",
            "-newkey", "rsa:2048",
            "-keyout", key_path,
            "-out", cert_path,
            "-subj", f"/C=US/ST=State/L=City/O=Limristem/CN={domain}",
            "-addext", "basicConstraints=critical,CA:TRUE",
            "-addext", "keyUsage=critical,keyCertSign,cRLSign"
        ]
        subprocess.run(cmd, check=True)
        os.chmod(key_path, 0o600)
        return True, cert_path, key_path
    except Exception as e:
        return False, str(e), None

def enable_ssl_selfsigned(domain, ca_cert_path=None, ca_key_path=None):
    if not is_valid_domain_name(domain):
        return False, "Invalid domain"
    try:
        os.makedirs(NGINX_SSL_DIR, exist_ok=True)
        cert_path = f"{NGINX_SSL_DIR}/{domain}.crt"
        key_path = f"{NGINX_SSL_DIR}/{domain}.key"
        csr_path = f"{NGINX_SSL_DIR}/{domain}.csr"
        
        # Generate private key and CSR
        cmd_req = [
            "openssl", "req", "-new", "-nodes",
            "-newkey", "rsa:2048",
            "-keyout", key_path,
            "-out", csr_path,
            "-subj", f"/C=US/ST=State/L=City/O=Limristem/CN={domain}"
        ]
        subprocess.run(cmd_req, check=True)
        
        if ca_cert_path and ca_key_path and os.path.exists(ca_cert_path) and os.path.exists(ca_key_path):
            # Sign with CA
            ext_path = f"{NGINX_SSL_DIR}/{domain}.ext"
            _write_text_file(ext_path, f"subjectAltName=DNS:{domain}")
            cmd_sign = [
                "openssl", "x509", "-req", "-in", csr_path,
                "-CA", ca_cert_path, "-CAkey", ca_key_path,
                "-CAcreateserial", "-out", cert_path,
                "-days", "365", "-extfile", ext_path
            ]
            subprocess.run(cmd_sign, check=True)
            try:
                os.unlink(ext_path)
            except OSError:
                pass
        else:
            # Self-sign directly if no CA
            cmd_sign = [
                "openssl", "x509", "-req", "-in", csr_path,
                "-signkey", key_path,
                "-out", cert_path, "-days", "365"
            ]
            subprocess.run(cmd_sign, check=True)

        os.chmod(key_path, 0o600)
        try:
            os.unlink(csr_path)
        except OSError:
            pass
            
        return update_nginx_ssl(domain, cert_path, key_path)
    except Exception as e:
        return False, str(e)


def enable_ssl_letsencrypt(domain, email):
    if not is_valid_domain_name(domain):
        return False, "Invalid domain"
    try:
        cmd = [
            "certbot", "certonly", "--nginx", "-d", domain,
            "--non-interactive", "--agree-tos"
        ]
        if not email or email == 'admin@example.com':
            cmd.append("--register-unsafely-without-email")
        else:
            cmd.extend(["-m", email])
        res = subprocess.run(cmd, capture_output=True, text=True)
        if res.returncode != 0:
            return False, res.stderr.strip() or res.stdout.strip() or "Certbot failed"
            
        cert_path = f"/etc/letsencrypt/live/{domain}/fullchain.pem"
        key_path = f"/etc/letsencrypt/live/{domain}/privkey.pem"
        return update_nginx_ssl(domain, cert_path, key_path)
    except Exception as e:
        return False, f"Certbot exception: {e}"


def enable_panel_letsencrypt(hostname, email):
    """
    Enables Let's Encrypt specifically for the panel proxy or panel host.
    """
    if not is_valid_hostname(hostname):
        return False, "Invalid hostname"
    try:
        cmd = [
            "certbot", "--nginx", "-d", hostname,
            "--non-interactive", "--agree-tos",
            "--redirect"
        ]
        if not email or email == 'admin@example.com':
            cmd.append("--register-unsafely-without-email")
        else:
            cmd.extend(["-m", email])
        res = subprocess.run(cmd, capture_output=True, text=True)
        if res.returncode != 0:
            return False, res.stderr.strip() or res.stdout.strip() or "Certbot failed"
        return True, "Panel SSL enabled via Let's Encrypt"
    except Exception as e:
        return False, f"Certbot exception: {e}"

def generate_panel_selfsigned(domain, install_dir, ca_cert_path=None, ca_key_path=None):
    if not is_valid_domain_name(domain) and not is_valid_hostname(domain):
        return False, "Invalid domain/hostname"
    try:
        instance_dir = os.path.join(install_dir, "instance")
        os.makedirs(instance_dir, exist_ok=True)
        cert_path = os.path.join(instance_dir, "panel.crt")
        key_path = os.path.join(instance_dir, "panel.key")
        csr_path = os.path.join(instance_dir, "panel.csr")
        
        # Generate private key and CSR
        cmd_req = [
            "openssl", "req", "-new", "-nodes",
            "-newkey", "rsa:2048",
            "-keyout", key_path,
            "-out", csr_path,
            "-subj", f"/C=US/ST=State/L=City/O=Limristem/CN={domain}"
        ]
        subprocess.run(cmd_req, check=True)
        
        if ca_cert_path and ca_key_path and os.path.exists(ca_cert_path) and os.path.exists(ca_key_path):
            # Sign with CA
            ext_path = os.path.join(instance_dir, "panel.ext")
            _write_text_file(ext_path, f"subjectAltName=DNS:{domain}")
            cmd_sign = [
                "openssl", "x509", "-req", "-in", csr_path,
                "-CA", ca_cert_path, "-CAkey", ca_key_path,
                "-CAcreateserial", "-out", cert_path,
                "-days", "365", "-extfile", ext_path
            ]
            subprocess.run(cmd_sign, check=True)
            try:
                os.unlink(ext_path)
            except OSError:
                pass
        else:
            # Self-sign directly if no CA.
            cmd_sign = [
                "openssl", "x509", "-req", "-in", csr_path,
                "-signkey", key_path,
                "-out", cert_path, "-days", "3650"
            ]
            subprocess.run(cmd_sign, check=True)

        os.chmod(key_path, 0o600)
        try:
            os.unlink(csr_path)
        except OSError:
            pass
            
        return True, "Panel self-signed certificate generated"
    except Exception as e:
        return False, str(e)
