#!/opt/cloudlinux/venv/bin/python3 -bb
# -*- coding: utf-8 -*-

#
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2019 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT

"""
Ensure nginxServePhp is disabled for PHP Selector compatible domains on Plesk.

When PHP Selector is active (domains using CGI/FastCGI handlers), nginx
should proxy PHP requests to Apache, not serve them directly via FPM.
If nginxServePhp remains true, Nginx generates fastcgi_pass directives
pointing to non-existent php-fpm sockets, causing 502/socket errors.

Can be run standalone or called via subprocess by other scripts.

Usage:
    plesk_nginx_php_sync.py [--dry-run] [--domain DOMAIN] [-v]

See: CLOS-3894
"""

import argparse
import logging
import subprocess
import sys

import clcommon.cpapi
import cldetectlib
from clcommon import mysql_lib

PLESK_DOMAIN_CLI = "/usr/local/psa/bin/domain"

log = logging.getLogger("plesk_nginx_php_sync")


def _get_selector_compatible_domains():
    """
    Return flat set of all PHP Selector compatible domain names on Plesk.
    Uses the existing panel-agnostic API via clselect.
    """
    from clselect.clselectdomains import get_all_selector_compatible_domains_flat

    return get_all_selector_compatible_domains_flat()


def _is_domain_selector_compatible(domain_name):
    """
    Check if a single domain is PHP Selector compatible on Plesk.
    Uses the canonical check from clselectdomains first, then
    falls back to a direct Plesk DB query (handler must not end
    with 'fpm', the only mode where nginx should serve PHP directly).
    """
    try:
        compatible = _get_selector_compatible_domains()
        return domain_name in compatible
    except Exception:
        log.debug(
            "Canonical check unavailable, falling back to DB query",
            exc_info=True,
        )

    try:
        db_access = clcommon.cpapi.db_access()
        connector = mysql_lib.MySQLConnector(
            host="localhost",
            user=db_access["login"],
            passwd=db_access["pass"],
            db="psa",
        )
        query = "SELECT h.php_handler_id FROM hosting h JOIN domains d ON h.dom_id = d.id WHERE d.name = %s LIMIT 1"
        with connector.connect() as db:
            rows = db.execute_query(query, (domain_name,))
        if rows and rows[0][0]:
            return not rows[0][0].endswith("fpm")
    except Exception:
        log.debug(
            "Could not determine handler for %s",
            domain_name,
            exc_info=True,
        )
    return False


def disable_nginx_serve_php(domain, dry_run=False):
    """
    Set nginxServePhp=false for the given domain via Plesk CLI.
    Uses --update-web-server-settings (not --update, which silently
    ignores -nginx-serve-php). Also triggers web server config regeneration.
    Returns True on success, False on error.
    """
    cmd = [PLESK_DOMAIN_CLI, "--update-web-server-settings", domain, "-nginx-serve-php", "false"]
    if dry_run:
        log.info("DRY RUN: would run: %s", " ".join(cmd))
        return True
    try:
        subprocess.check_output(cmd, stderr=subprocess.STDOUT, timeout=60)
        log.info("Set nginxServePhp=false for domain: %s", domain)
        return True
    except subprocess.CalledProcessError as e:
        log.warning(
            "Failed to update domain %s: %s",
            domain,
            e.output.decode(errors="replace").strip(),
        )
        return False
    except subprocess.TimeoutExpired:
        log.warning("Timeout updating domain %s", domain)
        return False


def sync_all(dry_run=False):
    """
    Sync nginxServePhp=false for all PHP Selector compatible domains.
    Returns (success_count, failure_count).
    """
    try:
        domains = _get_selector_compatible_domains()
    except Exception:
        log.error("Failed to get PHP Selector domains", exc_info=True)
        return 0, 0

    if not domains:
        log.info("No PHP Selector compatible domains found")
        return 0, 0

    log.info("Found %d PHP Selector compatible domain(s)", len(domains))
    success = 0
    failed = 0
    for domain in sorted(domains):
        if disable_nginx_serve_php(domain, dry_run=dry_run):
            success += 1
        else:
            failed += 1
    return success, failed


def sync_domain(domain, dry_run=False):
    """
    Sync nginxServePhp=false for a single domain if it is PHP Selector
    compatible (uses CGI/FastCGI handler). Safe to call for any domain —
    non-compatible domains are skipped.
    Returns True if updated or skipped, False on error.
    """
    if not _is_domain_selector_compatible(domain):
        log.debug("Domain %s is not PHP Selector compatible, skipping", domain)
        return True
    return disable_nginx_serve_php(domain, dry_run=dry_run)


def main():
    parser = argparse.ArgumentParser(
        description="Sync Plesk nginxServePhp=false for PHP Selector domains",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Show what would be done without making changes",
    )
    parser.add_argument(
        "--domain",
        type=str,
        help="Update a specific domain instead of all",
    )
    parser.add_argument(
        "--verbose",
        "-v",
        action="store_true",
        help="Enable verbose logging",
    )
    args = parser.parse_args()

    logging.basicConfig(
        level=logging.DEBUG if args.verbose else logging.INFO,
        format="%(levelname)s: %(message)s",
    )

    if not cldetectlib.is_plesk():
        log.info("Not a Plesk server, nothing to do")
        return 0

    if args.domain:
        ok = sync_domain(args.domain, dry_run=args.dry_run)
        return 0 if ok else 1

    success, failed = sync_all(dry_run=args.dry_run)
    log.info("Done: %d updated, %d failed", success, failed)
    return 1 if failed > 0 else 0


if __name__ == "__main__":
    sys.exit(main())
