package api

import (
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

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

func newTestDropperHandler(t *testing.T) (*DropperHandler, *db.Database, string) {
	t.Helper()
	dataDir := t.TempDir()
	database, err := db.New(dataDir)
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = database.Close() })
	return NewDropperHandler(database, func() string { return "https://public.example.com" }), database, dataDir
}

func TestDetectPlatformQueryParam(t *testing.T) {
	cases := map[string]string{
		"windows": "windows", "win": "windows",
		"linux": "linux",
		"darwin": "darwin", "mac": "darwin", "macos": "darwin",
		"universal": "universal", "any": "universal",
		"unknown": "",
	}
	for in, want := range cases {
		req := httptest.NewRequest(http.MethodGet, "/get?os="+in, nil)
		if got := detectPlatform(req); got != want {
			t.Fatalf("detectPlatform(%q) = %q, want %q", in, got, want)
		}
	}
}

func TestDetectPlatformUserAgent(t *testing.T) {
	tests := []struct {
		ua   string
		want string
	}{
		{"Mozilla/5.0 (Windows NT 10.0)", "windows"},
		{"Mozilla/5.0 (Macintosh; Intel Mac OS X)", "darwin"},
		{"Mozilla/5.0 (X11; Linux x86_64)", "linux"},
		{"curl/8.0", ""},
	}
	for _, tc := range tests {
		req := httptest.NewRequest(http.MethodGet, "/get", nil)
		req.Header.Set("User-Agent", tc.ua)
		if got := detectPlatform(req); got != tc.want {
			t.Fatalf("UA %q => %q, want %q", tc.ua, got, tc.want)
		}
	}
}

func TestDropperServeGetNoBuilds(t *testing.T) {
	h, _, _ := newTestDropperHandler(t)
	req := httptest.NewRequest(http.MethodGet, "/get", nil)
	rec := httptest.NewRecorder()
	h.ServeGet(rec, req)
	if rec.Code != http.StatusNotFound {
		t.Fatalf("expected 404, got %d", rec.Code)
	}
	if !strings.Contains(rec.Body.String(), "no agent build available") {
		t.Fatalf("unexpected body: %s", rec.Body.String())
	}
}

func TestDropperServeGetWindowsBuild(t *testing.T) {
	h, database, dataDir := newTestDropperHandler(t)
	buildID := "win-build"
	buildDir := filepath.Join(dataDir, "builds", buildID)
	if err := os.MkdirAll(buildDir, 0755); err != nil {
		t.Fatal(err)
	}
	binPath := filepath.Join(buildDir, "worker.exe")
	content := []byte("windows-agent-binary")
	if err := os.WriteFile(binPath, content, 0644); err != nil {
		t.Fatal(err)
	}
	if err := database.InsertBuild(&models.BuildRecord{
		ID: buildID, WorkerName: "w", ServerURL: "http://x", Wallet: "48x",
		FilePath: binPath, FileName: "worker.exe", Platform: "windows",
		CreatedAt: time.Now(),
	}); err != nil {
		t.Fatal(err)
	}

	req := httptest.NewRequest(http.MethodGet, "/get?os=windows", nil)
	rec := httptest.NewRecorder()
	h.ServeGet(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
	}
	if !strings.Contains(rec.Header().Get("Content-Disposition"), "worker.exe") {
		t.Fatalf("missing disposition: %q", rec.Header().Get("Content-Disposition"))
	}
}

func TestDropperResolveBasePublicURL(t *testing.T) {
	h, _, _ := newTestDropperHandler(t)
	req := httptest.NewRequest(http.MethodGet, "/install.sh", nil)
	req.Host = "ignored.local:8989"
	if base := h.resolveBase(req); base != "https://public.example.com" {
		t.Fatalf("publicURL override = %q", base)
	}
}

func TestDropperResolveBaseFromRequest(t *testing.T) {
	h := NewDropperHandler(nil, nil)
	req := httptest.NewRequest(http.MethodGet, "/install.sh", nil)
	req.Host = "deck.local:8989"
	req.Header.Set("X-Forwarded-Host", "proxy.example.com")
	if base := h.resolveBase(req); base != "http://proxy.example.com" {
		t.Fatalf("resolveBase = %q", base)
	}
}

func TestDropperServeShContent(t *testing.T) {
	h, _, _ := newTestDropperHandler(t)
	req := httptest.NewRequest(http.MethodGet, "/install.sh", nil)
	rec := httptest.NewRecorder()
	h.ServeSh(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("status %d", rec.Code)
	}
	body := rec.Body.String()
	if !strings.Contains(body, "#!/bin/sh") || !strings.Contains(body, "https://public.example.com/get") {
		t.Fatalf("unexpected install.sh body prefix: %.120s", body)
	}
}

func TestDropperServePs1Content(t *testing.T) {
	h, _, _ := newTestDropperHandler(t)
	req := httptest.NewRequest(http.MethodGet, "/install.ps1", nil)
	rec := httptest.NewRecorder()
	h.ServePs1(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("status %d", rec.Code)
	}
	if !strings.Contains(rec.Body.String(), "DownloadFile") {
		t.Fatal("expected PowerShell download snippet")
	}
}
