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

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
MAGENTA='\033[1;35m'
CYAN='\033[1;36m'
BOLD='\033[1m'
NC='\033[0m'

NODE_DIR="/opt/remnanode"
SELFSTEAL_DIR="/opt/selfsteal"
HTML_DIR="/opt/html"
LOG_DIR="/var/log/remnanode"
NODE_PORT="8881"
SELF_STEAL_PORT="8883"
XHTTP_PATH="/xhttppath/"
PROJECT_ZIP="/tmp/project.zip"
PROJECT_TMP_DIR="/tmp/project"
LOGROTATE_FILE="/etc/logrotate.d/remnanode"

info() { echo -e "${BLUE}[INFO]${NC} $*"; }
success() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERR]${NC} $*" >&2; }
banner() { echo -e "${MAGENTA}${BOLD}$*${NC}"; }

on_error() {
  error "Скрипт остановлен на строке $1. Проверь вывод выше."
}
trap 'on_error $LINENO' ERR

require_root() {
  if [[ ${EUID:-$(id -u)} -ne 0 ]]; then
    error "Запусти скрипт от root: sudo bash install_remnanode.sh"
    exit 1
  fi
}

check_commands() {
  local cmds=(docker ufw unzip curl sed grep awk nano logrotate python3)
  if [[ "$TRANSPORT" == "xhttp" ]]; then
    cmds+=(nginx certbot systemctl)
  fi

  for cmd in "${cmds[@]}"; do
    if ! command -v "$cmd" >/dev/null 2>&1; then
      error "Не найдена команда: $cmd"
      exit 1
    fi
  done
  success "Проверка необходимых команд завершена"
}

ask_transport() {
  while true; do
    echo
    echo -e "${CYAN}${BOLD}Выбор транспорта:${NC}"
    echo -e "  📌 TCP нажми - 1"
    echo -e "  📌 XHTTP нажми - 2"
    read -r -p "Нажми 1 или 2: " transport_choice

    case "$transport_choice" in
      1)
        TRANSPORT="tcp"
        success "Выбран транспорт: tcp"
        break
        ;;
      2)
        TRANSPORT="xhttp"
        success "Выбран транспорт: xhttp"
        break
        ;;
      *)
        warn "Допустимы только значения: 1 или 2"
        ;;
    esac
  done
}

pause_enter() {
  read -r -p "$* Нажми Enter для продолжения..."
}

ask_panel_ip() {
  while true; do
    read -r -p "Укажи IP панели Remnawave для ограничения NODE_PORT по UFW: " PANEL_IP
    PANEL_IP="$(echo "$PANEL_IP" | xargs)"
    if [[ "$PANEL_IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
      success "IP панели принят: $PANEL_IP"
      break
    fi
    warn "Похоже, это не IPv4-адрес. Попробуй ещё раз."
  done
}

remind_dns() {
  warn "Перед продолжением создай A-запись домена будущей ноды на IP этого сервера."
  pause_enter "После создания DNS-записи"
}

setup_ufw() {
  info "Настраиваю правила UFW"
  ufw allow 80/tcp >/dev/null
  ufw allow 443/tcp >/dev/null
  ufw allow from "$PANEL_IP" to any port "$NODE_PORT" proto tcp >/dev/null

  if [[ "$TRANSPORT" == "tcp" ]]; then
    ufw allow "$SELF_STEAL_PORT"/tcp >/dev/null
  fi

  success "Правила UFW добавлены успешно"
}

prepare_node_dir() {
  mkdir -p "$NODE_DIR"
  success "Директория $NODE_DIR готова"
}

edit_node_compose() {
  echo
  banner "=============================================================="
  banner "Сейчас откроется nano: вставь docker-compose из кнопки"
  banner "Copy docker-compose.yml в Web панели Remnawave и СОХРАНИ файл"
  banner "Путь: $NODE_DIR/docker-compose.yml"
  banner "=============================================================="
  sleep 3

  touch "$NODE_DIR/docker-compose.yml"
  nano "$NODE_DIR/docker-compose.yml"

  if [[ ! -s "$NODE_DIR/docker-compose.yml" ]]; then
    error "Файл $NODE_DIR/docker-compose.yml пустой"
    exit 1
  fi
  success "docker-compose.yml сохранён"
}

normalize_node_port() {
  local file="$NODE_DIR/docker-compose.yml"

  if grep -Eq 'NODE_PORT\s*=\s*2222' "$file"; then
    sed -Ei 's/(NODE_PORT\s*=\s*)2222/\18881/g' "$file"
    success "Найден NODE_PORT=2222, автоматически заменён на NODE_PORT=8881"
    return
  fi

  if grep -Eq 'NODE_PORT\s*=\s*8881' "$file"; then
    success "В compose уже указан NODE_PORT=8881"
    return
  fi

  if grep -Eq 'NODE_PORT\s*=' "$file"; then
    local current
    current="$(grep -Eo 'NODE_PORT\s*=\s*[0-9]+' "$file" | head -n1 | sed -E 's/.*=\s*([0-9]+)/\1/')"
    warn "В compose найден NODE_PORT=$current, он отличается от 8881"
    while true; do
      read -r -p "Заменить его на 8881 автоматически? [Y/n]: " ans
      ans="${ans:-Y}"
      case "$ans" in
        Y|y|'')
          sed -Ei 's/(NODE_PORT\s*=\s*)[0-9]+/\18881/g' "$file"
          success "NODE_PORT заменён на 8881"
          return
          ;;
        N|n)
          NODE_PORT="$current"
          warn "Оставляю NODE_PORT=$NODE_PORT как в compose"
          return
          ;;
        *)
          warn "Ответь Y или n"
          ;;
      esac
    done
  fi

  while true; do
    read -r -p "NODE_PORT не найден, добавить 8881 автоматически? [Y/n]: " ans
    ans="${ans:-Y}"
    case "$ans" in
      Y|y|'')
        python3 - "$file" <<'PY'
from pathlib import Path
import sys
p = Path(sys.argv[1])
text = p.read_text()
lines = text.splitlines()
out = []
inserted = False
for line in lines:
    out.append(line)
    if not inserted and line.strip() == 'environment:':
        indent = line[:len(line) - len(line.lstrip())] + '  '
        out.append(f"{indent}- NODE_PORT=8881")
        inserted = True
if not inserted:
    raise SystemExit(1)
p.write_text("\n".join(out) + "\n")
PY
        success "NODE_PORT=8881 добавлен в секцию environment"
        return
        ;;
      N|n)
        error "NODE_PORT отсутствует. Исправь compose вручную и запусти скрипт снова."
        exit 1
        ;;
      *)
        warn "Ответь Y или n"
        ;;
    esac
  done
}

ensure_node_volumes() {
  local file="$NODE_DIR/docker-compose.yml"

  python3 - "$file" "$TRANSPORT" <<'PY'
from pathlib import Path
import sys

path = Path(sys.argv[1])
transport = sys.argv[2]
text = path.read_text()
lines = text.splitlines()

required = ['/var/log/remnanode:/var/log/remnanode']
if transport == 'xhttp':
    required = ['/dev/shm:/dev/shm'] + required

service_start = None
service_end = None
for idx, line in enumerate(lines):
    if line.strip() == 'remnanode:':
        service_start = idx
        break

if service_start is None:
    raise SystemExit("remnanode service not found")

base_indent = len(lines[service_start]) - len(lines[service_start].lstrip())
service_indent = ' ' * base_indent
child_indent = service_indent + '  '
list_indent = child_indent + '  '

for idx in range(service_start + 1, len(lines)):
    line = lines[idx]
    stripped = line.strip()
    indent = len(line) - len(line.lstrip())
    if stripped and indent <= base_indent:
        service_end = idx
        break
if service_end is None:
    service_end = len(lines)

block = lines[service_start:service_end]
existing = set()
in_volumes = False
vol_indent = None

for line in block:
    stripped = line.strip()
    indent = len(line) - len(line.lstrip())
    if stripped == 'volumes:':
        in_volumes = True
        vol_indent = indent
        continue
    if in_volumes:
        if stripped and indent <= vol_indent:
            in_volumes = False
        elif stripped.startswith('- '):
            existing.add(stripped[2:].strip())

missing = [v for v in required if v not in existing]
if not missing:
    path.write_text("\n".join(lines) + "\n")
    raise SystemExit(0)

new_block = []
inserted = False
i = 0
while i < len(block):
    line = block[i]
    stripped = line.strip()
    indent = len(line) - len(line.lstrip())

    if stripped == 'volumes:':
        new_block.append(line)
        i += 1
        while i < len(block):
            nxt = block[i]
            nxt_stripped = nxt.strip()
            nxt_indent = len(nxt) - len(nxt.lstrip())
            if nxt_stripped and nxt_indent <= indent:
                break
            new_block.append(nxt)
            i += 1
        for v in missing:
            new_block.append(f"{list_indent}- {v}")
        inserted = True
        continue

    new_block.append(line)
    i += 1

if not inserted:
    insert_after = None
    for idx, line in enumerate(block):
        if line.strip() == 'environment:':
            env_indent = len(line) - len(line.lstrip())
            insert_after = idx + 1
            while insert_after < len(block):
                nxt = block[insert_after]
                nxt_stripped = nxt.strip()
                nxt_indent = len(nxt) - len(nxt.lstrip())
                if nxt_stripped and nxt_indent <= env_indent:
                    break
                insert_after += 1
            break

    if insert_after is None:
        insert_after = len(block)

    new_block = (
        block[:insert_after]
        + [f"{child_indent}volumes:"]
        + [f"{list_indent}- {v}" for v in required]
        + block[insert_after:]
    )

lines[service_start:service_end] = new_block
path.write_text("\n".join(lines) + "\n")
PY

  if [[ "$TRANSPORT" == "tcp" ]]; then
    success "В compose добавлен volumes: /var/log/remnanode:/var/log/remnanode"
  else
    success "В compose добавлены volumes: /dev/shm:/dev/shm и /var/log/remnanode:/var/log/remnanode"
  fi
}

prepare_logs() {
  mkdir -p "$LOG_DIR"
  success "Папка логов $LOG_DIR создана"
}

wait_for_banner_ready() {
  local attempts=60
  local delay=2
  local log_snapshot
  local pattern="Network Interfaces:"

  info "Ожидаю полный стартовый баннер Remnawave Node..."

  for ((i=1; i<=attempts; i++)); do
    log_snapshot="$(cd "$NODE_DIR" && docker compose logs --no-color remnanode 2>&1 || true)"

    if grep -Fq "$pattern" <<< "$log_snapshot"; then
      success "Стартовый баннер Remnawave Node обнаружен"
      echo
      echo "$log_snapshot" | tail -n 20
      echo
      return 0
    fi

    warn "Попытка $i/$attempts: баннер ещё не полный, жду ${delay}s"
    sleep "$delay"
  done

  warn "Не удалось дождаться полного баннера ноды"
  warn "Последние строки логов remnanode:"
  (
    cd "$NODE_DIR"
    docker compose logs --tail=50 --no-color remnanode || true
  )
  return 1
}

start_remnanode_and_wait() {
  info "Поднимаю контейнер remnanode"
  cd "$NODE_DIR"
  docker compose up -d
  sleep 3

  if docker compose ps --status running | grep -q remnanode; then
    success "Контейнер remnanode запущен"
  else
    warn "Не удалось подтвердить статус running через docker compose ps"
  fi

  wait_for_banner_ready || true

  echo
  banner "=============================================================="
  banner "Заверши настройку ноды в панели и нажми Enter"
  banner "=============================================================="
  read -r
}

setup_logrotate() {
  info "Настраиваю logrotate"
  cat > "$LOGROTATE_FILE" <<EOF2
/var/log/remnanode/*.log {
    size 50M
    rotate 5
    compress
    missingok
    notifempty
    copytruncate
}
EOF2
  logrotate -vf "$LOGROTATE_FILE" >/dev/null 2>&1 || true
  success "logrotate установлен и настроен"
}

ask_domain() {
  while true; do
    read -r -p "Укажи домен для этой ноды: " NODE_DOMAIN
    NODE_DOMAIN="$(echo "$NODE_DOMAIN" | xargs)"
    if [[ -n "$NODE_DOMAIN" ]]; then
      success "Домен принят: $NODE_DOMAIN"
      break
    fi
    warn "Домен не может быть пустым"
  done
}

wait_for_project_zip() {
  while [[ ! -f "$PROJECT_ZIP" ]]; do
    warn "Архив $PROJECT_ZIP не найден. Загрузи project.zip в /tmp и нажми Enter для повторной проверки."
    read -r
  done
  success "Архив $PROJECT_ZIP найден"
}

install_stub_site() {
  mkdir -p "$HTML_DIR"
  wait_for_project_zip
  rm -rf "$PROJECT_TMP_DIR"
  mkdir -p "$PROJECT_TMP_DIR"
  unzip -o "$PROJECT_ZIP" -d "$PROJECT_TMP_DIR" >/dev/null
  cp -r "$PROJECT_TMP_DIR"/* "$HTML_DIR"/
  ls -la "$HTML_DIR"
  chmod -R 755 "$HTML_DIR"
  rm -f "$PROJECT_ZIP"
  rm -rf "$PROJECT_TMP_DIR"
  success "Сайт-заглушка распакован и установлен в $HTML_DIR"
}

check_http_codes() {
  local url="$1"
  local insecure="${2:-0}"
  local attempts=10
  local delay=3
  local code
  local curl_opts=(--silent --output /dev/null --write-out '%{http_code}' --max-time 10 --location)

  if [[ "$insecure" == "1" ]]; then
    curl_opts+=(--insecure)
  fi

  for ((i=1; i<=attempts; i++)); do
    code="$(curl "${curl_opts[@]}" "$url" || true)"
    if [[ "$code" == "200" || "$code" == "301" || "$code" == "302" ]]; then
      success "Проверка $url успешна, HTTP code: $code"
      return 0
    fi
    warn "Попытка $i/$attempts: $url вернул код ${code:-000}, жду ${delay}s"
    sleep "$delay"
  done

  warn "Веб-проверка не прошла для $url, но скрипт продолжает работу"
  return 1
}

setup_tcp_branch() {
  info "Настраиваю tcp/selfsteal ветку"
  mkdir -p "$SELFSTEAL_DIR"

  cat > "$SELFSTEAL_DIR/Caddyfile" <<'EOF2'
{
    https_port {$SELF_STEAL_PORT}
    default_bind 127.0.0.1
    servers {
        listener_wrappers {
            proxy_protocol {
                allow 127.0.0.1/32
            }
            tls
        }
    }
    auto_https disable_redirects
}

http://{$SELF_STEAL_DOMAIN} {
    bind 0.0.0.0
    redir https://{$SELF_STEAL_DOMAIN}{uri} permanent
}

https://{$SELF_STEAL_DOMAIN} {
    root * /var/www/html
    try_files {path} /index.html
    file_server
}

:{$SELF_STEAL_PORT} {
    tls internal
    respond 204
}

:80 {
    bind 0.0.0.0
    respond 204
}
EOF2

  cat > "$SELFSTEAL_DIR/.env" <<EOF2
SELF_STEAL_DOMAIN=$NODE_DOMAIN
SELF_STEAL_PORT=$SELF_STEAL_PORT
EOF2

  cat > "$SELFSTEAL_DIR/docker-compose.yml" <<'EOF2'
services:
  caddy:
    image: caddy:latest
    container_name: caddy-remnawave
    restart: unless-stopped
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ../html:/var/www/html
      - ./logs:/var/log/caddy
      - caddy_data_selfsteal:/data
      - caddy_config_selfsteal:/config
    env_file:
      - .env
    network_mode: "host"

volumes:
  caddy_data_selfsteal:
  caddy_config_selfsteal:
EOF2

  success "Файлы Caddy/selfsteal созданы"
  install_stub_site

  cd "$SELFSTEAL_DIR"
  docker compose up -d
  sleep 3
  docker compose logs --tail=30 --no-color -t || true
  success "Контейнер Caddy поднят"

  check_http_codes "http://$NODE_DOMAIN" 0 || true
  check_http_codes "https://$NODE_DOMAIN" 1 || true
  warn "Если сайт отвечает, заверши настройку ноды в Web панели Remnawave"
}

backup_nginx_conf() {
  local conf="$1"
  if [[ -f "$conf" ]]; then
    cp "$conf" "$conf.bak.$(date +%F-%H%M%S)"
    success "Создана резервная копия $conf"
  fi
}

setup_xhttp_branch() {
  info "Настраиваю xhttp/nginx ветку"
  install_stub_site

  local conf="/etc/nginx/sites-enabled/${NODE_DOMAIN}.conf"
  backup_nginx_conf "$conf"

  cat > "$conf" <<EOF2
server {
    listen 80;
    listen [::]:80;
    server_name $NODE_DOMAIN;

    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }

    return 301 https://\$host\$request_uri;
}
EOF2

  nginx -t
  systemctl restart nginx
  success "HTTP-конфиг Nginx создан и применён"

  certbot --nginx -d "$NODE_DOMAIN"
  success "SSL-сертификат получен через certbot"

  backup_nginx_conf "$conf"
  cat > "$conf" <<EOF2
server {
    listen 80;
    listen [::]:80;
    server_name $NODE_DOMAIN;

    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }

    return 301 https://\$host\$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name $NODE_DOMAIN;

    ssl_certificate /etc/letsencrypt/live/$NODE_DOMAIN/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/$NODE_DOMAIN/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    root $HTML_DIR;
    index index.html index.htm;

    location / {
        try_files \$uri \$uri/ =404;
    }

    location /files/ {
        alias $HTML_DIR/files/;
        autoindex on;
        autoindex_exact_size off;
        autoindex_localtime on;
        add_header Cache-Control "public, max-age=3600";
        gzip on;
        expires 1h;
    }

    location $XHTTP_PATH {
        client_max_body_size 0;
        grpc_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;

        client_body_timeout 5m;
        grpc_read_timeout 315s;
        grpc_send_timeout 5m;

        grpc_pass unix:/dev/shm/xrxh.socket;
    }
}
EOF2

  nginx -t
  systemctl restart nginx
  success "HTTPS-конфиг Nginx создан и применён"

  if [[ -S /dev/shm/xrxh.socket ]]; then
    success "Сокет /dev/shm/xrxh.socket найден"
  else
    warn "Сокет /dev/shm/xrxh.socket не найден"
  fi

  check_http_codes "http://$NODE_DOMAIN" 0 || true
  check_http_codes "https://$NODE_DOMAIN" 1 || true
  warn "Если сайт отвечает и сокет на месте, заверши настройку ноды в Web панели Remnawave"
}

main() {
  require_root
  ask_transport
  check_commands
  remind_dns
  ask_panel_ip
  setup_ufw
  prepare_node_dir
  edit_node_compose
  normalize_node_port
  ensure_node_volumes
  prepare_logs
  start_remnanode_and_wait
  setup_logrotate
  ask_domain

  if [[ "$TRANSPORT" == "tcp" ]]; then
    setup_tcp_branch
  else
    setup_xhttp_branch
  fi

  success "Сценарий завершён"
}

main "$@"