package f5

import (
	"context"
	"errors"
	"log/slog"
	"os"
	"testing"

	"github.com/certctl-io/certctl/internal/connector/target"
)

// Phase 8 of the deploy-hardening I master bundle: F5 ValidateOnly
// real implementation tests. F5 already has full transactional
// rollback via the iControl REST `mgmt/tm/transaction` endpoint;
// the new bit is the explicit dry-run probe via Authenticate.

type stubF5Authenticator struct {
	authErr error
}

func (s *stubF5Authenticator) Authenticate(_ context.Context) error {
	return s.authErr
}

// implement the rest of the F5Client interface as no-ops so the
// stub satisfies the interface.
func (s *stubF5Authenticator) UploadFile(context.Context, string, []byte) error {
	return nil
}
func (s *stubF5Authenticator) InstallCert(context.Context, string, string) error { return nil }
func (s *stubF5Authenticator) InstallKey(context.Context, string, string) error  { return nil }
func (s *stubF5Authenticator) CreateTransaction(context.Context) (string, error) {
	return "", nil
}
func (s *stubF5Authenticator) CommitTransaction(context.Context, string) error {
	return nil
}
func (s *stubF5Authenticator) UpdateSSLProfile(context.Context, string, string, string, string, string, string) error {
	return nil
}
func (s *stubF5Authenticator) GetSSLProfile(context.Context, string, string) (*SSLProfileInfo, error) {
	return nil, nil
}
func (s *stubF5Authenticator) DeleteCert(context.Context, string, string) error { return nil }
func (s *stubF5Authenticator) DeleteKey(context.Context, string, string) error  { return nil }

func quietLogger() *slog.Logger {
	return slog.New(slog.NewTextHandler(os.NewFile(0, os.DevNull), &slog.HandlerOptions{Level: slog.LevelError}))
}

func TestF5_ValidateOnly_Auth_Succeeds_ReturnsNil(t *testing.T) {
	c := NewWithClient(&Config{Host: "f5.example", Username: "admin"}, quietLogger(), &stubF5Authenticator{authErr: nil})
	if err := c.ValidateOnly(context.Background(), target.DeploymentRequest{}); err != nil {
		t.Errorf("got %v, want nil", err)
	}
}

func TestF5_ValidateOnly_AuthFails_ReturnsWrappedError(t *testing.T) {
	c := NewWithClient(&Config{Host: "f5.example", Username: "admin"}, quietLogger(), &stubF5Authenticator{authErr: errors.New("invalid credentials")})
	err := c.ValidateOnly(context.Background(), target.DeploymentRequest{})
	if err == nil {
		t.Fatal("expected error")
	}
	if errors.Is(err, target.ErrValidateOnlyNotSupported) {
		t.Errorf("got sentinel, want wrapped auth error: %v", err)
	}
}

func TestF5_ValidateOnly_NilClient_ReturnsSentinel(t *testing.T) {
	c := &Connector{config: &Config{Host: "f5.example"}, logger: quietLogger()}
	// Don't inject a client.
	if err := c.ValidateOnly(context.Background(), target.DeploymentRequest{}); !errors.Is(err, target.ErrValidateOnlyNotSupported) {
		t.Errorf("got %v, want sentinel", err)
	}
}

func TestF5_ValidateOnly_AuthFailureMessageMentionsBIGIP(t *testing.T) {
	c := NewWithClient(&Config{Host: "f5.example"}, quietLogger(), &stubF5Authenticator{authErr: errors.New("conn refused")})
	err := c.ValidateOnly(context.Background(), target.DeploymentRequest{})
	if err == nil {
		t.Fatal("expected error")
	}
	if !contains(err.Error(), "BIG-IP") {
		t.Errorf("error missing BIG-IP context: %v", err)
	}
}

func TestF5_ValidateOnly_RecoverableAuthErrIsActionable(t *testing.T) {
	// Auth-fail variant that simulates a one-time TACACS+ outage —
	// the operator is meant to see this as actionable.
	c := NewWithClient(&Config{Host: "f5.example"}, quietLogger(), &stubF5Authenticator{authErr: errors.New("TACACS+ auth provider unreachable")})
	err := c.ValidateOnly(context.Background(), target.DeploymentRequest{})
	if err == nil {
		t.Fatal("expected error")
	}
	if !contains(err.Error(), "TACACS+") {
		t.Errorf("error doesn't surface auth provider info: %v", err)
	}
}

func contains(haystack, needle string) bool {
	return len(haystack) > 0 && len(needle) > 0 &&
		(len(haystack) >= len(needle)) &&
		(indexOf(haystack, needle) >= 0)
}

func indexOf(s, substr string) int {
	for i := 0; i+len(substr) <= len(s); i++ {
		if s[i:i+len(substr)] == substr {
			return i
		}
	}
	return -1
}
