from __future__ import annotations

import glob
import os
import re
import shutil
import subprocess
from actions.database import handle_database_create
from actions.sftp import handle_sftp_create
from actions.ssl import handle_ssl_generate
from identity import load_agent_token
from logger import get_logger

log = get_logger("actions.site")


def _normalize_docroot(docroot: str) -> str:
    # Resolve absolute path and normalize separators
    norm = os.path.abspath(docroot).replace("\\", "/")
    # Strip drive letters (e.g. C:) on Windows
    if len(norm) > 1 and norm[1] == ":":
        norm = norm[2:]
    return norm


def handle_site_create(payload: dict, config, api_client) -> dict:
    """Create a new site, write Nginx vhosts, configure PHP-FPM, and notify the backend."""
    site_id = payload.get("site_id")
    domain = payload.get("domain", "")
    www_alias = payload.get("www_alias", True)
    docroot = payload.get("docroot", "")
    runtime = payload.get("runtime", "static")
    php_version = payload.get("php_version")
    node_port = payload.get("node_port")
    starter_package = payload.get("starter_package") or {}

    # Rollback bookkeeping: only ever removes what THIS attempt created.
    # Pre-existing docroots/files are never touched, even on failure.
    domain_root = None
    created_domain_root = False
    created_available = False
    created_enabled = False
    created_php_pool = False
    php_pool_path = None

    try:
        # Sanitization & Security Validation
        if not domain or not re.match(r"^[a-zA-Z0-9.-]+$", domain):
            raise ValueError("Invalid domain name format")

        if not docroot:
            raise ValueError("docroot is required")

        norm_docroot = _normalize_docroot(docroot)
        if not norm_docroot.startswith("/var/www"):
            raise PermissionError("Access denied: docroot must be under /var/www")

        # Nginx must be present on THIS managed server before anything is
        # written to disk. Checked here (agent process context) rather than
        # relying on the eventual `nginx -t` exec to fail with a raw
        # [Errno 2] — that check happens after side effects, this one is
        # side-effect free and gives an actionable error up front.
        if shutil.which("nginx") is None:
            raise FileNotFoundError(
                "nginx binary not found in PATH on this managed server; "
                "install/verify nginx before retrying"
            )

        domain_root = os.path.join("/var/www", domain)
        domain_root_pre_existed = os.path.isdir(domain_root)

        # ADIM 1 — Dizin oluşturma
        os.makedirs(docroot, exist_ok=True)
        created_domain_root = not domain_root_pre_existed
        docroot_parent = os.path.dirname(docroot)
        subprocess.run(["chown", "www-data:www-data", docroot_parent], shell=False, check=True)
        subprocess.run(["chmod", "755", docroot_parent], shell=False, check=True)

        # İlk index dosyası
        index_path = os.path.normpath(os.path.join(docroot, "index.html"))
        with open(index_path, "w", encoding="utf-8") as f:
            f.write("Site hazır — WolfPanel tarafından oluşturuldu")

        # ADIM 2 — Nginx vhost config oluşturma
        server_names = f"{domain} www.{domain}" if www_alias else domain

        if runtime == "static":
            nginx_config = f"""server {{
    listen 80;
    server_name {server_names};
    root {docroot};
    index index.html index.htm;

    location / {{
        try_files $uri $uri/ =404;
    }}
}}
"""
        elif runtime == "php":
            if not php_version:
                raise ValueError("php_version is required for PHP runtime")
            nginx_config = f"""server {{
    listen 80;
    server_name {server_names};
    root {docroot};
    index index.php index.html index.htm;

    location / {{
        try_files $uri $uri/ /index.php?$query_string;
    }}

    location ~ \\.php$ {{
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php{php_version}-fpm-{domain}.sock;
    }}
}}
"""
        elif runtime in ("node", "python"):
            port = node_port or 3000
            nginx_config = f"""server {{
    listen 80;
    server_name {server_names};

    location / {{
        proxy_pass http://127.0.0.1:{port};
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }}
}}
"""
        else:
            raise ValueError(f"Unsupported runtime: {runtime}")

        available_path = f"/etc/nginx/sites-available/{domain}.conf"
        enabled_path = f"/etc/nginx/sites-enabled/{domain}.conf"

        # Make sure parent directories exist in test environments
        os.makedirs(os.path.dirname(available_path), exist_ok=True)
        os.makedirs(os.path.dirname(enabled_path), exist_ok=True)

        with open(available_path, "w", encoding="utf-8") as f:
            f.write(nginx_config)
        created_available = True

        if os.path.lexists(enabled_path):
            os.unlink(enabled_path)
        os.symlink(available_path, enabled_path)
        created_enabled = True

        # Test Nginx
        nginx_test = subprocess.run(["nginx", "-t"], capture_output=True, text=True, shell=False)
        if nginx_test.returncode != 0:
            raise OSError(f"Nginx configuration test failed: {nginx_test.stderr.strip()}")

        # ADIM 3 — PHP-FPM pool (sadece runtime="php")
        if runtime == "php":
            try:
                pool_config = f"""[{domain}]
user = www-data
group = www-data
listen = /run/php/php{php_version}-fpm-{domain}.sock
listen.owner = www-data
listen.group = www-data
pm = ondemand
pm.max_children = 5
pm.process_idle_timeout = 10s
pm.max_requests = 500
"""
                pool_path = f"/etc/php/{php_version}/fpm/pool.d/{domain}.conf"
                os.makedirs(os.path.dirname(pool_path), exist_ok=True)
                with open(pool_path, "w", encoding="utf-8") as f:
                    f.write(pool_config)
                created_php_pool = True
                php_pool_path = pool_path

                subprocess.run(["systemctl", "reload", f"php{php_version}-fpm"], shell=False, check=True)
            except Exception as e:
                log.warning("Soft fail setting up PHP-FPM pool for %s: %s", domain, e)

        # ADIM 4 — Nginx reload
        subprocess.run(["systemctl", "reload", "nginx"], shell=False, check=True)

        # ADIM 5 — Starter package
        db_suffix = domain.replace('.', '_')
        
        # 5.1 SFTP
        if starter_package.get("sftp"):
            try:
                sftp_payload = {
                    "username_suffix": db_suffix,
                    "password": f"Wolf_{db_suffix}_2026",
                    "home_directory": docroot
                }
                handle_sftp_create(sftp_payload)
            except Exception as e:
                log.warning("Soft fail creating starter package SFTP user: %s", e)

        # 5.2 Database
        if starter_package.get("database"):
            try:
                db_payload = {
                    "name": db_suffix[:16]
                }
                handle_database_create(db_payload)
            except Exception as e:
                log.warning("Soft fail creating starter package Database: %s", e)

        # 5.3 SSL
        if starter_package.get("ssl"):
            try:
                ssl_payload = {
                    "domain": domain,
                    "email": f"admin@{domain}",
                    "provider": "letsencrypt"
                }
                handle_ssl_generate(ssl_payload)
            except Exception as e:
                log.warning("Soft fail generating starter package SSL: %s", e)

        # Notify backend status callback - Success
        token = load_agent_token(config)
        api_client.update_site_status(site_id, {"status": "active", "error": None}, token)

        return {
            "success": True,
            "message": f"Site {domain} created successfully",
            "domain": domain,
            "docroot": docroot
        }

    except Exception as exc:
        server_id = getattr(config, "server_id", "") or "?"
        err_msg = f"[server_id={server_id}] {exc}"
        log.error("Site creation failed: %s", err_msg)

        # Rollback: undo only what this attempt created. Pre-existing
        # docroot/user data under domain_root is never touched.
        if created_enabled:
            try:
                if os.path.lexists(enabled_path):
                    os.unlink(enabled_path)
            except Exception as rb_exc:
                log.warning("Rollback: failed to remove enabled symlink for %s: %s", domain, rb_exc)

        if created_available:
            try:
                if os.path.exists(available_path):
                    os.remove(available_path)
            except Exception as rb_exc:
                log.warning("Rollback: failed to remove available config for %s: %s", domain, rb_exc)

        if created_php_pool and php_pool_path:
            try:
                if os.path.exists(php_pool_path):
                    os.remove(php_pool_path)
            except Exception as rb_exc:
                log.warning("Rollback: failed to remove php-fpm pool for %s: %s", domain, rb_exc)

        if created_domain_root and domain_root:
            try:
                shutil.rmtree(domain_root, ignore_errors=True)
            except Exception as rb_exc:
                log.warning("Rollback: failed to remove domain root %s: %s", domain_root, rb_exc)

        # Notify backend status callback - Failed
        try:
            token = load_agent_token(config)
            api_client.update_site_status(site_id, {"status": "failed", "error": err_msg}, token)
        except Exception as callback_exc:
            log.error("Failed to notify backend site failure: %s", callback_exc)

        return {
            "success": False,
            "error": err_msg
        }


def handle_site_delete(payload: dict, config, api_client) -> dict:
    """Delete site Nginx config, PHP pool, reload Nginx, and delete docroot directory."""
    domain = payload.get("domain", "")
    docroot = payload.get("docroot", "")

    try:
        # Sanitization & Security Validation
        if not domain or not re.match(r"^[a-zA-Z0-9.-]+$", domain):
            raise ValueError("Invalid domain name format")

        if not docroot:
            raise ValueError("docroot is required")

        norm_docroot = _normalize_docroot(docroot).rstrip("/")
        if not norm_docroot.startswith("/var/www") or norm_docroot in ("/var/www", "/var/www/"):
            raise PermissionError("Access denied: docroot must be strictly under /var/www")

        # 1. /etc/nginx/sites-enabled/{domain}.conf symlink'i kaldır
        enabled_path = f"/etc/nginx/sites-enabled/{domain}.conf"
        if os.path.lexists(enabled_path):
            os.unlink(enabled_path)

        # 2. /etc/nginx/sites-available/{domain}.conf dosyasını sil
        available_path = f"/etc/nginx/sites-available/{domain}.conf"
        if os.path.exists(available_path):
            os.remove(available_path)

        # 3. PHP-FPM pool varsa sil: /etc/php/*/fpm/pool.d/{domain}.conf
        pool_paths = glob.glob(f"/etc/php/*/fpm/pool.d/{domain}.conf")
        for p in pool_paths:
            try:
                os.remove(p)
            except Exception as e:
                log.warning("Failed to delete PHP-FPM pool file %s: %s", p, e)

        # 4. systemctl reload nginx
        subprocess.run(["systemctl", "reload", "nginx"], shell=False, check=True)

        # 5. docroot dizinini sil (shutil.rmtree) - Sadece /var/www/ altındaysa sil
        shutil.rmtree(docroot, ignore_errors=True)

        return {
            "success": True,
            "message": f"Site {domain} deleted successfully",
            "domain": domain
        }

    except Exception as exc:
        err_msg = str(exc)
        log.error("Site deletion failed: %s", err_msg)
        return {
            "success": False,
            "error": err_msg
        }
