from __future__ import annotations

import os
import re
import shutil
import subprocess
from logger import get_logger

log = get_logger("actions.ssl")


def _validate_domain(domain: str | None) -> tuple[bool, str]:
    if not domain or not isinstance(domain, str):
        return False, "Domain is required and must be a string"
    if len(domain) > 253:
        return False, "Domain exceeds maximum length of 253 characters"
    if not re.match(r"^[a-zA-Z0-9.-]+$", domain):
        return False, "Domain contains invalid characters"
    return True, ""


def handle_ssl_generate(payload: dict) -> dict:
    """Generate Let's Encrypt / ZeroSSL certificate using Certbot."""
    try:
        domain = payload.get("domain")
        email = payload.get("email")
        provider = payload.get("provider", "letsencrypt")

        # Validate domain
        is_valid_domain, err_msg = _validate_domain(domain)
        if not is_valid_domain:
            return {
                "success": False,
                "error": "invalid_domain",
                "message": err_msg
            }

        # Validate email
        if not email or not isinstance(email, str) or not re.match(r"^[^@\s]+@[^@\s]+$", email) or len(email) > 254:
            return {
                "success": False,
                "error": "invalid_email",
                "message": "Email is invalid or missing"
            }

        # Check for certbot binary
        if not shutil.which("certbot"):
            return {
                "success": False,
                "error": "certbot_not_found",
                "message": "Certbot binary is not installed on the system"
            }

        # Subprocess call setup
        cmd = [
            "certbot", "certonly",
            "--nginx",
            "-d", domain,
            "--non-interactive",
            "--agree-tos",
            "-m", email,
            "--no-eff-email"
        ]

        res = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            shell=False,
            timeout=120
        )

        cert_path = f"/etc/letsencrypt/live/{domain}/fullchain.pem"
        
        if res.returncode == 0:
            return {
                "success": True,
                "message": "SSL certificate generated successfully",
                "cert_path": cert_path,
                "stdout": res.stdout,
                "stderr": res.stderr
            }
        else:
            return {
                "success": False,
                "error": "certbot_failed",
                "message": f"Certbot execution failed with exit code {res.returncode}",
                "stdout": res.stdout,
                "stderr": res.stderr
            }

    except subprocess.TimeoutExpired as exc:
        return {
            "success": False,
            "error": "timeout",
            "message": "Certbot generation command timed out after 120 seconds",
            "stdout": exc.stdout or "",
            "stderr": exc.stderr or ""
        }
    except Exception as exc:
        log.error("SSL generate action failed: %s", exc)
        return {
            "success": False,
            "error": "execution_failed",
            "message": f"Failed to execute certbot command: {str(exc)}"
        }


def handle_ssl_renew(payload: dict) -> dict:
    """Renew existing certificate by cert-name using Certbot."""
    try:
        domain = payload.get("domain")

        # Validate domain (cert-name)
        is_valid_domain, err_msg = _validate_domain(domain)
        if not is_valid_domain:
            return {
                "success": False,
                "error": "invalid_domain",
                "message": err_msg
            }

        # Check for certbot binary
        if not shutil.which("certbot"):
            return {
                "success": False,
                "error": "certbot_not_found",
                "message": "Certbot binary is not installed on the system"
            }

        cmd = [
            "certbot", "renew",
            "--cert-name", domain,
            "--non-interactive"
        ]

        res = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            shell=False,
            timeout=120
        )

        if res.returncode == 0:
            return {
                "success": True,
                "message": "SSL certificate renewed successfully",
                "stdout": res.stdout,
                "stderr": res.stderr
            }
        else:
            return {
                "success": False,
                "error": "certbot_failed",
                "message": f"Certbot renew execution failed with exit code {res.returncode}",
                "stdout": res.stdout,
                "stderr": res.stderr
            }

    except subprocess.TimeoutExpired as exc:
        return {
            "success": False,
            "error": "timeout",
            "message": "Certbot renew command timed out after 120 seconds",
            "stdout": exc.stdout or "",
            "stderr": exc.stderr or ""
        }
    except Exception as exc:
        log.error("SSL renew action failed: %s", exc)
        return {
            "success": False,
            "error": "execution_failed",
            "message": f"Failed to execute certbot renew command: {str(exc)}"
        }


def handle_ssl_delete(payload: dict) -> dict:
    """Delete certificate files by cert-name using Certbot."""
    try:
        domain = payload.get("domain")

        # Validate domain
        is_valid_domain, err_msg = _validate_domain(domain)
        if not is_valid_domain:
            return {
                "success": False,
                "error": "invalid_domain",
                "message": err_msg
            }

        # Check if the folder exists under /etc/letsencrypt/live
        live_dir = f"/etc/letsencrypt/live/{domain}"
        if not os.path.exists(live_dir):
            return {
                "success": False,
                "error": "cert_not_found",
                "message": f"No certificate named {domain} found under /etc/letsencrypt/live/"
            }

        # Check for certbot binary
        if not shutil.which("certbot"):
            return {
                "success": False,
                "error": "certbot_not_found",
                "message": "Certbot binary is not installed on the system"
            }

        cmd = [
            "certbot", "delete",
            "--cert-name", domain,
            "--non-interactive"
        ]

        res = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            shell=False,
            timeout=120
        )

        if res.returncode == 0:
            return {
                "success": True,
                "message": "SSL certificate deleted successfully",
                "stdout": res.stdout,
                "stderr": res.stderr
            }
        else:
            return {
                "success": False,
                "error": "certbot_failed",
                "message": f"Certbot delete execution failed with exit code {res.returncode}",
                "stdout": res.stdout,
                "stderr": res.stderr
            }

    except subprocess.TimeoutExpired as exc:
        return {
            "success": False,
            "error": "timeout",
            "message": "Certbot delete command timed out after 120 seconds",
            "stdout": exc.stdout or "",
            "stderr": exc.stderr or ""
        }
    except Exception as exc:
        log.error("SSL delete action failed: %s", exc)
        return {
            "success": False,
            "error": "execution_failed",
            "message": f"Failed to execute certbot delete command: {str(exc)}"
        }
