package security

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

// Mock implementations for testing composite provider
type mockAuth struct {
	loginResp  *LoginResponse
	loginErr   error
	logoutErr  error
	authUser   *UserContext
	authErr    error
	supportsRefresh bool
	supportsValidate bool
}

func (m *mockAuth) Login(ctx context.Context, req LoginRequest) (*LoginResponse, error) {
	return m.loginResp, m.loginErr
}

func (m *mockAuth) LoginWithCookie(ctx context.Context, req LoginRequest, _ http.ResponseWriter) (*LoginResponse, error) {
	return m.Login(ctx, req)
}

func (m *mockAuth) Logout(ctx context.Context, req LogoutRequest) error {
	return m.logoutErr
}

func (m *mockAuth) LogoutWithCookie(ctx context.Context, req LogoutRequest, _ http.ResponseWriter) error {
	return m.Logout(ctx, req)
}

func (m *mockAuth) Authenticate(r *http.Request) (*UserContext, error) {
	return m.authUser, m.authErr
}

func (m *mockAuth) SetAuthenticateCallback(_ func(r *http.Request) (*UserContext, error)) {}

// Optional interface implementations
func (m *mockAuth) RefreshToken(ctx context.Context, refreshToken string) (*LoginResponse, error) {
	if !m.supportsRefresh {
		return nil, errors.New("not supported")
	}
	return m.loginResp, m.loginErr
}

func (m *mockAuth) ValidateToken(ctx context.Context, token string) (bool, error) {
	if !m.supportsValidate {
		return false, errors.New("not supported")
	}
	return true, nil
}

type mockColSec struct {
	rules []ColumnSecurity
	err   error
	supportsCache bool
}

func (m *mockColSec) GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]ColumnSecurity, error) {
	return m.rules, m.err
}

func (m *mockColSec) ClearCache(ctx context.Context, userID int, schema, table string) error {
	if !m.supportsCache {
		return errors.New("not supported")
	}
	return nil
}

type mockRowSec struct {
	rowSec RowSecurity
	err    error
	supportsCache bool
}

func (m *mockRowSec) GetRowSecurity(ctx context.Context, userRef any, schema, table string) (RowSecurity, error) {
	return m.rowSec, m.err
}

func (m *mockRowSec) ClearCache(ctx context.Context, userID int, schema, table string) error {
	if !m.supportsCache {
		return errors.New("not supported")
	}
	return nil
}

// Test NewCompositeSecurityProvider
func TestNewCompositeSecurityProvider(t *testing.T) {
	t.Run("with all valid providers", func(t *testing.T) {
		auth := &mockAuth{}
		colSec := &mockColSec{}
		rowSec := &mockRowSec{}

		composite, err := NewCompositeSecurityProvider(auth, colSec, rowSec)
		if err != nil {
			t.Fatalf("expected no error, got %v", err)
		}
		if composite == nil {
			t.Fatal("expected non-nil composite provider")
		}
	})

	t.Run("with nil authenticator", func(t *testing.T) {
		colSec := &mockColSec{}
		rowSec := &mockRowSec{}

		_, err := NewCompositeSecurityProvider(nil, colSec, rowSec)
		if err == nil {
			t.Fatal("expected error with nil authenticator")
		}
	})

	t.Run("with nil column security provider", func(t *testing.T) {
		auth := &mockAuth{}
		rowSec := &mockRowSec{}

		_, err := NewCompositeSecurityProvider(auth, nil, rowSec)
		if err == nil {
			t.Fatal("expected error with nil column security provider")
		}
	})

	t.Run("with nil row security provider", func(t *testing.T) {
		auth := &mockAuth{}
		colSec := &mockColSec{}

		_, err := NewCompositeSecurityProvider(auth, colSec, nil)
		if err == nil {
			t.Fatal("expected error with nil row security provider")
		}
	})
}

// Test CompositeSecurityProvider authentication delegation
func TestCompositeSecurityProviderAuth(t *testing.T) {
	userCtx := &UserContext{
		UserID:   1,
		UserName: "testuser",
	}

	t.Run("login delegates to authenticator", func(t *testing.T) {
		auth := &mockAuth{
			loginResp: &LoginResponse{
				Token: "abc123",
				User:  userCtx,
			},
		}
		colSec := &mockColSec{}
		rowSec := &mockRowSec{}

		composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
		ctx := context.Background()
		req := LoginRequest{Username: "test", Password: "pass"}

		resp, err := composite.Login(ctx, req)
		if err != nil {
			t.Fatalf("expected no error, got %v", err)
		}
		if resp.Token != "abc123" {
			t.Errorf("expected token abc123, got %s", resp.Token)
		}
	})

	t.Run("logout delegates to authenticator", func(t *testing.T) {
		auth := &mockAuth{}
		colSec := &mockColSec{}
		rowSec := &mockRowSec{}

		composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
		ctx := context.Background()
		req := LogoutRequest{Token: "abc123", UserID: 1}

		err := composite.Logout(ctx, req)
		if err != nil {
			t.Fatalf("expected no error, got %v", err)
		}
	})

	t.Run("authenticate delegates to authenticator", func(t *testing.T) {
		auth := &mockAuth{
			authUser: userCtx,
		}
		colSec := &mockColSec{}
		rowSec := &mockRowSec{}

		composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
		req := httptest.NewRequest("GET", "/test", nil)

		user, err := composite.Authenticate(req)
		if err != nil {
			t.Fatalf("expected no error, got %v", err)
		}
		if user.UserID != 1 {
			t.Errorf("expected UserID 1, got %d", user.UserID)
		}
	})
}

// Test CompositeSecurityProvider security provider delegation
func TestCompositeSecurityProviderSecurity(t *testing.T) {
	t.Run("get column security delegates to column provider", func(t *testing.T) {
		auth := &mockAuth{}
		colSec := &mockColSec{
			rules: []ColumnSecurity{
				{Schema: "public", Tablename: "users", Path: []string{"email"}},
			},
		}
		rowSec := &mockRowSec{}

		composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
		ctx := context.Background()

		rules, err := composite.GetColumnSecurity(ctx, 1, "public", "users")
		if err != nil {
			t.Fatalf("expected no error, got %v", err)
		}
		if len(rules) != 1 {
			t.Errorf("expected 1 rule, got %d", len(rules))
		}
	})

	t.Run("get row security delegates to row provider", func(t *testing.T) {
		auth := &mockAuth{}
		colSec := &mockColSec{}
		rowSec := &mockRowSec{
			rowSec: RowSecurity{
				Schema:    "public",
				Tablename: "orders",
				Template:  "user_id = {UserID}",
			},
		}

		composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
		ctx := context.Background()

		rowSecResult, err := composite.GetRowSecurity(ctx, 1, "public", "orders")
		if err != nil {
			t.Fatalf("expected no error, got %v", err)
		}
		if rowSecResult.Template != "user_id = {UserID}" {
			t.Errorf("expected template 'user_id = {UserID}', got %s", rowSecResult.Template)
		}
	})
}

// Test CompositeSecurityProvider optional interfaces
func TestCompositeSecurityProviderOptionalInterfaces(t *testing.T) {
	t.Run("refresh token with support", func(t *testing.T) {
		auth := &mockAuth{
			supportsRefresh: true,
			loginResp: &LoginResponse{
				Token: "new-token",
			},
		}
		colSec := &mockColSec{}
		rowSec := &mockRowSec{}

		composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
		ctx := context.Background()

		resp, err := composite.RefreshToken(ctx, "old-token")
		if err != nil {
			t.Fatalf("expected no error, got %v", err)
		}
		if resp.Token != "new-token" {
			t.Errorf("expected token new-token, got %s", resp.Token)
		}
	})

	t.Run("refresh token without support", func(t *testing.T) {
		auth := &mockAuth{
			supportsRefresh: false,
		}
		colSec := &mockColSec{}
		rowSec := &mockRowSec{}

		composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
		ctx := context.Background()

		_, err := composite.RefreshToken(ctx, "token")
		if err == nil {
			t.Fatal("expected error when refresh not supported")
		}
	})

	t.Run("validate token with support", func(t *testing.T) {
		auth := &mockAuth{
			supportsValidate: true,
		}
		colSec := &mockColSec{}
		rowSec := &mockRowSec{}

		composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
		ctx := context.Background()

		valid, err := composite.ValidateToken(ctx, "token")
		if err != nil {
			t.Fatalf("expected no error, got %v", err)
		}
		if !valid {
			t.Error("expected token to be valid")
		}
	})

	t.Run("validate token without support", func(t *testing.T) {
		auth := &mockAuth{
			supportsValidate: false,
		}
		colSec := &mockColSec{}
		rowSec := &mockRowSec{}

		composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
		ctx := context.Background()

		_, err := composite.ValidateToken(ctx, "token")
		if err == nil {
			t.Fatal("expected error when validate not supported")
		}
	})
}

// Test CompositeSecurityProvider cache clearing
func TestCompositeSecurityProviderClearCache(t *testing.T) {
	t.Run("clear cache with support", func(t *testing.T) {
		auth := &mockAuth{}
		colSec := &mockColSec{supportsCache: true}
		rowSec := &mockRowSec{supportsCache: true}

		composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
		ctx := context.Background()

		err := composite.ClearCache(ctx, 1, "public", "users")
		if err != nil {
			t.Fatalf("expected no error, got %v", err)
		}
	})

	t.Run("clear cache without support", func(t *testing.T) {
		auth := &mockAuth{}
		colSec := &mockColSec{supportsCache: false}
		rowSec := &mockRowSec{supportsCache: false}

		composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
		ctx := context.Background()

		// Should not error even if providers don't support cache
		// (they just won't implement the interface)
		err := composite.ClearCache(ctx, 1, "public", "users")
		if err != nil {
			// It's ok if this errors, as the providers don't implement Cacheable
			t.Logf("cache clear returned error as expected: %v", err)
		}
	})

	t.Run("clear cache with partial support", func(t *testing.T) {
		auth := &mockAuth{}
		colSec := &mockColSec{supportsCache: true}
		rowSec := &mockRowSec{supportsCache: false}

		composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
		ctx := context.Background()

		err := composite.ClearCache(ctx, 1, "public", "users")
		// Should succeed for column security even if row security fails
		if err == nil {
			t.Log("cache clear succeeded partially")
		} else {
			t.Logf("cache clear returned error: %v", err)
		}
	})
}

// Test error propagation
func TestCompositeSecurityProviderErrorPropagation(t *testing.T) {
	t.Run("login error propagates", func(t *testing.T) {
		auth := &mockAuth{
			loginErr: errors.New("invalid credentials"),
		}
		colSec := &mockColSec{}
		rowSec := &mockRowSec{}

		composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
		ctx := context.Background()

		_, err := composite.Login(ctx, LoginRequest{})
		if err == nil {
			t.Fatal("expected error to propagate")
		}
	})

	t.Run("authenticate error propagates", func(t *testing.T) {
		auth := &mockAuth{
			authErr: errors.New("invalid token"),
		}
		colSec := &mockColSec{}
		rowSec := &mockRowSec{}

		composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
		req := httptest.NewRequest("GET", "/test", nil)

		_, err := composite.Authenticate(req)
		if err == nil {
			t.Fatal("expected error to propagate")
		}
	})

	t.Run("column security error propagates", func(t *testing.T) {
		auth := &mockAuth{}
		colSec := &mockColSec{
			err: errors.New("failed to load column security"),
		}
		rowSec := &mockRowSec{}

		composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
		ctx := context.Background()

		_, err := composite.GetColumnSecurity(ctx, 1, "public", "users")
		if err == nil {
			t.Fatal("expected error to propagate")
		}
	})

	t.Run("row security error propagates", func(t *testing.T) {
		auth := &mockAuth{}
		colSec := &mockColSec{}
		rowSec := &mockRowSec{
			err: errors.New("failed to load row security"),
		}

		composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
		ctx := context.Background()

		_, err := composite.GetRowSecurity(ctx, 1, "public", "orders")
		if err == nil {
			t.Fatal("expected error to propagate")
		}
	})
}
