package agentrouter

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

	"connectrpc.com/connect"
	"google.golang.org/genproto/googleapis/rpc/errdetails"

	catalogv1 "github.com/tetrateio/agentrouter-go/genapi/api/tars/catalog/v1"
	"github.com/tetrateio/agentrouter-go/genapi/api/tars/catalog/v1/catalogv1connect"
)

// connectErrorWithReason builds a *connect.Error carrying a google.rpc.ErrorInfo
// detail with the given reason -- the same shape the platform's apierr.New /
// reason interceptor attaches to every error.
func connectErrorWithReason(t *testing.T, code connect.Code, msg, reason string) *connect.Error {
	t.Helper()
	e := connect.NewError(code, errors.New(msg))
	detail, err := connect.NewErrorDetail(&errdetails.ErrorInfo{
		Reason: reason,
		Domain: "tetrate.ai",
	})
	if err != nil {
		t.Fatalf("build ErrorInfo detail: %v", err)
	}
	e.AddDetail(detail)
	return e
}

// TestWrapReason asserts that wrap surfaces the ErrorInfo reason while leaving
// the code and message untouched, and yields an empty reason when the error
// carries no ErrorInfo detail.
func TestWrapReason(t *testing.T) {
	t.Run("with ErrorInfo detail", func(t *testing.T) {
		ce := connectErrorWithReason(t, connect.CodeNotFound, "model foo not found", "MODEL_NOT_FOUND")

		wrapped := wrap(ce)
		var fe *Error
		if !errors.As(wrapped, &fe) {
			t.Fatalf("wrap did not return *Error, got %T", wrapped)
		}
		if fe.Reason != "MODEL_NOT_FOUND" {
			t.Errorf("Reason = %q, want MODEL_NOT_FOUND", fe.Reason)
		}
		if fe.Code != connect.CodeNotFound {
			t.Errorf("Code = %v, want %v", fe.Code, connect.CodeNotFound)
		}
		if fe.Message != "model foo not found" {
			t.Errorf("Message = %q, want %q", fe.Message, "model foo not found")
		}
	})

	t.Run("without ErrorInfo detail", func(t *testing.T) {
		ce := connect.NewError(connect.CodePermissionDenied, errors.New("denied"))

		wrapped := wrap(ce)
		var fe *Error
		if !errors.As(wrapped, &fe) {
			t.Fatalf("wrap did not return *Error, got %T", wrapped)
		}
		if fe.Reason != "" {
			t.Errorf("Reason = %q, want empty", fe.Reason)
		}
		if fe.Code != connect.CodePermissionDenied {
			t.Errorf("Code = %v, want %v", fe.Code, connect.CodePermissionDenied)
		}
		if fe.Message != "denied" {
			t.Errorf("Message = %q, want %q", fe.Message, "denied")
		}
	})

	t.Run("non-connect error passes through", func(t *testing.T) {
		plain := errors.New("boom")
		if got := wrap(plain); !errors.Is(got, plain) {
			t.Errorf("wrap mutated a non-connect error: %v", got)
		}
	})
}

// reasonCatalog is a connect handler that fails ListProviders with an
// apierr-style error carrying an ErrorInfo reason, so the reason can be
// asserted across the real connect wire (decode-from-Any path).
type reasonCatalog struct {
	catalogv1connect.UnimplementedCatalogServiceHandler
}

func (reasonCatalog) ListProviders(context.Context, *connect.Request[catalogv1.ListProvidersRequest]) (*connect.Response[catalogv1.ListProvidersResponse], error) {
	e := connect.NewError(connect.CodePermissionDenied, errors.New("caller is not a member"))
	detail, _ := connect.NewErrorDetail(&errdetails.ErrorInfo{Reason: "CROSS_TENANT_DENIED", Domain: "tetrate.ai"})
	e.AddDetail(detail)
	return nil, e
}

// TestWrapReasonOverWire proves the reason decodes off the wire status
// details[] (google.protobuf.Any unmarshal), not just from a locally built
// detail. It drives a real connect server through the SDK's wrap path.
func TestWrapReasonOverWire(t *testing.T) {
	mux := http.NewServeMux()
	mux.Handle(catalogv1connect.NewCatalogServiceHandler(reasonCatalog{}))
	srv := httptest.NewServer(mux)
	defer srv.Close()

	client := catalogv1connect.NewCatalogServiceClient(srv.Client(), srv.URL)
	_, err := client.ListProviders(context.Background(), connect.NewRequest(&catalogv1.ListProvidersRequest{}))
	if err == nil {
		t.Fatal("expected an error from the server")
	}

	wrapped := wrap(err)
	var fe *Error
	if !errors.As(wrapped, &fe) {
		t.Fatalf("wrap did not return *Error, got %T", wrapped)
	}
	if fe.Reason != "CROSS_TENANT_DENIED" {
		t.Errorf("Reason = %q, want CROSS_TENANT_DENIED", fe.Reason)
	}
	if fe.Code != connect.CodePermissionDenied {
		t.Errorf("Code = %v, want %v", fe.Code, connect.CodePermissionDenied)
	}
}
