#!/bin/bash
# =============================================================================
# Mastodon Production Installer
# =============================================================================
# Self-hosted Mastodon instance - production ready with Docker
#
# Supported: Ubuntu, Debian, Fedora, Rocky/Alma/RHEL 8+, Arch, openSUSE
# Deploys via Docker Compose
#
# Usage:
#   curl -fsSL <url>/install.sh | sudo bash
#
# Options:
#   --domain <domain>    Your domain (required)
#   --email <email>      Admin email / Let's Encrypt
#   --no-ssl             Skip SSL (local testing only)
#   --single-user        Single user mode
#   --s3                 Enable S3 storage configuration
# =============================================================================

set -o pipefail

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

log()     { echo -e "${BLUE}[INFO]${NC} $1"; }
success() { echo -e "${GREEN}[OK]${NC} $1"; }
warn()    { echo -e "${YELLOW}[WARN]${NC} $1"; }
error()   { echo -e "${RED}[ERROR]${NC} $1" >&2; exit 1; }

# Configuration
INSTALL_DIR="/opt/mastodon"
DATA_DIR="/opt/mastodon-data"
DOMAIN=""
ADMIN_EMAIL=""
ENABLE_SSL=true
SINGLE_USER_MODE=false
ENABLE_S3=false

# Parse arguments
while [ $# -gt 0 ]; do
    case $1 in
        --domain)       DOMAIN="$2"; shift 2 ;;
        --email)        ADMIN_EMAIL="$2"; shift 2 ;;
        --no-ssl)       ENABLE_SSL=false; shift ;;
        --single-user)  SINGLE_USER_MODE=true; shift ;;
        --s3)           ENABLE_S3=true; shift ;;
        --help|-h)
            echo "Mastodon Production Installer"
            echo ""
            echo "Usage: install.sh [options]"
            echo ""
            echo "Options:"
            echo "  --domain <domain>  Your domain (e.g., mastodon.example.com)"
            echo "  --email <email>    Admin email for Let's Encrypt"
            echo "  --no-ssl           Skip SSL (testing only)"
            echo "  --single-user      Single user mode"
            echo "  --s3               Configure S3 storage"
            exit 0
            ;;
        *) shift ;;
    esac
done

# Check root
[ "$(id -u)" -ne 0 ] && error "Run as root: sudo bash install.sh"

# Detect OS
detect_os() {
    if [ -f /etc/os-release ]; then
        . /etc/os-release
        OS=$ID
        OS_VERSION=${VERSION_ID:-}
    else
        error "Cannot detect OS"
    fi
    log "Detected: $OS $OS_VERSION"
}

# Wait for package manager locks
wait_for_lock() {
    case $OS in
        ubuntu|debian|linuxmint|pop)
            while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do
                sleep 2
            done
            ;;
    esac
}

# Install Docker
install_docker() {
    if command -v docker >/dev/null 2>&1; then
        success "Docker already installed"
        systemctl enable --now docker 2>/dev/null || true
        return
    fi

    log "Installing Docker..."

    case $OS in
        ubuntu|debian|linuxmint|pop)
            export DEBIAN_FRONTEND=noninteractive
            wait_for_lock
            apt-get update -qq
            apt-get install -y -qq ca-certificates curl gnupg

            install -m 0755 -d /etc/apt/keyrings
            DOCKER_OS=$OS
            case "$OS" in linuxmint|pop) DOCKER_OS="ubuntu" ;; esac

            curl -fsSL https://download.docker.com/linux/$DOCKER_OS/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg 2>/dev/null
            chmod a+r /etc/apt/keyrings/docker.gpg

            CODENAME=${VERSION_CODENAME:-jammy}
            case "$OS" in linuxmint|pop) CODENAME="jammy" ;; esac
            [ "$OS" = "debian" ] && case "$CODENAME" in trixie|sid) CODENAME="bookworm" ;; esac

            echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$DOCKER_OS $CODENAME stable" > /etc/apt/sources.list.d/docker.list

            wait_for_lock
            apt-get update -qq
            apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
            ;;

        fedora)
            dnf install -y -q dnf-plugins-core
            dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
            dnf install -y -q docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
            ;;

        rocky|almalinux|rhel|centos)
            dnf install -y -q dnf-plugins-core || yum install -y yum-utils
            dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 2>/dev/null || \
                yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
            dnf install -y -q docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin || \
                yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
            ;;

        arch|manjaro|endeavouros)
            pacman -Sy --noconfirm docker docker-compose
            ;;

        opensuse*|sles)
            zypper install -y docker docker-compose
            ;;

        *)
            error "Unsupported OS: $OS"
            ;;
    esac

    systemctl enable --now docker
    success "Docker installed"
}

# Generate secrets
generate_secrets() {
    SECRET_KEY_BASE=$(openssl rand -hex 64)
    OTP_SECRET=$(openssl rand -hex 64)

    # Generate VAPID keys
    VAPID_KEYS=$(docker run --rm tootsuite/mastodon:latest bundle exec rake mastodon:webpush:generate_vapid_key 2>/dev/null || echo "")
    if [ -n "$VAPID_KEYS" ]; then
        VAPID_PRIVATE_KEY=$(echo "$VAPID_KEYS" | grep VAPID_PRIVATE_KEY | cut -d= -f2)
        VAPID_PUBLIC_KEY=$(echo "$VAPID_KEYS" | grep VAPID_PUBLIC_KEY | cut -d= -f2)
    else
        VAPID_PRIVATE_KEY=$(openssl rand -hex 32)
        VAPID_PUBLIC_KEY=$(openssl rand -hex 32)
    fi

    POSTGRES_PASSWORD="REDACTED_PASSWORD" rand -hex 32)
    REDIS_PASSWORD="REDACTED_PASSWORD" rand -hex 32)
}

# Get domain interactively
get_domain() {
    if [ -z "$DOMAIN" ]; then
        echo ""
        echo "========================================"
        echo "  Domain Configuration"
        echo "========================================"
        echo ""
        echo "Enter your domain for Mastodon (e.g., mastodon.example.com)"
        echo "A domain is REQUIRED for Mastodon to work properly."
        echo ""
        read -p "Domain: " DOMAIN
        if [ -z "$DOMAIN" ]; then
            error "Domain is required for Mastodon"
        fi
    fi

    if [ -z "$ADMIN_EMAIL" ]; then
        read -p "Admin email: " ADMIN_EMAIL
        if [ -z "$ADMIN_EMAIL" ]; then
            warn "No email provided - SSL may not work"
            ADMIN_EMAIL="admin@$DOMAIN"
        fi
    fi
}

# Create directories
create_directories() {
    log "Creating directories..."
    mkdir -p "$INSTALL_DIR"
    mkdir -p "$DATA_DIR"/{postgres,redis,mastodon/{public/system,live}}
    mkdir -p "$DATA_DIR"/caddy/{data,config}
    chmod -R 755 "$DATA_DIR"
    success "Directories created"
}

# Create .env file
create_env() {
    log "Creating environment configuration..."

    local protocol="https"
    [ "$ENABLE_SSL" != true ] && protocol="http"

    cat > "$INSTALL_DIR/.env.production" << EOF
# Federation
LOCAL_DOMAIN=$DOMAIN
SINGLE_USER_MODE=$SINGLE_USER_MODE

# Redis
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD="REDACTED_PASSWORD"

# PostgreSQL
DB_HOST=db
DB_USER=mastodon
DB_NAME=mastodon
DB_PASS="REDACTED_PASSWORD"
DB_PORT=5432

# Secrets
SECRET_KEY_BASE=$SECRET_KEY_BASE
OTP_SECRET=$OTP_SECRET
VAPID_PRIVATE_KEY=$VAPID_PRIVATE_KEY
VAPID_PUBLIC_KEY=$VAPID_PUBLIC_KEY

# Web
WEB_DOMAIN=$DOMAIN
ALTERNATE_DOMAINS=

# Email (configure for production)
SMTP_SERVER=smtp.mailgun.org
SMTP_PORT=587
SMTP_LOGIN=
SMTP_PASSWORD=
"REDACTED_PASSWORD"
SMTP_AUTH_METHOD=plain
SMTP_OPENSSL_VERIFY_MODE=none
SMTP_ENABLE_STARTTLS=auto

# File storage
# For S3 storage, uncomment and configure:
# S3_ENABLED=true
# S3_BUCKET=your-bucket
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
# S3_REGION=us-east-1
# S3_PROTOCOL=https
# S3_HOSTNAME=s3.amazonaws.com

# Elasticsearch (optional, for full-text search)
# ES_ENABLED=true
# ES_HOST=elasticsearch
# ES_PORT=9200

# Performance
RAILS_ENV=production
NODE_ENV=production
RAILS_LOG_LEVEL=warn
TRUSTED_PROXY_IP=172.16.0.0/12

# IP and session
IP_RETENTION_PERIOD=31556952
SESSION_RETENTION_PERIOD=31556952
EOF

    chmod 600 "$INSTALL_DIR/.env.production"
    success "Environment configuration created"
}

# Create docker-compose.yml
create_compose() {
    log "Creating Docker Compose file..."

    cat > "$INSTALL_DIR/docker-compose.yml" << 'EOF'
services:
  db:
    image: postgres:16-alpine
    container_name: mastodon-db
    shm_size: 256mb
    environment:
      POSTGRES_USER: mastodon
      POSTGRES_PASSWORD: "REDACTED_PASSWORD"
      POSTGRES_DB: mastodon
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "mastodon"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - internal

  redis:
    image: redis:7-alpine
    container_name: mastodon-redis
    command: redis-server --requirepass REDACTED_PASSWORD
    volumes:
      - ./data/redis:/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:"REDACTED_PASSWORD" "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - internal

  web:
    image: tootsuite/mastodon:latest
    container_name: mastodon-web
    env_file: .env.production
    command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
    volumes:
      - ./data/mastodon/public/system:/mastodon/public/system
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "wget -q --spider --proxy=off localhost:3000/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
    networks:
      - internal
      - external

  streaming:
    image: tootsuite/mastodon:latest
    container_name: mastodon-streaming
    env_file: .env.production
    command: node ./streaming
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
    networks:
      - internal
      - external

  sidekiq:
    image: tootsuite/mastodon:latest
    container_name: mastodon-sidekiq
    env_file: .env.production
    command: bundle exec sidekiq
    volumes:
      - ./data/mastodon/public/system:/mastodon/public/system
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "ps aux | grep '[s]idekiq 6' || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
    networks:
      - internal
      - external

  caddy:
    image: caddy:2-alpine
    container_name: mastodon-caddy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./data/caddy/data:/data
      - ./data/caddy/config:/config
      - ./data/mastodon/public:/mastodon/public:ro
    depends_on:
      - web
      - streaming
    restart: unless-stopped
    networks:
      - external

  watchtower:
    image: containrrr/watchtower:latest
    container_name: mastodon-watchtower
    environment:
      WATCHTOWER_CLEANUP: "true"
      WATCHTOWER_SCHEDULE: "0 0 4 * * *"
      WATCHTOWER_LABEL_ENABLE: "false"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    restart: unless-stopped

networks:
  internal:
    internal: true
  external:
EOF

    # Extract DB_PASS for compose
    echo "DB_PASS="REDACTED_PASSWORD" > "$INSTALL_DIR/.env"
    echo "REDIS_PASSWORD="REDACTED_PASSWORD" >> "$INSTALL_DIR/.env"

    success "Docker Compose file created"
}

# Create Caddyfile
create_caddyfile() {
    log "Creating Caddy configuration..."

    if [ "$ENABLE_SSL" = true ]; then
        cat > "$INSTALL_DIR/Caddyfile" << EOF
$DOMAIN {
    encode gzip

    handle_path /system/* {
        file_server {
            root /mastodon/public
        }
    }

    handle /api/v1/streaming/* {
        reverse_proxy streaming:4000
    }

    handle /* {
        reverse_proxy web:3000
    }

    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Frame-Options "SAMEORIGIN"
        X-Content-Type-Options "nosniff"
        X-XSS-Protection "1; mode=block"
        Referrer-Policy "strict-origin-when-cross-origin"
    }

    log {
        output stdout
    }
}
EOF
    else
        cat > "$INSTALL_DIR/Caddyfile" << EOF
:80 {
    encode gzip

    handle_path /system/* {
        file_server {
            root /mastodon/public
        }
    }

    handle /api/v1/streaming/* {
        reverse_proxy streaming:4000
    }

    handle /* {
        reverse_proxy web:3000
    }
}
EOF
    fi

    success "Caddy configuration created"
}

# Initialize database
init_database() {
    log "Initializing database..."
    cd "$INSTALL_DIR"

    # Start database first
    docker compose up -d db redis
    sleep 10

    # Run migrations
    docker compose run --rm web bundle exec rails db:setup SAFETY_ASSURED=1 2>/dev/null || \
    docker compose run --rm web bundle exec rails db:migrate SAFETY_ASSURED=1

    # Precompile assets
    docker compose run --rm web bundle exec rails assets:precompile

    success "Database initialized"
}

# Create management script
create_management_script() {
    log "Creating management script..."

    cat > /usr/local/bin/mastodon << 'EOF'
#!/bin/bash
cd /opt/mastodon || exit 1

case "${1:-help}" in
    start)   docker compose up -d ;;
    stop)    docker compose down ;;
    restart) docker compose restart ${2:-} ;;
    status)  docker compose ps ;;
    logs)    docker compose logs -f ${2:-} ;;
    update)
        docker compose pull
        docker compose up -d
        docker compose run --rm web bundle exec rails db:migrate
        docker compose run --rm web bundle exec rails assets:precompile
        docker compose restart
        ;;
    edit)    ${EDITOR:-nano} /opt/mastodon/.env.production ;;

    admin)
        if [ -z "$2" ]; then
            echo "Usage: mastodon admin <username>"
            exit 1
        fi
        docker compose run --rm web bin/tootctl accounts create "$2" --email "${3:-admin@localhost}" --confirmed --role Owner
        ;;

    reset-password)
        if [ -z "$2" ]; then
            echo "Usage: mastodon reset-password <username>"
            exit 1
        fi
        docker compose run --rm web bin/tootctl accounts modify "$2" --reset-password
        ;;

    tootctl)
        shift
        docker compose run --rm web bin/tootctl "$@"
        ;;

    console)
        docker compose run --rm web bin/rails console
        ;;

    shell)
        docker compose run --rm web /bin/bash
        ;;

    backup)
        timestamp=$(date +"%Y%m%d_%H%M%S")
        backup_dir="/opt/mastodon-data/backups"
        mkdir -p "$backup_dir"

        echo "Backing up database..."
        docker compose exec -T db pg_dump -U mastodon mastodon > "$backup_dir/mastodon_db_$timestamp.sql"

        echo "Backing up media..."
        tar -czf "$backup_dir/mastodon_media_$timestamp.tar.gz" -C /opt/mastodon-data mastodon/public/system

        echo "Backup complete: $backup_dir"
        ls -la "$backup_dir"/*$timestamp*
        ;;

    cleanup)
        echo "Cleaning up old media..."
        docker compose run --rm web bin/tootctl media remove --days=7
        docker compose run --rm web bin/tootctl preview_cards remove --days=30
        docker compose run --rm web bin/tootctl statuses remove --days=90
        ;;

    *)
        echo "Mastodon Management"
        echo ""
        echo "Usage: mastodon <command>"
        echo ""
        echo "Commands:"
        echo "  start              Start all services"
        echo "  stop               Stop all services"
        echo "  restart [service]  Restart services"
        echo "  status             Show status"
        echo "  logs [service]     View logs"
        echo "  update             Update and migrate"
        echo "  edit               Edit configuration"
        echo "  admin <user>       Create admin user"
        echo "  reset-password <u> Reset user password"
        echo "  tootctl <args>     Run tootctl command"
        echo "  console            Rails console"
        echo "  shell              Bash shell"
        echo "  backup             Backup database and media"
        echo "  cleanup            Clean old media/statuses"
        ;;
esac
EOF

    chmod +x /usr/local/bin/mastodon
    success "Management script created"
}

# Configure firewall
configure_firewall() {
    log "Configuring firewall..."

    if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld 2>/dev/null; then
        firewall-cmd --permanent --add-service=http 2>/dev/null || true
        firewall-cmd --permanent --add-service=https 2>/dev/null || true
        firewall-cmd --reload 2>/dev/null || true
        success "Firewall configured (firewalld)"
    elif command -v ufw >/dev/null 2>&1 && ufw status | grep -q "active"; then
        ufw allow 80/tcp 2>/dev/null || true
        ufw allow 443/tcp 2>/dev/null || true
        success "Firewall configured (ufw)"
    else
        warn "No active firewall detected"
    fi
}

# Deploy
deploy() {
    log "Deploying Mastodon..."
    cd "$INSTALL_DIR"

    # Copy data directory reference
    ln -sf "$DATA_DIR" "$INSTALL_DIR/data" 2>/dev/null || true
    mkdir -p "$INSTALL_DIR/data"
    ln -sf "$DATA_DIR/postgres" "$INSTALL_DIR/data/postgres"
    ln -sf "$DATA_DIR/redis" "$INSTALL_DIR/data/redis"
    ln -sf "$DATA_DIR/mastodon" "$INSTALL_DIR/data/mastodon"
    ln -sf "$DATA_DIR/caddy" "$INSTALL_DIR/data/caddy"

    docker compose pull

    # Initialize database
    init_database

    # Start all services
    docker compose up -d

    # Wait for services
    log "Waiting for services to start..."
    sleep 15

    success "Mastodon deployed!"
}

# Show completion message
show_complete() {
    local protocol="https"
    [ "$ENABLE_SSL" != true ] && protocol="http"

    echo ""
    echo "========================================"
    echo "  Mastodon Installation Complete!"
    echo "========================================"
    echo ""
    echo "Access:"
    echo "  Web Interface: ${protocol}://${DOMAIN}"
    echo ""
    echo "Create your admin account:"
    echo "  mastodon admin yourusername your@email.com"
    echo ""
    echo "Then reset password to get initial password:"
    echo "  mastodon reset-password yourusername"
    echo ""
    echo "Commands:"
    echo "  mastodon status    - Show service status"
    echo "  mastodon logs      - View logs"
    echo "  mastodon update    - Update Mastodon"
    echo "  mastodon backup    - Backup database"
    echo "  mastodon cleanup   - Clean old media"
    echo "  mastodon tootctl   - Run tootctl commands"
    echo ""
    echo "Config: $INSTALL_DIR/.env.production"
    echo "Data:   $DATA_DIR"
    echo ""
    echo "⚠️  Configure email in .env.production for:"
    echo "   - Email notifications"
    echo "   - Password resets"
    echo "   - Account confirmations"
    echo ""
}

# Main
main() {
    echo ""
    echo "========================================"
    echo "  Mastodon Production Installer"
    echo "========================================"
    echo ""

    detect_os
    get_domain
    generate_secrets
    install_docker
    create_directories
    create_env
    create_compose
    create_caddyfile
    create_management_script
    configure_firewall
    deploy
    show_complete
}

main "$@"
