package license

import (
	"context"
	"errors"
	"testing"
	"time"
)

type stubRemote struct {
	detailErr    error
	heartbeatErr error
	getKeyResult []byte
	getKeyErr    error
}

type probeRemote struct {
	stubRemote
	delay       time.Duration
	probeErr    error
	name        string
	detailCalls int
}

func (s *probeRemote) Probe(ctx context.Context) error {
	timer := time.NewTimer(s.delay)
	defer timer.Stop()
	select {
	case <-ctx.Done():
		return ctx.Err()
	case <-timer.C:
		return s.probeErr
	}
}

func (s *probeRemote) Detail(_ context.Context, _ string) (Detail, error) {
	s.detailCalls++
	return Detail{Version: s.name}, nil
}

func (s *stubRemote) Detail(_ context.Context, _ string) (Detail, error) {
	return Detail{}, s.detailErr
}
func (s *stubRemote) Register(_ context.Context, _ RegisterRequest) (Registration, error) {
	return Registration{}, errors.New("stub")
}
func (s *stubRemote) QueryRaw(_ context.Context, _, _, _, _ string) (Query, []byte, string, error) {
	return Query{}, nil, "", errors.New("stub")
}
func (s *stubRemote) Heartbeat(_ context.Context, _ string, _ HeartbeatRequest) (*HeartbeatResult, error) {
	if s.heartbeatErr != nil {
		return nil, s.heartbeatErr
	}
	return nil, errors.New("stub")
}
func (s *stubRemote) GetPublicKey(_ context.Context) ([]byte, error) {
	return s.getKeyResult, s.getKeyErr
}

func TestMultiRemoteGetPublicKeyFirstSuccess(t *testing.T) {
	want := []byte("ed25519-key")
	mr := NewMultiRemote(
		&stubRemote{getKeyErr: errors.New("fail")},
		&stubRemote{getKeyResult: want},
		&stubRemote{getKeyErr: errors.New("fail")},
	)
	got, err := mr.GetPublicKey(context.Background())
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if string(got) != string(want) {
		t.Fatalf("got %q, want %q", got, want)
	}
}

func TestMultiRemoteGetPublicKeyAllFail(t *testing.T) {
	mr := NewMultiRemote(
		&stubRemote{getKeyErr: errors.New("fail1")},
		&stubRemote{getKeyErr: errors.New("fail2")},
	)
	_, err := mr.GetPublicKey(context.Background())
	if err == nil {
		t.Fatal("expected error when all remotes fail")
	}
}

func TestMultiRemoteNilRemotesSkipped(t *testing.T) {
	mr := NewMultiRemote(nil, nil)
	if mr.Len() != 0 {
		t.Fatalf("Len() = %d, want 0 after filtering nils", mr.Len())
	}
}

func TestMultiRemoteDetailFallthrough(t *testing.T) {
	errFirst := errors.New("first fails")
	mr := NewMultiRemote(
		&stubRemote{detailErr: errFirst},
		&stubRemote{detailErr: nil}, // second succeeds
	)
	_, err := mr.Detail(context.Background(), "TEST-CODE")
	if err != nil {
		t.Fatalf("expected success on second remote, got: %v", err)
	}
}

func TestMultiRemoteUsesFastestHealthyProbe(t *testing.T) {
	slow := &probeRemote{delay: 40 * time.Millisecond, name: "slow"}
	fast := &probeRemote{delay: time.Millisecond, name: "fast"}
	mr := NewMultiRemote(slow, fast)

	detail, err := mr.Detail(context.Background(), "TEST-CODE")
	if err != nil {
		t.Fatalf("Detail: %v", err)
	}
	if detail.Version != "fast" {
		t.Fatalf("Detail selected %q, want fast", detail.Version)
	}
	if fast.detailCalls != 1 || slow.detailCalls != 0 {
		t.Fatalf("unexpected detail calls: fast=%d slow=%d", fast.detailCalls, slow.detailCalls)
	}
}

func TestMultiRemoteHeartbeatReturnsQuorumRejection(t *testing.T) {
	mr := NewMultiRemote(
		&stubRemote{heartbeatErr: &HTTPError{StatusCode: 403, Code: "DOMAIN_MISMATCH"}},
		&stubRemote{heartbeatErr: &HTTPError{StatusCode: 403, Code: "DOMAIN_MISMATCH"}},
		&stubRemote{heartbeatErr: errors.New("dial tcp: connection refused")},
	)
	_, err := mr.Heartbeat(context.Background(), "inst_test", HeartbeatRequest{})
	var quorum *QuorumRejectionError
	if !errors.As(err, &quorum) {
		t.Fatalf("expected QuorumRejectionError, got %v", err)
	}
	if quorum.Rejections != 2 || quorum.Required != 2 || quorum.Total != 3 {
		t.Fatalf("unexpected quorum result: %+v", quorum)
	}
}
