package app

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"reflect"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.opentelemetry.io/otel/metric"
	"go.opentelemetry.io/otel/metric/noop"
	"go.uber.org/mock/gomock"

	"github.com/stacklok/toolhive-registry-server/internal/app/storage/mocks"
	"github.com/stacklok/toolhive-registry-server/internal/config"
	"github.com/stacklok/toolhive-registry-server/internal/kubernetes"
	mocksvc "github.com/stacklok/toolhive-registry-server/internal/service/mocks"
	"github.com/stacklok/toolhive-registry-server/internal/sources"
	pkgsync "github.com/stacklok/toolhive-registry-server/internal/sync"
)

func TestNewRegistryAppBuilder(t *testing.T) {
	t.Parallel()
	cfg := &config.Config{
		Sources: []config.SourceConfig{
			{
				Name: "test-registry-1",
				File: &config.FileConfig{
					Path: "/tmp/test-registry.json",
				},
				SyncPolicy: &config.SyncPolicyConfig{
					Interval: "30m",
				},
			},
		},
		// Auth is required by default; use anonymous mode for tests
		Auth: &config.AuthConfig{
			Mode: config.AuthModeAnonymous,
		},
	}

	built, err := baseConfig(WithConfig(cfg))
	require.NoError(t, err)
	require.NotNil(t, built)
	assert.Equal(t, defaultHTTPAddress, built.address)
}

func TestRegistryAppWithFunctions(t *testing.T) {
	t.Parallel()
	built, err := baseConfig(
		WithConfig(createValidTestConfig()),
		WithAddress(":9090"),
	)
	require.NoError(t, err)
	require.NotNil(t, built)
}

func TestRegistryAppWithFunctionsError(t *testing.T) {
	t.Parallel()
	built, err := baseConfig(
		WithConfig(createValidTestConfig()),
		WithAddress(":"),
	)
	require.Error(t, err)
	require.Nil(t, built)
}

func TestRegistryAppBuilder_WithAddress(t *testing.T) {
	t.Parallel()
	built, err := baseConfig(
		WithConfig(createValidTestConfig()),
		WithAddress(":9090"),
	)
	require.NoError(t, err)
	assert.Equal(t, ":9090", built.address)
}

// createValidTestConfig creates a minimal valid config for testing
func createValidTestConfig() *config.Config {
	return &config.Config{
		Sources: []config.SourceConfig{
			{
				Name: "test-registry-1",
				File: &config.FileConfig{
					Path: "/tmp/test-registry.json",
				},
				SyncPolicy: &config.SyncPolicyConfig{
					Interval: "30m",
				},
			},
		},
		// Auth is required by default; use anonymous mode for tests
		Auth: &config.AuthConfig{
			Mode: config.AuthModeAnonymous,
		},
	}
}

func TestWithConfig(t *testing.T) {
	t.Parallel()
	cfg := &registryAppConfig{}
	testConfig := createValidTestConfig()

	opt := WithConfig(testConfig)
	err := opt(cfg)

	require.NoError(t, err)
	assert.Equal(t, testConfig, cfg.config)
}

func TestWithAddress(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name    string
		address string
		want    string
		wantErr bool
	}{
		{name: "valid address", address: ":9999", want: ":9999"},
		{name: "valid address with host", address: "127.0.0.1:9999", want: "127.0.0.1:9999"},
		{name: "valid address with host and port", address: "localhost:9999", want: "localhost:9999"},
		{name: "invalid empty address", address: "", want: "", wantErr: true},
		{name: "invalid empty port", address: ":", want: "", wantErr: true},
		{name: "invalid address with host and port", address: "localhost:999999", want: "", wantErr: true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			cfg := &registryAppConfig{}
			opt := WithAddress(tt.address)
			err := opt(cfg)

			if tt.wantErr {
				require.Error(t, err)
				return
			}

			require.NoError(t, err)
			assert.Equal(t, tt.want, cfg.address)
		})
	}
}

func TestWithRegistryName(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name         string
		registryName string
		want         string
		wantErr      bool
	}{
		{
			name:         "valid registry name",
			registryName: "my-registry",
			want:         "my-registry",
			wantErr:      false,
		},
		{
			name:         "valid registry name with underscores",
			registryName: "my_registry_v1",
			want:         "my_registry_v1",
			wantErr:      false,
		},
		{
			name:         "valid registry name with numbers",
			registryName: "registry-123",
			want:         "registry-123",
			wantErr:      false,
		},
		{
			name:         "valid long registry name",
			registryName: "my-very-long-registry-name-with-many-segments",
			want:         "my-very-long-registry-name-with-many-segments",
			wantErr:      false,
		},
		{
			name:         "invalid empty registry name",
			registryName: "",
			want:         "",
			wantErr:      true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			opt := kubernetes.WithRegistryName(tt.registryName)

			// Get the Option function type to extract the parameter type
			optionFuncType := reflect.TypeOf(opt)
			// Option is func(*mcpServerReconcilerOptions) error
			// Get the element type of the first parameter
			paramType := optionFuncType.In(0).Elem()

			// Create a new instance of the struct
			optionsValue := reflect.New(paramType)

			// Call the option function
			results := reflect.ValueOf(opt).Call([]reflect.Value{optionsValue})
			errVal := results[0]
			var err error
			if !errVal.IsNil() {
				err = errVal.Interface().(error)
			}

			if tt.wantErr {
				require.Error(t, err)
				return
			}

			require.NoError(t, err)

			// Use reflection to get the registryName field
			registryNameField := optionsValue.Elem().FieldByName("registryName")
			require.True(t, registryNameField.IsValid(), "registryName field should exist")
			got := registryNameField.String()
			assert.Equal(t, tt.want, got)
		})
	}
}

func TestWithMiddlewares(t *testing.T) {
	t.Parallel()
	cfg := &registryAppConfig{}
	middleware1 := func(next http.Handler) http.Handler { return next }
	middleware2 := func(next http.Handler) http.Handler { return next }

	opt := WithMiddlewares(middleware1, middleware2)
	err := opt(cfg)

	require.NoError(t, err)
	assert.Len(t, cfg.middlewares, 2)
}

func TestWithRegistryHandlerFactory(t *testing.T) {
	t.Parallel()
	cfg := &registryAppConfig{}
	// Use nil factory for testing - we're just verifying the field is set
	var testFactory sources.RegistryHandlerFactory

	opt := WithRegistryHandlerFactory(testFactory)
	err := opt(cfg)

	require.NoError(t, err)
	assert.Equal(t, testFactory, cfg.registryHandlerFactory)
}

func TestWithStorageFactory(t *testing.T) {
	t.Parallel()
	ctrl := gomock.NewController(t)
	cfg := &registryAppConfig{}
	testFactory := mocks.NewMockFactory(ctrl)

	opt := WithStorageFactory(testFactory)
	err := opt(cfg)

	require.NoError(t, err)
	assert.Equal(t, testFactory, cfg.storageFactory)
}

func TestWithSyncManager(t *testing.T) {
	t.Parallel()
	cfg := &registryAppConfig{}
	// Use nil sync manager for testing - we're just verifying the field is set
	var testSyncManager pkgsync.Manager

	opt := WithSyncManager(testSyncManager)
	err := opt(cfg)

	require.NoError(t, err)
	assert.Equal(t, testSyncManager, cfg.syncManager)
}

func TestWithMeterProvider(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name     string
		provider metric.MeterProvider
		wantNil  bool
	}{
		{
			name:     "sets noop meter provider",
			provider: noop.NewMeterProvider(),
			wantNil:  false,
		},
		{
			name:     "sets nil meter provider",
			provider: nil,
			wantNil:  true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			cfg := &registryAppConfig{}

			opt := WithMeterProvider(tt.provider)
			err := opt(cfg)

			require.NoError(t, err)
			if tt.wantNil {
				assert.Nil(t, cfg.meterProvider)
			} else {
				assert.Equal(t, tt.provider, cfg.meterProvider)
			}
		})
	}
}

func TestBuildHTTPServer(t *testing.T) {
	t.Parallel()
	ctx := context.Background()

	tests := []struct {
		name           string
		config         *registryAppConfig
		setupMock      func(*mocksvc.MockRegistryService)
		wantAddr       string
		wantReadTO     time.Duration
		wantWriteTO    time.Duration
		wantIdleTO     time.Duration
		expectDefaults bool
	}{
		{
			name: "with default middlewares",
			config: &registryAppConfig{
				address:        ":8080",
				middlewares:    nil, // nil triggers default middlewares
				requestTimeout: 10 * time.Second,
				readTimeout:    10 * time.Second,
				writeTimeout:   15 * time.Second,
				idleTimeout:    60 * time.Second,
			},
			setupMock:      func(_ *mocksvc.MockRegistryService) {},
			wantAddr:       ":8080",
			wantReadTO:     10 * time.Second,
			wantWriteTO:    15 * time.Second,
			wantIdleTO:     60 * time.Second,
			expectDefaults: true,
		},
		{
			name: "with custom middlewares",
			config: &registryAppConfig{
				address: ":9090",
				middlewares: []func(http.Handler) http.Handler{
					func(next http.Handler) http.Handler { return next },
				},
				requestTimeout: 5 * time.Second,
				readTimeout:    5 * time.Second,
				writeTimeout:   10 * time.Second,
				idleTimeout:    30 * time.Second,
			},
			setupMock:      func(_ *mocksvc.MockRegistryService) {},
			wantAddr:       ":9090",
			wantReadTO:     5 * time.Second,
			wantWriteTO:    10 * time.Second,
			wantIdleTO:     30 * time.Second,
			expectDefaults: false,
		},
		{
			name: "with custom address and timeouts",
			config: &registryAppConfig{
				address:        "127.0.0.1:3000",
				middlewares:    nil,
				requestTimeout: 20 * time.Second,
				readTimeout:    20 * time.Second,
				writeTimeout:   30 * time.Second,
				idleTimeout:    120 * time.Second,
			},
			setupMock:      func(_ *mocksvc.MockRegistryService) {},
			wantAddr:       "127.0.0.1:3000",
			wantReadTO:     20 * time.Second,
			wantWriteTO:    30 * time.Second,
			wantIdleTO:     120 * time.Second,
			expectDefaults: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			ctrl := gomock.NewController(t)
			defer ctrl.Finish()

			mockSvc := mocksvc.NewMockRegistryService(ctrl)
			tt.setupMock(mockSvc)

			// Set auth middleware in config for tests
			tt.config.authMiddleware = func(next http.Handler) http.Handler { return next }
			tt.config.authInfoHandler = nil
			server, err := buildHTTPServer(ctx, tt.config, mockSvc, nil)

			require.NoError(t, err)
			require.NotNil(t, server)
			assert.Equal(t, tt.wantAddr, server.Addr)
			assert.Equal(t, tt.wantReadTO, server.ReadTimeout)
			assert.Equal(t, tt.wantWriteTO, server.WriteTimeout)
			assert.Equal(t, tt.wantIdleTO, server.IdleTimeout)
			assert.NotNil(t, server.Handler)

			// Verify middlewares were set
			// Note: auth middleware is always appended, so counts are +1
			if tt.expectDefaults {
				assert.NotNil(t, tt.config.middlewares)
				assert.Greater(t, len(tt.config.middlewares), 0, "default middlewares should be set")
			} else {
				assert.Equal(t, 5, len(tt.config.middlewares), "custom middlewares should be preserved plus auth-failure-audit + auth + role-resolution + audit middlewares")
			}
		})
	}
}

func TestBuildHTTPServer_WithMeterProvider(t *testing.T) {
	t.Parallel()
	ctx := context.Background()

	tests := []struct {
		name                    string
		meterProvider           metric.MeterProvider
		initialMiddlewareCount  int
		expectedMiddlewareCount int
		description             string
	}{
		{
			name:                    "with meter provider adds metrics middleware",
			meterProvider:           noop.NewMeterProvider(),
			initialMiddlewareCount:  0,  // nil triggers defaults (5) + metrics (1) + auth-failure-audit (1) + auth (1) + roles (1) + audit (1) = 10
			expectedMiddlewareCount: 10, // 5 default + 1 metrics + 1 auth-failure-audit + 1 auth + 1 roles + 1 audit
			description:             "metrics middleware should be prepended when meter provider is set",
		},
		{
			name:                    "without meter provider no metrics middleware",
			meterProvider:           nil,
			initialMiddlewareCount:  0, // nil triggers defaults (5) + auth-failure-audit (1) + auth (1) + roles (1) + audit (1) = 9
			expectedMiddlewareCount: 9, // 5 default + 1 auth-failure-audit + 1 auth + 1 roles + 1 audit (no metrics)
			description:             "no metrics middleware should be added when meter provider is nil",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			ctrl := gomock.NewController(t)
			defer ctrl.Finish()

			mockSvc := mocksvc.NewMockRegistryService(ctrl)

			cfg := &registryAppConfig{
				address:        ":8080",
				middlewares:    nil, // nil triggers default middlewares
				requestTimeout: 10 * time.Second,
				readTimeout:    10 * time.Second,
				writeTimeout:   15 * time.Second,
				idleTimeout:    60 * time.Second,
				meterProvider:  tt.meterProvider,
				authMiddleware: func(next http.Handler) http.Handler { return next },
			}

			server, err := buildHTTPServer(ctx, cfg, mockSvc, nil)

			require.NoError(t, err, tt.description)
			require.NotNil(t, server)
			assert.Len(t, cfg.middlewares, tt.expectedMiddlewareCount, tt.description)
		})
	}
}

func TestBuildHTTPServer_MetricsMiddlewareWithCustomMiddlewares(t *testing.T) {
	t.Parallel()
	ctx := context.Background()

	tests := []struct {
		name                    string
		meterProvider           metric.MeterProvider
		customMiddlewareCount   int
		expectedMiddlewareCount int
		description             string
	}{
		{
			name:                    "meter provider with custom middlewares prepends metrics",
			meterProvider:           noop.NewMeterProvider(),
			customMiddlewareCount:   2,
			expectedMiddlewareCount: 7, // 1 metrics + 2 custom + 1 auth-failure-audit + 1 auth + 1 roles + 1 audit
			description:             "metrics middleware should be prepended to custom middlewares",
		},
		{
			name:                    "no meter provider with custom middlewares",
			meterProvider:           nil,
			customMiddlewareCount:   2,
			expectedMiddlewareCount: 6, // 2 custom + 1 auth-failure-audit + 1 auth + 1 roles + 1 audit (no metrics)
			description:             "no metrics middleware with custom middlewares when provider is nil",
		},
		{
			name:                    "meter provider with single custom middleware",
			meterProvider:           noop.NewMeterProvider(),
			customMiddlewareCount:   1,
			expectedMiddlewareCount: 6, // 1 metrics + 1 custom + 1 auth-failure-audit + 1 auth + 1 roles + 1 audit
			description:             "metrics middleware prepended to single custom middleware",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			ctrl := gomock.NewController(t)
			defer ctrl.Finish()

			mockSvc := mocksvc.NewMockRegistryService(ctrl)

			// Create custom middlewares
			customMiddlewares := make([]func(http.Handler) http.Handler, tt.customMiddlewareCount)
			for i := range customMiddlewares {
				customMiddlewares[i] = func(next http.Handler) http.Handler { return next }
			}

			cfg := &registryAppConfig{
				address:        ":8080",
				middlewares:    customMiddlewares,
				requestTimeout: 10 * time.Second,
				readTimeout:    10 * time.Second,
				writeTimeout:   15 * time.Second,
				idleTimeout:    60 * time.Second,
				meterProvider:  tt.meterProvider,
				authMiddleware: func(next http.Handler) http.Handler { return next },
			}

			server, err := buildHTTPServer(ctx, cfg, mockSvc, nil)

			require.NoError(t, err, tt.description)
			require.NotNil(t, server)
			assert.Len(t, cfg.middlewares, tt.expectedMiddlewareCount, tt.description)
		})
	}
}

func TestBuildServiceComponents(t *testing.T) {
	t.Parallel()
	ctx := context.Background()

	t.Run("success with storage factory", func(t *testing.T) {
		t.Parallel()
		ctrl := gomock.NewController(t)
		mockFactory := mocks.NewMockFactory(ctrl)
		mockSvc := mocksvc.NewMockRegistryService(ctrl)

		mockFactory.EXPECT().
			CreateRegistryService(gomock.Any()).
			Return(mockSvc, nil)

		cfg := &registryAppConfig{
			config:         createValidTestConfig(),
			storageFactory: mockFactory,
		}

		svc, err := buildServiceComponents(ctx, cfg)

		require.NoError(t, err)
		require.NotNil(t, svc)
		assert.Equal(t, mockSvc, svc)
	})

	t.Run("error when config is nil", func(t *testing.T) {
		t.Parallel()
		ctrl := gomock.NewController(t)
		mockFactory := mocks.NewMockFactory(ctrl)

		// No expectations - config check happens before factory call

		cfg := &registryAppConfig{
			config:         nil,
			storageFactory: mockFactory,
		}

		svc, err := buildServiceComponents(ctx, cfg)

		require.Error(t, err)
		assert.Nil(t, svc)
	})

	t.Run("error when factory fails", func(t *testing.T) {
		t.Parallel()
		ctrl := gomock.NewController(t)
		mockFactory := mocks.NewMockFactory(ctrl)

		mockFactory.EXPECT().
			CreateRegistryService(gomock.Any()).
			Return(nil, fmt.Errorf("factory creation failed"))

		cfg := &registryAppConfig{
			config:         createValidTestConfig(),
			storageFactory: mockFactory,
		}

		svc, err := buildServiceComponents(ctx, cfg)

		require.Error(t, err)
		assert.Nil(t, svc)
	})
}

func TestBuildSyncComponents(t *testing.T) {
	t.Parallel()
	ctx := context.Background()

	t.Run("success with mock factory", func(t *testing.T) {
		t.Parallel()
		ctrl := gomock.NewController(t)
		mockFactory := mocks.NewMockFactory(ctrl)

		mockFactory.EXPECT().
			CreateStateService(gomock.Any()).
			Return(nil, nil)

		mockFactory.EXPECT().
			CreateSyncWriter(gomock.Any()).
			Return(nil, nil)

		cfg := &registryAppConfig{
			config:         createValidTestConfig(),
			storageFactory: mockFactory,
		}

		coord, err := buildSyncComponents(ctx, cfg)

		require.NoError(t, err)
		require.NotNil(t, coord)
	})

	t.Run("error when state service creation fails", func(t *testing.T) {
		t.Parallel()
		ctrl := gomock.NewController(t)
		mockFactory := mocks.NewMockFactory(ctrl)

		mockFactory.EXPECT().
			CreateStateService(gomock.Any()).
			Return(nil, fmt.Errorf("state service creation failed"))

		cfg := &registryAppConfig{
			config:         createValidTestConfig(),
			storageFactory: mockFactory,
		}

		coord, err := buildSyncComponents(ctx, cfg)

		require.Error(t, err)
		assert.Nil(t, coord)
	})

	t.Run("error when sync writer creation fails", func(t *testing.T) {
		t.Parallel()
		ctrl := gomock.NewController(t)
		mockFactory := mocks.NewMockFactory(ctrl)

		mockFactory.EXPECT().
			CreateStateService(gomock.Any()).
			Return(nil, nil)

		mockFactory.EXPECT().
			CreateSyncWriter(gomock.Any()).
			Return(nil, fmt.Errorf("sync writer creation failed"))

		cfg := &registryAppConfig{
			config:         createValidTestConfig(),
			storageFactory: mockFactory,
		}

		coord, err := buildSyncComponents(ctx, cfg)

		require.Error(t, err)
		assert.Nil(t, coord)
	})
}

func TestBuildSyncComponents_WithMeterProvider(t *testing.T) {
	t.Parallel()
	ctx := context.Background()

	tests := []struct {
		name          string
		meterProvider metric.MeterProvider
		description   string
	}{
		{
			name:          "with meter provider creates available metrics",
			meterProvider: noop.NewMeterProvider(),
			description:   "coordinator should be created with metrics when meter provider is set",
		},
		{
			name:          "without meter provider no metrics created",
			meterProvider: nil,
			description:   "coordinator should be created without metrics when meter provider is nil",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			ctrl := gomock.NewController(t)
			mockFactory := mocks.NewMockFactory(ctrl)

			mockFactory.EXPECT().
				CreateStateService(gomock.Any()).
				Return(nil, nil)

			mockFactory.EXPECT().
				CreateSyncWriter(gomock.Any()).
				Return(nil, nil)

			cfg := &registryAppConfig{
				config:         createValidTestConfig(),
				storageFactory: mockFactory,
				meterProvider:  tt.meterProvider,
			}

			coord, err := buildSyncComponents(ctx, cfg)

			require.NoError(t, err, tt.description)
			require.NotNil(t, coord, tt.description)
		})
	}
}

func TestNewRegistryApp(t *testing.T) {
	t.Parallel()
	ctx := context.Background()

	// Helper to create a mock factory for success tests
	newSuccessMockFactory := func(ctrl *gomock.Controller) *mocks.MockFactory {
		mockFactory := mocks.NewMockFactory(ctrl)
		mockSvc := mocksvc.NewMockRegistryService(ctrl)
		mockFactory.EXPECT().CreateStateService(gomock.Any()).Return(nil, nil)
		mockFactory.EXPECT().CreateSyncWriter(gomock.Any()).Return(nil, nil)
		mockFactory.EXPECT().CreateRegistryService(gomock.Any()).Return(mockSvc, nil)
		return mockFactory
	}

	t.Run("success with minimal config", func(t *testing.T) {
		t.Parallel()
		ctrl := gomock.NewController(t)
		mockFactory := newSuccessMockFactory(ctrl)

		app, err := NewRegistryApp(ctx,
			WithConfig(createValidTestConfig()),
			WithStorageFactory(mockFactory),
		)

		require.NoError(t, err)
		require.NotNil(t, app)
		assert.NotNil(t, app.config)
		assert.Equal(t, 1, len(app.config.Sources))
		assert.NotNil(t, app.components)
		assert.NotNil(t, app.components.SyncCoordinator)
		assert.NotNil(t, app.components.RegistryService)
		assert.NotNil(t, app.httpServer)
		assert.NotNil(t, app.ctx)
		assert.NotNil(t, app.cancelFunc)
		assert.Equal(t, defaultHTTPAddress, app.httpServer.Addr)
	})

	t.Run("success with custom address", func(t *testing.T) {
		t.Parallel()
		ctrl := gomock.NewController(t)
		mockFactory := newSuccessMockFactory(ctrl)

		app, err := NewRegistryApp(ctx,
			WithConfig(createValidTestConfig()),
			WithAddress(":9090"),
			WithStorageFactory(mockFactory),
		)

		require.NoError(t, err)
		require.NotNil(t, app)
		assert.NotNil(t, app.httpServer)
		assert.Equal(t, ":9090", app.httpServer.Addr)
		assert.NotNil(t, app.components.SyncCoordinator)
		assert.NotNil(t, app.components.RegistryService)
	})

	t.Run("success with multiple options", func(t *testing.T) {
		t.Parallel()
		ctrl := gomock.NewController(t)
		mockFactory := newSuccessMockFactory(ctrl)

		app, err := NewRegistryApp(ctx,
			WithConfig(createValidTestConfig()),
			WithAddress(":8888"),
			WithStorageFactory(mockFactory),
		)

		require.NoError(t, err)
		require.NotNil(t, app)
		assert.NotNil(t, app.config)
		assert.NotNil(t, app.components)
		assert.NotNil(t, app.components.SyncCoordinator)
		assert.NotNil(t, app.components.RegistryService)
		assert.NotNil(t, app.httpServer)
		assert.Equal(t, ":8888", app.httpServer.Addr)
		assert.NotNil(t, app.ctx)
		assert.NotNil(t, app.cancelFunc)
	})
}

func TestNewRegistryApp_ErrorPaths(t *testing.T) {
	t.Parallel()
	ctx := context.Background()

	tests := []struct {
		name           string
		setupMocks     func(*gomock.Controller) *mocks.MockFactory
		opts           func(*mocks.MockFactory, string) []RegistryAppOptions
		wantErrContain string
		verifyCleanup  bool
	}{
		{
			name: "error when option returns error",
			setupMocks: func(ctrl *gomock.Controller) *mocks.MockFactory {
				return mocks.NewMockFactory(ctrl)
			},
			opts: func(_ *mocks.MockFactory, _ string) []RegistryAppOptions {
				return []RegistryAppOptions{
					WithConfig(createValidTestConfig()),
					WithAddress(":"), // Invalid address triggers error
				}
			},
			wantErrContain: "failed to build base configuration",
			verifyCleanup:  false, // Storage factory not created yet
		},
		{
			name: "error when state service creation fails",
			setupMocks: func(ctrl *gomock.Controller) *mocks.MockFactory {
				mockFactory := mocks.NewMockFactory(ctrl)
				mockFactory.EXPECT().
					CreateStateService(gomock.Any()).
					Return(nil, fmt.Errorf("state service creation failed"))
				mockFactory.EXPECT().
					Cleanup()
				return mockFactory
			},
			opts: func(mockFactory *mocks.MockFactory, _ string) []RegistryAppOptions {
				return []RegistryAppOptions{
					WithConfig(createValidTestConfig()),
					WithStorageFactory(mockFactory),
				}
			},
			wantErrContain: "failed to build sync components",
			verifyCleanup:  true,
		},
		{
			name: "error when sync writer creation fails",
			setupMocks: func(ctrl *gomock.Controller) *mocks.MockFactory {
				mockFactory := mocks.NewMockFactory(ctrl)
				mockFactory.EXPECT().
					CreateStateService(gomock.Any()).
					Return(nil, nil)
				mockFactory.EXPECT().
					CreateSyncWriter(gomock.Any()).
					Return(nil, fmt.Errorf("sync writer creation failed"))
				mockFactory.EXPECT().
					Cleanup()
				return mockFactory
			},
			opts: func(mockFactory *mocks.MockFactory, _ string) []RegistryAppOptions {
				return []RegistryAppOptions{
					WithConfig(createValidTestConfig()),
					WithStorageFactory(mockFactory),
				}
			},
			wantErrContain: "failed to build sync components",
			verifyCleanup:  true,
		},
		{
			name: "error when service component build fails",
			setupMocks: func(ctrl *gomock.Controller) *mocks.MockFactory {
				mockFactory := mocks.NewMockFactory(ctrl)
				mockFactory.EXPECT().
					CreateStateService(gomock.Any()).
					Return(nil, nil)
				mockFactory.EXPECT().
					CreateSyncWriter(gomock.Any()).
					Return(nil, nil)
				mockFactory.EXPECT().
					CreateRegistryService(gomock.Any()).
					Return(nil, fmt.Errorf("registry service creation failed"))
				mockFactory.EXPECT().
					Cleanup()
				return mockFactory
			},
			opts: func(mockFactory *mocks.MockFactory, _ string) []RegistryAppOptions {
				return []RegistryAppOptions{
					WithConfig(createValidTestConfig()),
					WithStorageFactory(mockFactory),
				}
			},
			wantErrContain: "failed to build service components",
			verifyCleanup:  true,
		},
		{
			name: "error when auth middleware creation fails",
			setupMocks: func(ctrl *gomock.Controller) *mocks.MockFactory {
				mockFactory := mocks.NewMockFactory(ctrl)
				mockSvc := mocksvc.NewMockRegistryService(ctrl)
				mockFactory.EXPECT().
					CreateStateService(gomock.Any()).
					Return(nil, nil)
				mockFactory.EXPECT().
					CreateSyncWriter(gomock.Any()).
					Return(nil, nil)
				mockFactory.EXPECT().
					CreateRegistryService(gomock.Any()).
					Return(mockSvc, nil)
				mockFactory.EXPECT().
					Cleanup()
				return mockFactory
			},
			opts: func(mockFactory *mocks.MockFactory, _ string) []RegistryAppOptions {
				// Config with invalid auth mode to trigger auth middleware error
				invalidAuthConfig := createValidTestConfig()
				invalidAuthConfig.Auth = nil // nil auth config causes error
				return []RegistryAppOptions{
					WithConfig(invalidAuthConfig),
					WithStorageFactory(mockFactory),
				}
			},
			wantErrContain: "failed to build auth middleware",
			verifyCleanup:  true,
		},
		{
			name: "error when service creation returns nil config error",
			setupMocks: func(ctrl *gomock.Controller) *mocks.MockFactory {
				mockFactory := mocks.NewMockFactory(ctrl)
				mockSvc := mocksvc.NewMockRegistryService(ctrl)
				mockFactory.EXPECT().
					CreateStateService(gomock.Any()).
					Return(nil, nil)
				mockFactory.EXPECT().
					CreateSyncWriter(gomock.Any()).
					Return(nil, nil)
				mockFactory.EXPECT().
					CreateRegistryService(gomock.Any()).
					Return(mockSvc, nil)
				mockFactory.EXPECT().
					Cleanup()
				return mockFactory
			},
			opts: func(mockFactory *mocks.MockFactory, _ string) []RegistryAppOptions {
				// Config with unsupported auth mode to trigger error
				invalidConfig := createValidTestConfig()
				invalidConfig.Auth = &config.AuthConfig{
					Mode: "unsupported-mode", // Invalid auth mode
				}
				return []RegistryAppOptions{
					WithConfig(invalidConfig),
					WithStorageFactory(mockFactory),
				}
			},
			wantErrContain: "failed to build auth middleware",
			verifyCleanup:  true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			ctrl := gomock.NewController(t)
			tmpDir := t.TempDir()

			mockFactory := tt.setupMocks(ctrl)
			opts := tt.opts(mockFactory, tmpDir)

			app, err := NewRegistryApp(ctx, opts...)

			require.Error(t, err)
			assert.Nil(t, app)
			assert.Contains(t, err.Error(), tt.wantErrContain)
			// Cleanup verification is done through gomock expectations
		})
	}
}

func TestSecurityHeadersInDefaultMiddleware(t *testing.T) {
	t.Parallel()

	// securityHeadersMiddleware is part of the default middleware chain in
	// buildHTTPServer. Verify the named function sets both headers.
	req, err := http.NewRequest(http.MethodGet, "/", nil)
	require.NoError(t, err)

	rr := httptest.NewRecorder()
	securityHeadersMiddleware(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
	})).ServeHTTP(rr, req)

	assert.Equal(t, "nosniff", rr.Header().Get("X-Content-Type-Options"))
	assert.Equal(t, "same-origin", rr.Header().Get("Cross-Origin-Resource-Policy"))
}
