#!/usr/bin/env bash
set -Eeuo pipefail

export DEBIAN_FRONTEND=noninteractive

log(){ echo "[THEX-FIX] $*"; }
warn(){ echo "[THEX-FIX][WARNING] $*" >&2; }
die(){ echo "[THEX-FIX][ERROR] $*" >&2; exit 1; }

[[ "$(id -u)" -eq 0 ]] || die "Run as root."

OLS="/usr/local/lsws"
CONF="$OLS/conf/httpd_config.conf"

[[ -d "$OLS" ]] || die "OpenLiteSpeed is not installed."
[[ -f "$CONF" ]] || die "OpenLiteSpeed server config not found."

log "Stopping OpenLiteSpeed before repair..."
"$OLS/bin/lswsctrl" stop >/dev/null 2>&1 || true

STAMP="$(date +%Y%m%d-%H%M%S)"
cp -a "$CONF" "${CONF}.pre-v4-${STAMP}"

log "Removing broken THEX-generated blocks and stray braces..."
python3 - "$CONF" <<'PY'
from pathlib import Path
import re, sys

cfg = Path(sys.argv[1])
text = cfg.read_text()

def remove_named_block(text, keyword, name):
    pat = re.compile(r"(?m)^[ \t]*" + re.escape(keyword) + r"[ \t]+" + re.escape(name) + r"[ \t]*\{")
    while True:
        m = pat.search(text)
        if not m:
            return text
        brace = text.find("{", m.start(), m.end())
        depth = 0
        end = None
        for i in range(brace, len(text)):
            if text[i] == "{":
                depth += 1
            elif text[i] == "}":
                depth -= 1
                if depth == 0:
                    end = i + 1
                    break
        if end is None:
            return text[:m.start()].rstrip() + "\n"
        text = text[:m.start()] + text[end:]

# Remove all non-Example virtual hosts left by earlier failed repair attempts.
domains = re.findall(r"(?m)^[ \t]*virtualhost[ \t]+([A-Za-z0-9_.-]+)[ \t]*\{", text)
for dom in domains:
    if dom != "Example":
        text = remove_named_block(text, "virtualhost", dom)

# Remove the public listeners and rebuild them cleanly.
text = remove_named_block(text, "listener", "Default")
text = remove_named_block(text, "listener", "HTTPS")

# Remove only top-level stray closing braces.
clean = []
depth = 0
for line in text.splitlines(True):
    if line.strip() == "}" and depth == 0:
        continue
    clean.append(line)
    depth += line.count("{") - line.count("}")
    if depth < 0:
        depth = 0

text = "".join(clean).rstrip() + "\n"

text += r'''
listener Default {
    address                 *:80
    secure                  0
    map                     Example *
}

listener HTTPS {
    address                 *:443
    secure                  1
    keyFile                 /usr/local/lsws/admin/conf/webadmin.key
    certFile                /usr/local/lsws/admin/conf/webadmin.crt
    map                     Example *
}
'''

cfg.write_text(text)
PY

log "Writing a clean Example vHost..."
mkdir -p /var/www/html /usr/local/lsws/Example/html
chown -R nobody:nogroup /var/www/html /usr/local/lsws/Example/html
chmod 755 /var/www/html /usr/local/lsws/Example/html

cat > /usr/local/lsws/conf/vhosts/Example/vhconf.conf <<'CONF'
docRoot                   /var/www/html/

index {
    useServer              0
    indexFiles             index.php, index.html, index.htm
}

context / {
    type                   static
    location               /var/www/html/
    allowBrowse            1
}

extprocessor lsphp74 {
    type                   lsapi
    address                uds://tmp/lshttpd/lsphp74.sock
    maxConns               35
    env                   PHP_LSAPI_CHILDREN=35
    initTimeout            60
    retryTimeout           0
    persistConn             1
    respBuffer              0
    autoStart               2
    path                    /usr/local/lsws/lsphp74/bin/lsphp
    backlog                 100
    instances               1
}

scriptHandler {
    add                    lsapi:lsphp74 php
}

rewrite {
    enable                 1
    autoLoadHtaccess        1
}
CONF

log "Installing clean THEX OpenLiteSpeed helper..."
cat > /usr/local/sbin/thex-ols-site <<'HELPER'
#!/usr/bin/env bash
set -Eeuo pipefail

DOMAIN="${1:-}"
DOCROOT="${2:-}"
ACTION="${3:-create}"

log(){ echo "[THEX-OLS] $*"; }
die(){ echo "[THEX-OLS][ERROR] $*" >&2; exit 1; }

[[ "$(id -u)" -eq 0 ]] || die "Must run as root."

case "$ACTION" in
    create|add|repair|site_create|"") ACTION="create" ;;
    ssl|issue_ssl|enable_ssl) ACTION="ssl" ;;
    *) die "Invalid action: $ACTION" ;;
esac

[[ "$DOMAIN" =~ ^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$ ]] || die "Invalid domain: $DOMAIN"

EXPECTED="/var/www/${DOMAIN}/html"
[[ "$DOCROOT" == "$EXPECTED" ]] || die "Invalid document root. Expected: $EXPECTED"

OLS="/usr/local/lsws"
CONF="$OLS/conf/httpd_config.conf"
VROOT="/var/www/$DOMAIN"
VHOST_DIR="$OLS/conf/vhosts/$DOMAIN"
VHOST_CONF="$VHOST_DIR/vhconf.conf"

mkdir -p "$VROOT/logs" "$VHOST_DIR"
install -d -o nobody -g nogroup -m 755 "$VROOT/html"
chown -R nobody:nogroup "$VROOT" "$VHOST_DIR"

log "Creating/repairing vHost: $DOMAIN"

# IMPORTANT: use server-level logging. Do not create per-vhost log files.
# This avoids the "log file is not writable" failure we just saw.
cat > "$VHOST_CONF" <<CONF
docRoot                   $VROOT/html/

index {
    useServer              0
    indexFiles             index.php, index.html
}

context / {
    type                   static
    location               $VROOT/html/
    allowBrowse            1
}

extprocessor lsphp74 {
    type                   lsapi
    address                uds://tmp/lshttpd/lsphp74.sock
    maxConns               35
    env                    PHP_LSAPI_CHILDREN=35
    initTimeout            60
    retryTimeout           0
    persistConn            1
    respBuffer              0
    autoStart              2
    path                   /usr/local/lsws/lsphp74/bin/lsphp
    backlog                100
    instances              1
}

scriptHandler {
    add                    lsapi:lsphp74 php
}

rewrite {
    enable                 1
    autoLoadHtaccess       1
}
CONF

python3 - "$CONF" "$DOMAIN" "$VHOST_CONF" <<'PY'
from pathlib import Path
import re, sys

cfg = Path(sys.argv[1])
domain = sys.argv[2]
vconf = sys.argv[3]
text = cfg.read_text()

def remove_named_block(text, keyword, name):
    pat = re.compile(r"(?m)^[ \t]*" + re.escape(keyword) + r"[ \t]+" + re.escape(name) + r"[ \t]*\{")
    while True:
        m = pat.search(text)
        if not m:
            return text
        brace = text.find("{", m.start(), m.end())
        depth = 0
        end = None
        for i in range(brace, len(text)):
            if text[i] == "{":
                depth += 1
            elif text[i] == "}":
                depth -= 1
                if depth == 0:
                    end = i + 1
                    break
        if end is None:
            return text[:m.start()].rstrip() + "\n"
        text = text[:m.start()] + text[end:]

text = remove_named_block(text, "virtualhost", domain)

vblock = (
    f"virtualhost {domain} {{\n"
    f"    vhRoot                  /var/www/{domain}/\n"
    f"    configFile              {vconf}\n"
    f"    allowSymbolLink         1\n"
    f"    enableScript            1\n"
    f"    restrained              1\n"
    f"}}\n"
)
text = text.rstrip() + "\n\n" + vblock

def replace_listener(text, name, body):
    pat = re.compile(r"(?m)^[ \t]*listener[ \t]+" + re.escape(name) + r"[ \t]*\{")
    m = pat.search(text)
    if not m:
        return text.rstrip() + "\n\n" + body
    brace = text.find("{", m.start(), m.end())
    depth = 0
    end = None
    for i in range(brace, len(text)):
        if text[i] == "{":
            depth += 1
        elif text[i] == "}":
            depth -= 1
            if depth == 0:
                end = i + 1
                break
    if end is None:
        raise SystemExit("Unbalanced listener " + name)
    return text[:m.start()] + body + text[end:]

default_body = (
    "listener Default {\n"
    "    address                 *:80\n"
    "    secure                  0\n"
    "    map                     Example *\n"
    f"    map                     {domain} {domain}\n"
    "}\n"
)
https_body = (
    "listener HTTPS {\n"
    "    address                 *:443\n"
    "    secure                  1\n"
    "    keyFile                 /usr/local/lsws/admin/conf/webadmin.key\n"
    "    certFile                /usr/local/lsws/admin/conf/webadmin.crt\n"
    "    map                     Example *\n"
    f"    map                     {domain} {domain}\n"
    "}\n"
)

text = replace_listener(text, "Default", default_body)
text = replace_listener(text, "HTTPS", https_body)
cfg.write_text(text)
PY

if [[ ! -f "$VROOT/html/index.php" && ! -f "$VROOT/html/index.html" ]]; then
cat > "$VROOT/html/index.php" <<PHP
<?php
echo '<h1>Welcome to ' . htmlspecialchars('${DOMAIN}', ENT_QUOTES, 'UTF-8') . '</h1>';
PHP
fi

chown -R nobody:nogroup "$VROOT"
find "$VROOT" -type d -exec chmod 755 {} \;
find "$VROOT" -type f -exec chmod 644 {} \;

"$OLS/bin/openlitespeed" -t
"$OLS/bin/lswsctrl" reload

if [[ "$ACTION" == "ssl" ]]; then
    command -v certbot >/dev/null 2>&1 || die "Certbot is not installed."
    mkdir -p "$VROOT/html/.well-known/acme-challenge"
    chown -R nobody:nogroup "$VROOT/html/.well-known"
    chmod 755 "$VROOT/html/.well-known" "$VROOT/html/.well-known/acme-challenge"

    getent ahosts "$DOMAIN" >/dev/null 2>&1 || die "DNS does not resolve for $DOMAIN."

    certbot certonly         --webroot         -w "$VROOT/html"         -d "$DOMAIN"         --non-interactive         --agree-tos         --register-unsafely-without-email         --keep-until-expiring || die "Let's Encrypt certificate issuance failed."

    CERT="/etc/letsencrypt/live/$DOMAIN"
    [[ -s "$CERT/fullchain.pem" && -s "$CERT/privkey.pem" ]] || die "Certificate files missing."

    # OpenLiteSpeed virtual-host TLS block.
    if ! grep -qE '^[[:space:]]*vhssl[[:space:]]*\{' "$VHOST_CONF"; then
cat >> "$VHOST_CONF" <<CONF

vhssl {
    keyFile                 $CERT/privkey.pem
    certFile                $CERT/fullchain.pem
}
CONF
    else
        sed -i -E "s#^[[:space:]]*keyFile[[:space:]].*#    keyFile                 $CERT/privkey.pem#" "$VHOST_CONF"
        sed -i -E "s#^[[:space:]]*certFile[[:space:]].*#    certFile                $CERT/fullchain.pem#" "$VHOST_CONF"
    fi

    "$OLS/bin/openlitespeed" -t
    "$OLS/bin/lswsctrl" reload
    echo "OpenLiteSpeed SSL enabled: $DOMAIN"
else
    echo "OpenLiteSpeed website ready: $DOMAIN"
fi
HELPER

chmod 755 /usr/local/sbin/thex-ols-site
chown root:root /usr/local/sbin/thex-ols-site


log "Installing safe THEX domain-create helper..."

cat > /tmp/thex-domain-create <<'DOMAIN_HELPER'
#!/usr/bin/env bash
set -Eeuo pipefail

DOMAIN="${1:-}"
DOCROOT="${2:-}"
PHPVER="${3:-7.4}"

log(){ echo "[THEX-DOMAIN] $*"; }
die(){ echo "[THEX-DOMAIN][ERROR] $*" >&2; exit 1; }

[[ "$(id -u)" -eq 0 ]] || die "Must run as root."

[[ "$DOMAIN" =~ ^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$ ]] \
    || die "Invalid domain: $DOMAIN"

EXPECTED="/var/www/${DOMAIN}/html"
[[ "$DOCROOT" == "$EXPECTED" ]] \
    || die "Invalid document root. Expected: $EXPECTED"

case "$PHPVER" in
    7.4|8.0|8.1|8.2|8.3|8.4)
        ;;
    *)
        die "Unsupported PHP version: $PHPVER"
        ;;
esac

OLS="/usr/local/lsws"
CONF="$OLS/conf/httpd_config.conf"
VROOT="/var/www/$DOMAIN"
VHOST_DIR="$OLS/conf/vhosts/$DOMAIN"
VHOST_CONF="$VHOST_DIR/vhconf.conf"

[[ -d "$OLS" ]] || die "OpenLiteSpeed is not installed."
[[ -f "$CONF" ]] || die "OpenLiteSpeed server config not found."

# Serialize config changes.
exec 9>"${CONF}.lock"
flock 9

# Never overwrite an existing domain.
if grep -qiE "^[[:space:]]*virtualhost[[:space:]]+$DOMAIN[[:space:]]*\{" "$CONF"; then
    die "Domain already exists in OpenLiteSpeed."
fi

STAMP="$(date +%Y%m%d-%H%M%S)"
BACKUP="${CONF}.before-domain-${DOMAIN}-${STAMP}"

cp -a "$CONF" "$BACKUP"

PHPNUM="${PHPVER//./}"
PHPPATH="$OLS/lsphp${PHPNUM}/bin/lsphp"
SOCK="uds://tmp/lshttpd/lsphp${PHPNUM}.sock"

[[ -x "$PHPPATH" ]] || die "PHP ${PHPVER} not found: $PHPPATH"

mkdir -p "$VROOT/html" "$VROOT/logs" "$VHOST_DIR"

cat > "$VHOST_CONF" <<CONF
docRoot                   $VROOT/html/

index {
    useServer              0
    indexFiles             index.php, index.html, index.htm
}

context / {
    type                   static
    location               $VROOT/html/
    allowBrowse            1
}

extprocessor lsphp${PHPNUM} {
    type                   lsapi
    address                $SOCK
    maxConns               35
    env                    PHP_LSAPI_CHILDREN=35
    initTimeout            60
    retryTimeout           0
    persistConn            1
    respBuffer             0
    autoStart              2
    path                   $PHPPATH
    backlog                100
    instances              1
}

scriptHandler {
    add                    lsapi:lsphp${PHPNUM} php
}

rewrite {
    enable                 1
    autoLoadHtaccess       1
}
CONF

chown root:root "$VHOST_CONF"
chmod 644 "$VHOST_CONF"

python3 - "$CONF" "$DOMAIN" "$VHOST_CONF" <<'PY'
from pathlib import Path
import re
import sys

conf = Path(sys.argv[1])
domain = sys.argv[2]
vhost_conf = sys.argv[3]

text = conf.read_text(errors="ignore")

def find_block(text, keyword, name):
    m = re.search(
        rf"(?mi)^[ \t]*{re.escape(keyword)}[ \t]+{re.escape(name)}[ \t]*\{{",
        text
    )
    if not m:
        return None

    brace = text.find("{", m.start(), m.end())
    depth = 0

    for i in range(brace, len(text)):
        if text[i] == "{":
            depth += 1
        elif text[i] == "}":
            depth -= 1
            if depth == 0:
                return m.start(), brace, i

    raise SystemExit(f"Unbalanced {keyword}: {name}")

def add_map(text, listener_name, domain):
    found = find_block(text, "listener", listener_name)

    if found is None:
        raise SystemExit(f"Listener not found: {listener_name}")

    _, brace, end = found
    body = text[brace + 1:end]

    if re.search(
        rf"(?mi)^[ \t]*map[ \t]+{re.escape(domain)}[ \t]+{re.escape(domain)}[ \t]*$",
        body
    ):
        return text

    insertion = f"\n    map                     {domain} {domain}\n"
    return text[:end] + insertion + text[end:]

# Add only the new map; do NOT replace/rebuild listeners.
text = add_map(text, "Default", domain)
text = add_map(text, "HTTPS", domain)

# Add exactly one new virtualhost block.
vhost = (
    f"\n\nvirtualhost {domain} {{\n"
    f"    vhRoot                  /var/www/{domain}/\n"
    f"    configFile              {vhost_conf}\n"
    f"    allowSymbolLink         1\n"
    f"    enableScript            1\n"
    f"    restrained              1\n"
    f"}}\n"
)

text = text.rstrip() + vhost
conf.write_text(text)
PY

chown -R nobody:nogroup "$VROOT"
find "$VROOT" -type d -exec chmod 755 {} \;
find "$VROOT" -type f -exec chmod 644 {} \;

# Create a starter page only when the document root is empty.
if [[ -z "$(find "$VROOT/html" -mindepth 1 -maxdepth 1 -print -quit)" ]]; then
cat > "$VROOT/html/index.php" <<PHP
<?php
echo 'Website ready: ' . htmlspecialchars('${DOMAIN}', ENT_QUOTES, 'UTF-8');
PHP
    chown nobody:nogroup "$VROOT/html/index.php"
    chmod 644 "$VROOT/html/index.php"
fi

# Validate before reloading.
if ! env PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \
    "$OLS/bin/openlitespeed" -t
then
    cp -a "$BACKUP" "$CONF"
    rm -rf "$VHOST_DIR"
    die "OpenLiteSpeed configuration test failed. Previous config restored."
fi

"$OLS/bin/lswsctrl" reload

echo "SUCCESS"
echo "DOMAIN=$DOMAIN"
echo "DOCROOT=$DOCROOT"
echo "PHP=$PHPVER"
echo "BACKUP=$BACKUP"
DOMAIN_HELPER

chmod 755 /tmp/thex-domain-create
chown root:root /tmp/thex-domain-create

cp -a /tmp/thex-domain-create /usr/local/sbin/thex-domain-create
chown root:root /usr/local/sbin/thex-domain-create
chmod 755 /usr/local/sbin/thex-domain-create

bash -n /usr/local/sbin/thex-domain-create || die "Invalid domain helper syntax."

log "THEX domain-create helper installed."


cat > /etc/sudoers.d/thex-panel <<'SUDO'
nobody ALL=(root) NOPASSWD: /usr/local/sbin/thex-ols-site
nobody ALL=(root) NOPASSWD: /usr/local/sbin/thex-domain-create
nobody ALL=(root) NOPASSWD: /usr/local/lsws/bin/lswsctrl restart
SUDO
chmod 440 /etc/sudoers.d/thex-panel
visudo -cf /etc/sudoers.d/thex-panel >/dev/null || die "Invalid THEX sudoers file."

log "Final OLS validation..."
"$OLS/bin/openlitespeed" -t
"$OLS/bin/lswsctrl" restart
sleep 2

ss -lntp | grep -E ':(80|443)\b' || die "80/443 are not listening."

HTTP_CODE="$(curl -4 -sS --max-time 10 -o /dev/null -w '%{http_code}' http://127.0.0.1/ || echo 000)"
[[ "$HTTP_CODE" != "000" ]] || die "Local HTTP connection failed."
log "Local HTTP check passed: HTTP $HTTP_CODE"

if command -v ufw >/dev/null 2>&1; then
    ufw allow 22/tcp >/dev/null 2>&1 || true
    ufw allow 80/tcp >/dev/null 2>&1 || true
    ufw allow 443/tcp >/dev/null 2>&1 || true
    ufw allow 7080/tcp >/dev/null 2>&1 || true
    ufw reload >/dev/null 2>&1 || true
fi

echo
echo "===================================================="
echo "THEX POST-INSTALL FIX V4 COMPLETED"
echo "===================================================="
echo "OpenLiteSpeed : READY"
echo "Apache        : NOT USED"
echo "Nginx         : NOT USED"
echo "HTTP 80       : LISTENING"
echo "HTTPS 443     : LISTENING"
echo "THEX OLS helper: INSTALLED"
echo "===================================================="
