package hetznerapi

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

// compile-time: both the real client and the fake satisfy CloudAPI.
var _ CloudAPI = (*Client)(nil)
var _ CloudAPI = (*Fake)(nil)

func newTestClient(t *testing.T, h http.Handler) (*Client, *httptest.Server) {
	t.Helper()
	srv := httptest.NewServer(h)
	t.Cleanup(srv.Close)
	c := NewClient(func() string { return "TESTTOKEN" })
	c.BaseURL = srv.URL
	c.PollEvery = 5 * time.Millisecond
	c.PollMax = 2 * time.Second
	return c, srv
}

// The create request carries the bearer + JSON, and the {action,subaccount} envelope decodes.
func TestClient_CreateSubaccount(t *testing.T) {
	var gotAuth, gotBody string
	c, _ := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotAuth = r.Header.Get("Authorization")
		buf := make([]byte, r.ContentLength)
		r.Body.Read(buf)
		gotBody = string(buf)
		w.WriteHeader(201)
		w.Write([]byte(`{"action":{"id":42,"command":"create_subaccount","status":"success"},"subaccount":{"id":268917,"storage_box":611421}}`))
	}))
	id, act, err := c.CreateSubaccount(context.Background(), 611421, CreateSubaccountRequest{HomeDirectory: "spike-sub", Password: "Xx1%", AccessSettings: AccessSettings{SSHEnabled: true}})
	if err != nil {
		t.Fatal(err)
	}
	if gotAuth != "Bearer TESTTOKEN" {
		t.Errorf("bearer not set, got %q", gotAuth)
	}
	if !strings.Contains(gotBody, `"home_directory":"spike-sub"`) {
		t.Errorf("request body missing home_directory: %s", gotBody)
	}
	if id != 268917 || act.ID != 42 || act.Status != "success" {
		t.Fatalf("got id=%d action=%+v", id, act)
	}
}

// A non-2xx surfaces the API's error code/message (the 422 password shape).
func TestClient_ErrorEnvelope(t *testing.T) {
	c, _ := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(422)
		w.Write([]byte(`{"error":{"code":"invalid_input","message":"invalid input in field password"}}`))
	}))
	_, _, err := c.CreateSubaccount(context.Background(), 1, CreateSubaccountRequest{})
	if err == nil || !strings.Contains(err.Error(), "invalid_input") {
		t.Fatalf("want invalid_input error, got %v", err)
	}
}

// WaitAction: an already-success action returns immediately; running→success polls; error fails.
func TestClient_WaitAction(t *testing.T) {
	if err := (&Client{}).WaitAction(context.Background(), Action{Status: "success"}); err != nil {
		t.Fatalf("terminal success must return nil, got %v", err)
	}
	if err := (&Client{}).WaitAction(context.Background(), Action{ID: 9, Command: "x", Status: "error"}); err == nil {
		t.Fatal("terminal error must return an error")
	}

	calls := 0
	c, _ := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		calls++
		st := "running"
		if calls >= 3 {
			st = "success"
		}
		w.Write([]byte(`{"action":{"id":7,"command":"create","status":"` + st + `"}}`))
	}))
	if err := c.WaitAction(context.Background(), Action{ID: 7, Command: "create", Status: "running"}); err != nil {
		t.Fatalf("running→success must resolve, got %v", err)
	}
	if calls < 3 {
		t.Fatalf("expected polling, only %d calls", calls)
	}
}

func TestClient_WaitActionTimeout(t *testing.T) {
	c, _ := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`{"action":{"id":7,"status":"running"}}`))
	}))
	c.PollMax = 30 * time.Millisecond
	if err := c.WaitAction(context.Background(), Action{ID: 7, Status: "running"}); err == nil || !strings.Contains(err.Error(), "did not reach success") {
		t.Fatalf("want timeout error, got %v", err)
	}
}
