package middleware

import (
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/pkg/auth"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/stretchr/testify/assert"
)

func TestRequirePermission_AllowsSuperUser(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()

	r.Use(func(c *gin.Context) {
		c.Set(constants.ContextKeyUser, auth.UserSession{
			ID:        "admin-123",
			ProfileId: constants.ProfileSystemAdmin,
		})
		c.Next()
	})
	r.Use(RequirePermission(constants.PermManageUsers))
	r.GET("/admin", func(c *gin.Context) {
		c.String(http.StatusOK, "admin access")
	})

	req := httptest.NewRequest("GET", "/admin", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
}

func TestRequirePermission_BlocksWithoutUser(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(RequirePermission(constants.PermManageUsers))
	r.GET("/admin", func(c *gin.Context) {
		c.String(http.StatusOK, "admin access")
	})

	req := httptest.NewRequest("GET", "/admin", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
	assert.Contains(t, w.Body.String(), "USER_NOT_FOUND")
}

func TestRequirePermission_BlocksWithoutPermission(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()

	r.Use(func(c *gin.Context) {
		c.Set(constants.ContextKeyUser, auth.UserSession{
			ID:        "user-123",
			ProfileId: "standard_user",
		})
		c.Set(constants.ContextKeySystemPermissions, map[string]bool{})
		c.Next()
	})
	r.Use(RequirePermission(constants.PermManageUsers))
	r.GET("/admin", func(c *gin.Context) {
		c.String(http.StatusOK, "admin access")
	})

	req := httptest.NewRequest("GET", "/admin", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
	assert.Contains(t, w.Body.String(), "MISSING_PERMISSION")
}

func TestRequirePermission_AllowsWithPermission(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()

	r.Use(func(c *gin.Context) {
		c.Set(constants.ContextKeyUser, auth.UserSession{
			ID:        "user-123",
			ProfileId: "standard_user",
		})
		c.Set(constants.ContextKeySystemPermissions, map[string]bool{
			string(constants.PermManageUsers): true,
		})
		c.Next()
	})
	r.Use(RequirePermission(constants.PermManageUsers))
	r.GET("/admin", func(c *gin.Context) {
		c.String(http.StatusOK, "admin access")
	})

	req := httptest.NewRequest("GET", "/admin", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
}

func TestRequireAnyPermission_AllowsWithOneMatch(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()

	r.Use(func(c *gin.Context) {
		c.Set(constants.ContextKeyUser, auth.UserSession{
			ID:        "user-123",
			ProfileId: "standard_user",
		})
		c.Set(constants.ContextKeySystemPermissions, map[string]bool{
			string(constants.PermRunReports): true,
		})
		c.Next()
	})
	r.Use(RequireAnyPermission(constants.PermManageUsers, constants.PermRunReports))
	r.GET("/reports", func(c *gin.Context) {
		c.String(http.StatusOK, "reports access")
	})

	req := httptest.NewRequest("GET", "/reports", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
}

func TestRequireAnyPermission_BlocksWithNoMatch(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()

	r.Use(func(c *gin.Context) {
		c.Set(constants.ContextKeyUser, auth.UserSession{
			ID:        "user-123",
			ProfileId: "standard_user",
		})
		c.Set(constants.ContextKeySystemPermissions, map[string]bool{
			"SomeOtherPermission": true,
		})
		c.Next()
	})
	r.Use(RequireAnyPermission(constants.PermManageUsers, constants.PermRunReports))
	r.GET("/restricted", func(c *gin.Context) {
		c.String(http.StatusOK, "access granted")
	})

	req := httptest.NewRequest("GET", "/restricted", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
	assert.Contains(t, w.Body.String(), "MISSING_PERMISSION")
}

func TestRequirePermission_InvalidUserType(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set(constants.ContextKeyUser, "not-a-user-session")
		c.Next()
	})
	r.Use(RequirePermission(constants.PermManageUsers))
	r.GET("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})

	req := httptest.NewRequest("GET", "/test", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusInternalServerError, w.Code)
	assert.Contains(t, w.Body.String(), "INTERNAL_ERROR")
}

func TestRequirePermission_PermissionsNotLoaded(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set(constants.ContextKeyUser, auth.UserSession{
			ID:        "user-123",
			ProfileId: "standard_user",
		})
		c.Next()
	})
	r.Use(RequirePermission(constants.PermManageUsers))
	r.GET("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})

	req := httptest.NewRequest("GET", "/test", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
	assert.Contains(t, w.Body.String(), "PERMISSIONS_NOT_LOADED")
}

func TestRequirePermission_InvalidPermissionsType(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set(constants.ContextKeyUser, auth.UserSession{
			ID:        "user-123",
			ProfileId: "standard_user",
		})
		c.Set(constants.ContextKeySystemPermissions, "invalid-type")
		c.Next()
	})
	r.Use(RequirePermission(constants.PermManageUsers))
	r.GET("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})

	req := httptest.NewRequest("GET", "/test", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusInternalServerError, w.Code)
	assert.Contains(t, w.Body.String(), "INTERNAL_ERROR")
}

func TestRequireAnyPermission_BlocksWithoutUser(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(RequireAnyPermission(constants.PermManageUsers))
	r.GET("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})

	req := httptest.NewRequest("GET", "/test", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
	assert.Contains(t, w.Body.String(), "USER_NOT_FOUND")
}

func TestRequireAnyPermission_InvalidUserType(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set(constants.ContextKeyUser, 42)
		c.Next()
	})
	r.Use(RequireAnyPermission(constants.PermManageUsers))
	r.GET("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})

	req := httptest.NewRequest("GET", "/test", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusInternalServerError, w.Code)
	assert.Contains(t, w.Body.String(), "INTERNAL_ERROR")
}

func TestRequireAnyPermission_AllowsSuperUser(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set(constants.ContextKeyUser, auth.UserSession{
			ID:        "admin-1",
			ProfileId: constants.ProfileSystemAdmin,
		})
		c.Next()
	})
	r.Use(RequireAnyPermission(constants.PermManageUsers, constants.PermRunReports))
	r.GET("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})

	req := httptest.NewRequest("GET", "/test", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
}

func TestRequireAnyPermission_PermissionsNotLoaded(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set(constants.ContextKeyUser, auth.UserSession{
			ID:        "user-123",
			ProfileId: "standard_user",
		})
		c.Next()
	})
	r.Use(RequireAnyPermission(constants.PermManageUsers))
	r.GET("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})

	req := httptest.NewRequest("GET", "/test", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
	assert.Contains(t, w.Body.String(), "PERMISSIONS_NOT_LOADED")
}

func TestRequireAnyPermission_InvalidPermissionsType(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set(constants.ContextKeyUser, auth.UserSession{
			ID:        "user-123",
			ProfileId: "standard_user",
		})
		c.Set(constants.ContextKeySystemPermissions, "bad-type")
		c.Next()
	})
	r.Use(RequireAnyPermission(constants.PermManageUsers))
	r.GET("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})

	req := httptest.NewRequest("GET", "/test", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusInternalServerError, w.Code)
	assert.Contains(t, w.Body.String(), "INTERNAL_ERROR")
}

// ---------------------------------------------------------------------------
// Tests: PermissionCache
// ---------------------------------------------------------------------------

func TestPermissionCache_GetSet(t *testing.T) {
	cache := NewPermissionCache(5 * time.Minute)

	sysPerms := map[string]bool{"ViewSetup": true, "ManageUsers": false}
	custPerms := map[string]bool{"CustomPerm1": true}

	cache.set("user1", "profile1", sysPerms, custPerms)

	gotSys, gotCust, hit := cache.get("user1", "profile1")
	assert.True(t, hit)
	assert.Equal(t, sysPerms, gotSys)
	assert.Equal(t, custPerms, gotCust)
}

func TestPermissionCache_Miss(t *testing.T) {
	cache := NewPermissionCache(5 * time.Minute)

	_, _, hit := cache.get("nonexistent", "profile")
	assert.False(t, hit)
}

func TestPermissionCache_Expiry(t *testing.T) {
	cache := NewPermissionCache(1 * time.Millisecond)

	cache.set("user1", "profile1", map[string]bool{"ViewSetup": true}, map[string]bool{})
	time.Sleep(5 * time.Millisecond)

	_, _, hit := cache.get("user1", "profile1")
	assert.False(t, hit, "cache entry should have expired")
}

func TestPermissionCache_Invalidate(t *testing.T) {
	cache := NewPermissionCache(5 * time.Minute)

	cache.set("user1", "profile1", map[string]bool{"ViewSetup": true}, map[string]bool{})
	cache.set("user2", "profile2", map[string]bool{"ManageUsers": true}, map[string]bool{})

	cache.Invalidate()

	_, _, hit1 := cache.get("user1", "profile1")
	_, _, hit2 := cache.get("user2", "profile2")
	assert.False(t, hit1)
	assert.False(t, hit2)
}

func TestPermissionCache_InvalidateUser(t *testing.T) {
	cache := NewPermissionCache(5 * time.Minute)

	cache.set("user1", "profile1", map[string]bool{"ViewSetup": true}, map[string]bool{})
	cache.set("user2", "profile2", map[string]bool{"ManageUsers": true}, map[string]bool{})

	cache.InvalidateUser("user1")

	_, _, hit1 := cache.get("user1", "profile1")
	_, _, hit2 := cache.get("user2", "profile2")
	assert.False(t, hit1, "user1 should be invalidated")
	assert.True(t, hit2, "user2 should still be cached")
}
