#!/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 imported by other scripts.

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

See: CLOS-3894
"""

from __future__ import print_function
from __future__ import division
from __future__ import absolute_import

import argparse
import logging
import subprocess
import sys

import cldetectlib

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 clselectstatistics first, then
    falls back to a direct Plesk DB query (handler must not be '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,
        )

    from clcommon import mysql_lib
    import clcommon.cpapi

    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.
    The CLI also triggers web server config regeneration.
    Returns True on success, False on error.
    """
    cmd = [PLESK_DOMAIN_CLI, '--update', 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())
