package trackers

import (
	"context"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/rs/zerolog"

	"github.com/operator/command-center/internal/scrape"
)

// Synthetic HTML approximating TorrentDay's post-login landing page header.
// The exact markup TD ships changes across UI revisions / forks; the
// adapter is regex-driven against label-anchored patterns so it can
// tolerate variation. These fixtures cover the main shapes seen in the
// wild and stress the parser's fallback paths.

const tdLoggedInHTML = `
<html><head><title>TorrentDay - Browse</title></head>
<body>
<div id="userinfo">
  <a href="/userdetails.php?id=12345">testuser</a>
  | Up: <a>4.56 TB</a>
  | Down: <a>1.23 TB</a>
  | Ratio: <a>3.70</a>
  | Bonus: <a>123,456</a>
  | User Class: <a href="/faq.php#class">Power User</a>
</div>
<div id="content">browse table here</div>
</body></html>
`

const tdLoginHTML = `
<html><body>
<form action="/takelogin.php" method="post">
  <input type="text" name="username">
  <input type="password" name="password">
  <input type="submit" value="Login">
</form>
</body></html>
`

const tdInfiniteRatioHTML = `
<html><body>
<div id="userinfo">
  Up: 12.0 GB | Down: 0 B | Ratio: ∞ | Bonus: 0
</div>
</body></html>
`

const tdNoBonusHTML = `
<html><body>
<div id="userinfo">
  Up: 100.0 GB | Down: 50.0 GB | Ratio: 2.000
</div>
</body></html>
`

func newTestTDAdapter(t *testing.T, baseURL string) scrape.Adapter {
	t.Helper()
	// nil secrets store: hygiene attaches no cookie, which matches how
	// these tests probe parsing rather than auth wiring.
	h := scrape.New(nil, scrape.Options{}, zerolog.Nop())
	a, err := newTD(Config{
		ID:      "td-test",
		Name:    "TorrentDay (test)",
		Type:    "td",
		BaseURL: baseURL,
	}, h)
	if err != nil {
		t.Fatalf("newTD: %v", err)
	}
	return a
}

func TestTDRegistered(t *testing.T) {
	types := RegisteredTypes()
	for _, n := range types {
		if n == "td" {
			return
		}
	}
	t.Errorf("td adapter not registered; got %v", types)
}

func TestTDFetchRatioHappyPath(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		_, _ = w.Write([]byte(tdLoggedInHTML))
	}))
	t.Cleanup(srv.Close)

	snap, err := newTestTDAdapter(t, srv.URL).FetchRatio(context.Background())
	if err != nil {
		t.Fatalf("FetchRatio: %v", err)
	}
	if snap.DisplayedRatio == nil || *snap.DisplayedRatio != 3.70 {
		t.Errorf("ratio = %+v, want 3.70", snap.DisplayedRatio)
	}
	// Use parseByteSize directly so the expected value is computed
	// the same way the adapter does — sidesteps a Go-constant overflow
	// check on the typed conversion.
	wantUp, _ := parseByteSize("4.56", "TB")
	if snap.DisplayedUploadedBytes == nil || *snap.DisplayedUploadedBytes != wantUp {
		t.Errorf("uploaded = %v, want %d", snap.DisplayedUploadedBytes, wantUp)
	}
	wantDown, _ := parseByteSize("1.23", "TB")
	if snap.DisplayedDownloadedBytes == nil || *snap.DisplayedDownloadedBytes != wantDown {
		t.Errorf("downloaded = %v, want %d", snap.DisplayedDownloadedBytes, wantDown)
	}
	if snap.BonusPoints == nil || *snap.BonusPoints != 123456 {
		t.Errorf("bonus = %v, want 123456", snap.BonusPoints)
	}
	if snap.ClassOrRank != "Power User" {
		t.Errorf("class = %q, want %q", snap.ClassOrRank, "Power User")
	}
}

func TestTDFetchRatioLoginPageSurfaces(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		_, _ = w.Write([]byte(tdLoginHTML))
	}))
	t.Cleanup(srv.Close)

	_, err := newTestTDAdapter(t, srv.URL).FetchRatio(context.Background())
	if err == nil {
		t.Fatal("expected auth error on login page")
	}
	if !strings.Contains(err.Error(), "td: auth") {
		t.Errorf("error = %q, want td: auth …", err.Error())
	}
}

func TestTDFetchRatioInfiniteRatio(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		_, _ = w.Write([]byte(tdInfiniteRatioHTML))
	}))
	t.Cleanup(srv.Close)

	snap, err := newTestTDAdapter(t, srv.URL).FetchRatio(context.Background())
	if err != nil {
		t.Fatalf("FetchRatio: %v", err)
	}
	if snap.DisplayedRatio == nil || *snap.DisplayedRatio < 1e6 {
		t.Errorf("infinite ratio not surfaced as a large finite float: %v", snap.DisplayedRatio)
	}
}

func TestTDFetchRatioNoBonus(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		_, _ = w.Write([]byte(tdNoBonusHTML))
	}))
	t.Cleanup(srv.Close)

	snap, err := newTestTDAdapter(t, srv.URL).FetchRatio(context.Background())
	if err != nil {
		t.Fatalf("FetchRatio: %v", err)
	}
	if snap.BonusPoints != nil {
		t.Errorf("bonus should be nil when not present, got %v", *snap.BonusPoints)
	}
	if snap.DisplayedRatio == nil || *snap.DisplayedRatio != 2.0 {
		t.Errorf("ratio = %v, want 2.0", snap.DisplayedRatio)
	}
}

func TestTDHealthAuthFailure(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		http.Error(w, "nope", http.StatusUnauthorized)
	}))
	t.Cleanup(srv.Close)

	a := newTestTDAdapter(t, srv.URL)
	err := a.Health(context.Background())
	if err == nil {
		t.Fatal("expected auth error from Health on 401")
	}
	if !strings.Contains(err.Error(), "auth") {
		t.Errorf("error = %q, want auth-related", err)
	}
}
