package middleware

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

	"hyapp-admin-server/internal/model"
	"hyapp-admin-server/internal/repository"
	"hyapp-admin-server/internal/service"

	"github.com/gin-gonic/gin"
)

type fakeAppAccessStore struct {
	access repository.AppAccess
}

type fakeAuthorizationStore struct {
	authorization repository.UserAuthorization
	err           error
}

func (store fakeAuthorizationStore) CurrentAuthorizationForUser(uint) (repository.UserAuthorization, error) {
	return store.authorization, store.err
}

func (store fakeAppAccessStore) AppAccessForUser(uint) (repository.AppAccess, error) {
	return store.access, nil
}

func TestRequireAppScopeAllowsOnlySelectedApp(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(func(c *gin.Context) {
		c.Set(ContextUserID, uint(7))
	})
	router.GET("/app", RequireAppScope(fakeAppAccessStore{access: repository.AppAccess{
		Mode:     model.UserAppScopeModeSelected,
		AppCodes: []string{"lalu"},
	}}), func(c *gin.Context) { c.Status(http.StatusNoContent) })

	allowed := httptest.NewRequest(http.MethodGet, "/app", nil)
	allowed.Header.Set("X-App-Code", "LALU")
	allowedResponse := httptest.NewRecorder()
	router.ServeHTTP(allowedResponse, allowed)
	if allowedResponse.Code != http.StatusNoContent {
		t.Fatalf("allowed status = %d body=%s", allowedResponse.Code, allowedResponse.Body.String())
	}

	for _, header := range []string{"", "huwaa"} {
		request := httptest.NewRequest(http.MethodGet, "/app", nil)
		if header != "" {
			request.Header.Set("X-App-Code", header)
		}
		response := httptest.NewRecorder()
		router.ServeHTTP(response, request)
		if response.Code != http.StatusForbidden {
			t.Fatalf("header %q status = %d body=%s", header, response.Code, response.Body.String())
		}
	}
}

func TestAuthRequiredUsesCurrentPermissionsInsteadOfJWTClaim(t *testing.T) {
	gin.SetMode(gin.TestMode)
	auth := service.NewAuthService("live-authorization-test-secret", time.Hour)
	// This models an access token issued before a role migration: finance audit
	// existed in the claim, while operations audit was absent at issue time.
	token, _, err := auth.GenerateAccessToken(7, "stale-name", []string{"finance-withdrawal:audit"})
	if err != nil {
		t.Fatalf("generate access token: %v", err)
	}

	router := gin.New()
	router.Use(AuthRequired(auth, fakeAuthorizationStore{authorization: repository.UserAuthorization{
		Username:    "current-name",
		Status:      model.UserStatusActive,
		Permissions: []string{"operations-withdrawal:audit"},
	}}))
	router.GET("/finance", RequirePermission("finance-withdrawal:audit"), func(c *gin.Context) {
		c.Status(http.StatusNoContent)
	})
	router.GET("/operations", RequirePermission("operations-withdrawal:audit"), func(c *gin.Context) {
		if CurrentUsername(c) != "current-name" {
			c.Status(http.StatusInternalServerError)
			return
		}
		c.Status(http.StatusNoContent)
	})

	financeRequest := httptest.NewRequest(http.MethodGet, "/finance", nil)
	financeRequest.Header.Set("Authorization", "Bearer "+token)
	financeResponse := httptest.NewRecorder()
	router.ServeHTTP(financeResponse, financeRequest)
	if financeResponse.Code != http.StatusForbidden {
		t.Fatalf("removed JWT permission status = %d body=%s", financeResponse.Code, financeResponse.Body.String())
	}

	operationsRequest := httptest.NewRequest(http.MethodGet, "/operations", nil)
	operationsRequest.Header.Set("Authorization", "Bearer "+token)
	operationsResponse := httptest.NewRecorder()
	router.ServeHTTP(operationsResponse, operationsRequest)
	if operationsResponse.Code != http.StatusNoContent {
		t.Fatalf("current database permission status = %d body=%s", operationsResponse.Code, operationsResponse.Body.String())
	}
}

func TestAuthRequiredFailsClosedWhenAuthorizationCannotBeResolved(t *testing.T) {
	gin.SetMode(gin.TestMode)
	auth := service.NewAuthService("authorization-failure-test-secret", time.Hour)
	token, _, err := auth.GenerateAccessToken(7, "admin", []string{"role:manage"})
	if err != nil {
		t.Fatalf("generate access token: %v", err)
	}

	router := gin.New()
	router.Use(AuthRequired(auth, fakeAuthorizationStore{err: errors.New("database unavailable")}))
	router.GET("/protected", func(c *gin.Context) { c.Status(http.StatusNoContent) })
	request := httptest.NewRequest(http.MethodGet, "/protected", nil)
	request.Header.Set("Authorization", "Bearer "+token)
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)
	if response.Code != http.StatusInternalServerError {
		t.Fatalf("authorization database failure status = %d body=%s", response.Code, response.Body.String())
	}
}
