#!/opt/brepo/ruby33/bin/ruby
# info: action with bunkerweb API
# options: [SSL_CERT_PATH SSL_KEY_PATH]
#
# example: v-bunkerweb-module-install
#
# This function enables and disables additional modules
#
#------------------------------------------#
#                Variables & Functions                     #
#------------------------------------------#

# Argument definition

require "/usr/local/hestia/func_ruby/global_options"

load_ruby_options_defaults
$HESTIA = load_hestia_default_path_from_env

require "main"
require "modules"
require "HestiaBunkerWebApi"
require "securerandom"
require "socket"
require 'json' unless defined?(JSON)
require 'etc'

hestia_check_privileged_user

load_global_bash_variables "/etc/hestiacp/hestia.conf"
if $HESTIA.nil?
  hestia_print_error_message_to_cli "Can't find HESTIA base path"
  exit 1
end

load_global_bash_variables "#{$HESTIA}/conf/hestia.conf"

# Perform verification if read-only mode is enabled
check_hestia_demo_mode

#------------------------------------------#
#                       Action                             #
#------------------------------------------#

puts "========================================================"
puts "=== AUTOMATED BUNKERWEB SETUP SCRIPT ==="
puts "========================================================"
puts ""

# Parse command line arguments for SSL cert and key paths
SSL_CERT_PATH = ARGV[0] || nil  # First argument: SSL certificate path
SSL_KEY_PATH = ARGV[1] || nil   # Second argument: SSL key path

# Generate secure passwords (meeting BunkerWeb password policy requirements)
api_env_file = "/etc/bunkerweb/api.env"
api_password = nil
if File.exist?(api_env_file)
  File.foreach(api_env_file) do |line|
    if line =~ /^\s*API_PASSWORD=(.*)/
      val = $1.strip
      api_password = val unless val.empty?
      break
    end
  end
end
specials = '!@#$%^&*()-_=+[]{}|;:,.<>?'
API_PASSWORD = api_password || (SecureRandom.alphanumeric(24) + specials.chars.sample(3).join).chars.shuffle.join

ui_env_file = "/etc/bunkerweb/ui.env"
admin_password = nil
if File.exist?(ui_env_file)
  File.foreach(ui_env_file) do |line|
    if line =~ /^\s*ADMIN_PASSWORD=(.*)/
      val = $1.strip
      admin_password = val unless val.empty?
      break
    end
  end
end
ADMIN_PASSWORD = admin_password || (SecureRandom.alphanumeric(24) + specials.chars.sample(3).join).chars.shuffle.join

puts "[INFO] Get server IP address"

server_ip_addr = "127.0.0.1"  # default fallback
# Attempt to retrieve server IP via Hestia utility
begin
  cmd = "/usr/local/hestia/bin/v-list-sys-ips json"
  ips_output = `#{cmd}`.strip
  unless ips_output.empty?
    parsed_ips = JSON.parse(ips_output)
    parsed_ips.each do |ip, details|
      if details["OWNER"] == "admin"
        server_ip_addr = ip
        break
      end
    end
  end
rescue JSON::ParserError, Errno::ENOENT
  # If the command fails or output is invalid, keep default fallback
end

puts ""

# Compute nginx group ID once
nginx_gid = Etc.getgrnam('nginx').gid

# Step 1: Create /etc/bunkerweb/api.env configuration file
puts "[INFO] Creating API configuration at /etc/bunkerweb/api.env..."
api_env_content = <<~APIENV
  # ==============================
  # BunkerWeb API Configuration
  # This file lists all supported API environment variables with their defaults.
  # Uncomment and adjust as needed. Lines starting with # are ignored.
  # ==============================

  # --- Network & Proxy ---
  # Listen address/port for the API
  LISTEN_ADDR=127.0.0.1
  LISTEN_PORT=8888
  # Trusted proxy IPs for X-Forwarded-* headers (comma-separated).
  # Default is restricted to loopback for security.
  FORWARDED_ALLOW_IPS=127.0.0.1,::1
  # Trusted proxy IPs for PROXY protocol (comma-separated).
  # Defaults to FORWARDED_ALLOW_IPS when unset.
  PROXY_ALLOW_IPS=127.0.0.1,::1

  # --- Logging & Runtime ---
  # LOG_LEVEL affects most components; CUSTOM_LOG_LEVEL overrides when provided.
  # LOG_LEVEL=info
  LOG_TYPES=file
  LOG_FILE_PATH=/var/log/bunkerweb/api.log
  # Number of workers/threads (auto if unset).
  # MAX_WORKERS=<auto>
  # MAX_THREADS=<auto>

  # --- Authentication & Authorization ---
  # Optional admin Bearer token (grants full access when provided).
  # API_TOKEN=#{API_PASSWORD}
  # Bootstrap admin user (created/validated on startup if provided).
  API_USERNAME=admin
  API_PASSWORD=#{API_PASSWORD}
  # Force re-applying bootstrap admin credentials on startup (use with care).
  # OVERRIDE_API_CREDS=no
  # Fine-grained ACLs can be enabled/disabled here.
  # API_ACL_BOOTSTRAP_FILE=

  # --- IP allowlist ---
  # Enable and shape inbound IP allowlist for the API.
  API_WHITELIST_ENABLED=yes
  WHITELIST_IPS=127.0.0.1

  # --- FastAPI surface ---
  # Customize or disable documentation endpoints. Use 'disabled' to turn off.
  # API_TITLE=BunkerWeb API
  # API_DOCS_URL=/docs
  # API_REDOC_URL=/redoc
  # API_OPENAPI_URL=/openapi.json
  # Mount the API under a subpath (useful behind reverse proxies).
  # API_ROOT_PATH=

  # --- TLS/SSL ---
  # Enable TLS for the API listener (requires cert and key).
  # API_SSL_ENABLED=no
  # Path to PEM-encoded certificate and private key.
  # API_SSL_CERTFILE=/etc/ssl/certs/bunkerweb-api.crt
  # API_SSL_KEYFILE=/etc/ssl/private/bunkerweb-api.key
  # Optional chain/CA bundle and cipher suite.
  # API_SSL_CA_CERTS=
  # API_SSL_CIPHERS_CUSTOM=
  # API_SSL_CIPHERS_LEVEL=modern   # choices: modern|intermediate

  # --- Biscuit keys & policy ---
  # Bind token to client IP (except private ranges).
  # CHECK_PRIVATE_IP=yes
  # Biscuit token lifetime in seconds (0 disables expiry).
  # API_BISCUIT_TTL_SECONDS=3600
  # Provide Biscuit keys via env (hex) instead of files.
  # BISCUIT_PUBLIC_KEY=
  # BISCUIT_PRIVATE_KEY=

  # --- Rate limiting ---
  # Enable/disable and shape rate limiting.
  API_RATE_LIMIT_ENABLED=no
  API_RATE_LIMIT_HEADERS_ENABLED=no
  # Global default limit (times per seconds).
  # API_RATE_LIMIT_TIMES=100
  # API_RATE_LIMIT_SECONDS=60
  # Authentication endpoint limit.
  # API_RATE_LIMIT_AUTH_TIMES=10
  # API_RATE_LIMIT_AUTH_SECONDS=60
  # Advanced limits and rules (CSV/JSON/YAML).
  # API_RATE_LIMIT_DEFAULTS="200/minute"
  # API_RATE_LIMIT_APPLICATION_LIMITS=
  # API_RATE_LIMIT_RULES=
  # Strategy: fixed-window | moving-window | sliding-window-counter
  # API_RATE_LIMIT_STRATEGY=fixed-window
  # Key selector: ip | user | path | method | header:<Name>
  # API_RATE_LIMIT_KEY=ip
  # Exempt IPs (space or comma-separated CIDRs).
  # API_RATE_LIMIT_EXEMPT_IPS=
  # Storage options in JSON (merged with Redis settings if USE_REDIS=yes).
  # API_RATE_LIMIT_STORAGE_OPTIONS=

  # --- Redis (optional, for rate limiting storage) ---
  # USE_REDIS=no
  # REDIS_HOST=
  # REDIS_PORT=6379
  # REDIS_DATABASE=0
  # REDIS_USERNAME=
  # REDIS_PASSWORD=
  # REDIS_SSL=no
  # REDIS_SSL_VERIFY=yes
  # REDIS_TIMEOUT=1000
  # REDIS_KEEPALIVE_POOL=10
  # REDIS_SENTINEL_HOSTS=sentinel1:26379 sentinel2:26379
  # REDIS_SENTINEL_MASTER=mymaster
  # REDIS_SENTINEL_USERNAME=
  # REDIS_SENTINEL_PASSWORD=
APIENV

File.write("/etc/bunkerweb/api.env", api_env_content)
File.chmod(0o660, "/etc/bunkerweb/api.env")
File.chown(0, nginx_gid, "/etc/bunkerweb/api.env")
puts "[SUCCESS] API configuration file created at /etc/bunkerweb/api.env"
puts ""

variables_env_content = <<~VENV
DNS_RESOLVERS=9.9.9.9 149.112.112.112 8.8.8.8 8.8.4.4
HTTP_PORT=80
HTTPS_PORT=443
API_LISTEN_IP=127.0.0.1
MULTISITE=yes
UI_HOST=http://127.0.0.1:7000
SERVER_NAME=

API_WHITELIST_IP=127.0.0.0/8
USE_SERVE_FILES=no
VENV

File.write("/etc/bunkerweb/variables.env", variables_env_content)
File.chmod(0o660, "/etc/bunkerweb/variables.env")
File.chown(0, nginx_gid, "/etc/bunkerweb/variables.env")
puts "[SUCCESS] Variables configuration file created at /etc/bunkerweb/variables.env"
puts ""

# Step 2: Enable and start bunkerweb-api service, wait for it to be running
puts "[INFO] Enabling bunkerweb-api service..."
system("systemctl enable bunkerweb-api")
puts "[INFO] Starting bunkerweb-api service..."
system("systemctl start bunkerweb-api")

sleep(30)

# Wait for the service to be ready (max 30 seconds)
puts "[INFO] Waiting for bunkerweb-api service to be running..."
max_attempts = 60  # Wait up to 30 seconds (check every half second)
attempt = 0

while attempt < max_attempts
  status_output = `systemctl is-active bunkerweb-api 2>&1`
  status = status_output.strip

  if status == "active" || status == "running"
    puts "[SUCCESS] bunkerweb-api service is running!"
    break
  elsif status == "failed"
    puts "[ERROR] bunkerweb-api service failed to start!"
    exit 1
  else
    print "."
    sleep(0.5)
    attempt += 1
  end
end

if attempt >= max_attempts
  puts ""
  puts "[ERROR] bunkerweb-api service did not become active within timeout"
  puts "[INFO] Current status: #{status_output.strip}"
  log_event E_INVALID, $ARGUMENTS
  exit 1
end

puts ""

# Step 3: Configure UI settings based on documentation at https://docs.bunkerweb.io/latest/web-ui/
puts "[INFO] Configuring Web UI..."

# Determine SSL settings for UI
ui_ssl_enabled = "no"
if SSL_CERT_PATH && SSL_KEY_PATH && File.exist?(SSL_CERT_PATH) && File.exist?(SSL_KEY_PATH)
  ui_ssl_enabled = "yes"
end

ui_env_content = <<~UIENV
  # ==============================
  # BunkerWeb UI Configuration
  # This file configures the Web UI settings.
  # ==============================

  # --- Listener & TLS ---
  # Bind address for the UI (use server IP for external access)
  UI_LISTEN_ADDR=127.0.0.1
  # Bind port for the UI
  UI_LISTEN_PORT=7000
  # Enable TLS in the UI container
  UI_SSL_ENABLED=#{ui_ssl_enabled}
UIENV

# Add SSL cert/key paths if provided
if ui_ssl_enabled == "yes"
  ui_env_content += <<~SSLCONF

    # SSL Certificate and Key paths
    UI_SSL_CERTFILE=#{SSL_CERT_PATH}
    UI_SSL_KEYFILE=#{SSL_KEY_PATH}
  SSLCONF
end

ui_env_content += <<~UIENV2

  # --- Admin Authentication ---
  # Seed admin account
  ADMIN_USERNAME=admin
  ADMIN_PASSWORD=#{ADMIN_PASSWORD}

  # --- Proxy settings ---
  # Trusted proxy IPs for X-Forwarded-* headers
  # UI_FORWARDED_ALLOW_IPS=127.0.0.1,::1

UIENV2

File.write("/etc/bunkerweb/ui.env", ui_env_content)
File.chmod(0o660, "/etc/bunkerweb/ui.env")
File.chown(0, nginx_gid, "/etc/bunkerweb/ui.env")
puts "[SUCCESS] UI configuration file created at /etc/bunkerweb/ui.env"
puts ""

# Step 4: Reload the bunkerweb-ui service to apply new configuration
puts "[INFO] Reloading bunkerweb-ui service..."
system("systemctl restart bunkerweb-ui")

# Wait for UI to be ready (max 10 seconds)
sleep(2)

if system("systemctl is-active bunkerweb-ui >/dev/null 2>&1")
  puts "[SUCCESS] bunkerweb-ui service is running!"
else
  puts "[WARN] bunkerweb-ui service status could not be verified"
end

puts ""

# Step 5: Now proceed with the original service creation logic
puts "[INFO] Connecting to BunkerWeb API..."

API_URL = "http://127.0.0.1:8888"

USERNAME = "admin"
PASSWORD = API_PASSWORD

# Default services to create after setup
DEFAULT_SERVICES = [
  {
    name: "#{server_ip_addr}",
    options: {
      ssl: "no",
      reverse_proxy_host: "http://127.0.0.1:7000",
      use_template: "ui",
      reverse_proxy_url: "/kormilo",
      use_reverse_proxy: "yes"
    }
  },
  # Add more services here if needed:
  # {
  #   name: "secure.example.com",
  #   options: {
  #     ssl: "yes",
  #     certificate_path: "/etc/ssl/certs/example.crt",
  #     key_path: "/etc/ssl/private/example.key"
  #   }
  # }
]

begin
  api = HestiaBunkerWebApi.new(API_URL, USERNAME, PASSWORD)

  puts ""
  puts "[SUCCESS] API connected successfully!"
  puts ""

  # List existing services
  services = api.list_services()
  services = api.list_services()
  if services && services.is_a?(Hash) && services.key?('services')
    services = services['services']
    services = nil if services.is_a?(Array) && services.empty?
  else
    services = nil
  end
  if services.nil?
    puts "[INFO] No services found - creating default configuration..."

    DEFAULT_SERVICES.each do |service_config|
      begin
        puts "[INFO] Creating service: #{service_config[:name]}"
        result = api.create_service(service_config[:name], service_config[:options])
        puts "  ✓ Service '#{service_config[:name]}' created"
      rescue BunkerWebApiError => e
        if e.message.include?("already exists")
          puts "  ℹ Service '#{service_config[:name]}' already exists, skipping..."
        else
          raise
        end
      end

    end

    puts "[SUCCESS] Default services created!"

  else
    puts "[INFO] Existing services:"
    services.each { |s| puts "  - #{s['server_name']}" }
    puts ""
  end

  # Reload configuration on all instances
  puts "[INFO] Reloading configuration..."
  api.reload_instance()

  puts "[INFO] Restarting bunkerweb service..."
  system("systemctl restart bunkerweb")
  puts "[INFO] Restarting bunkerweb-scheduler service..."
  system("systemctl restart bunkerweb-scheduler")

  puts ""
  puts "======================================================"
  puts "=== SETUP COMPLETED SUCCESSFULLY ==="
  puts "======================================================"
  puts ""
  puts "Web UI is now accessible at:"
  if ui_ssl_enabled == "yes"
    puts "  https://#{server_ip_addr}/kormilo"
  else
    puts "  http://#{server_ip_addr}/kormilo"
  end
  puts ""
  puts "API URL: #{API_URL}"
  puts ""
  puts "API Credentials:"
  puts "  Username: admin"
  puts "  Password: #{PASSWORD}"
  puts ""

  puts "UI Credentials:"
  puts "  Username: admin"
  puts "  Password: #{ADMIN_PASSWORD}"
  puts ""

rescue BunkerWebApiError => e

  if e.message.include?("Authentication") || e.message.include?("Connection refused")
    puts "[ERROR] Could not connect to BunkerWeb API"
    puts "[INFO] This means the setup has NOT been completed correctly"
    puts ""
    puts "Please verify that:"
    puts "  1. bunkerweb-api service is running: systemctl status bunkerweb-api"
    puts "  2. API configuration file exists at /etc/bunkerweb/api.env"
    puts "  3. Check logs: journalctl -u bunkerweb-api -f"
    puts ""
    log_event E_INVALID, $ARGUMENTS
    exit 1

  else
    puts "[ERROR] #{e.message}"
    log_event E_INVALID, $ARGUMENTS
    exit 1
  end

rescue => e
  puts "[ERROR] Unexpected error: #{e.message}"
  puts "Backtrace:"
  puts e.backtrace.inspect
  log_event E_INVALID, $ARGUMENTS
  exit 1
end

exit 0
