package extsvc

import (
	"context"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

// The verb, the key and the record must arrive in the shape HAMLOG's own agent
// sends — this is read from their client, not from documentation, so the test
// pins it rather than trusting a memory of it.
func TestUploadHamlogRequestShape(t *testing.T) {
	var got map[string]map[string]any
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			t.Errorf("method %s, want POST", r.Method)
		}
		if ct := r.Header.Get("Content-Type"); !strings.Contains(ct, "application/json") {
			t.Errorf("Content-Type %q", ct)
		}
		b, _ := io.ReadAll(r.Body)
		_ = json.Unmarshal(b, &got)
		_, _ = w.Write([]byte(`{"STATUS":"OK"}`))
	}))
	defer srv.Close()

	res, err := uploadHamlogLive(context.Background(), nil, srv.URL, ServiceConfig{APIKey: "KEY123"}, "<call:5>F4BPO <eor>")
	if err != nil {
		t.Fatal(err)
	}
	if !res.OK {
		t.Fatalf("upload not OK: %+v", res)
	}
	add, ok := got["ADIFADD"]
	if !ok {
		t.Fatalf("no ADIFADD verb in %v", got)
	}
	if add["APIKEY"] != "KEY123" {
		t.Errorf("APIKEY = %v", add["APIKEY"])
	}
	if add["ADIFDATA"] != "<call:5>F4BPO <eor>" {
		t.Errorf("ADIFDATA = %v", add["ADIFDATA"])
	}
}

// A refusal must be reported as a refusal. Their failure shape carries ERROR
// and no STATUS, so "no error field" would have read an unknown reply as an
// accepted QSO — which is how a contact goes missing without anyone noticing.
func TestHamlogFailureIsNotSuccess(t *testing.T) {
	for _, tc := range []struct {
		body     string
		wantOK   bool
		wantSaid string
	}{
		{`{"STATUS":"OK"}`, true, ""},
		{`{"ERROR":"Invalid API key"}`, false, "Invalid API key"},
		{`{"STATUS":"FAILED"}`, false, "rejected"},
		{`{}`, false, "rejected"}, // an empty object is not an acceptance
	} {
		srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			_, _ = w.Write([]byte(tc.body))
		}))
		res, err := uploadHamlogLive(context.Background(), nil, srv.URL, ServiceConfig{APIKey: "K"}, "<eor>")
		srv.Close()
		if err != nil {
			t.Fatalf("%s: %v", tc.body, err)
		}
		if res.OK != tc.wantOK {
			t.Errorf("%s → OK=%v, want %v", tc.body, res.OK, tc.wantOK)
		}
		if !tc.wantOK && res.Message != tc.wantSaid {
			t.Errorf("%s → message %q, want %q", tc.body, res.Message, tc.wantSaid)
		}
	}
}

// Nothing leaves without a key, and the message says where to get one.
func TestUploadHamlogNeedsAKey(t *testing.T) {
	_, err := uploadHamlogLive(context.Background(), nil, "http://example.invalid", ServiceConfig{}, "<eor>")
	if err == nil || !strings.Contains(err.Error(), hamlogKeyPage) {
		t.Fatalf("err = %v, want it to point at %s", err, hamlogKeyPage)
	}
}

// The door is shut: HAMLOG.online stopped issuing API keys, so an upload that
// could only fail is refused before it is attempted. The request-shape tests
// above still cover the code kept against the day keys come back.
func TestUploadHamlogIsClosed(t *testing.T) {
	res, err := UploadHamlog(context.Background(), nil, ServiceConfig{APIKey: "KEY123"}, "<eor>")
	if !errors.Is(err, ErrHamlogClosed) {
		t.Fatalf("err = %v, want ErrHamlogClosed", err)
	}
	if res.OK {
		t.Error("a refused upload reported success")
	}
}
