package middleware

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

	"github.com/gofiber/fiber/v2"
	"github.com/golang-jwt/jwt/v5"
	"gitlab.com/mbugroup/lti-api.git/internal/apikeys"
	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	sso "gitlab.com/mbugroup/lti-api.git/internal/modules/sso/verifier"
	userValidation "gitlab.com/mbugroup/lti-api.git/internal/modules/users/validations"
)

type stubUserService struct {
	user *entity.User
	err  error
}

func (s *stubUserService) GetAll(_ *fiber.Ctx, _ *userValidation.Query) ([]entity.User, int64, error) {
	return nil, 0, nil
}

func (s *stubUserService) GetOne(_ *fiber.Ctx, _ uint) (*entity.User, error) {
	return s.user, s.err
}

func (s *stubUserService) CreateOne(_ *fiber.Ctx, _ *userValidation.Create) (*entity.User, error) {
	return nil, nil
}

func (s *stubUserService) UpdateOne(_ *fiber.Ctx, _ *userValidation.Update, _ uint) (*entity.User, error) {
	return nil, nil
}

func (s *stubUserService) DeleteOne(_ *fiber.Ctx, _ uint) error {
	return nil
}

func (s *stubUserService) GetBySSOUserID(_ *fiber.Ctx, _ uint) (*entity.User, error) {
	return s.user, s.err
}

type stubAPIKeyAuthenticator struct {
	principal *apikeys.Principal
	err       error
}

func (s *stubAPIKeyAuthenticator) Authenticate(_ context.Context, _ string, _ string) (*apikeys.Principal, error) {
	return s.principal, s.err
}

func TestAuthAllowsAPIKeyOnGet(t *testing.T) {
	SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{
		principal: &apikeys.Principal{
			Name:        "dashboard",
			Permissions: []string{"perm.read"},
			LocationIDs: []uint{3, 5},
		},
	})
	defer SetAPIKeyAuthenticator(nil)

	app := fiber.New()
	app.Get("/reports", Auth(&stubUserService{}), RequirePermissions("perm.read"), func(c *fiber.Ctx) error {
		scope, err := ResolveLocationScope(c, nil)
		if err != nil {
			return err
		}
		return c.JSON(fiber.Map{
			"principal": c.Locals(authContextLocalsKey).(*AuthContext).PrincipalType,
			"restrict":  scope.Restrict,
			"ids":       scope.IDs,
		})
	})

	req := httptest.NewRequest(fiber.MethodGet, "/reports", nil)
	req.Header.Set("X-API-Key", "lti_dev_prefix_secret")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.StatusCode != fiber.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}
}

func TestAuthRejectsAPIKeyOnPost(t *testing.T) {
	SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{
		principal: &apikeys.Principal{
			Name:        "dashboard",
			Permissions: []string{"perm.write"},
		},
	})
	defer SetAPIKeyAuthenticator(nil)

	app := fiber.New()
	app.Post("/reports", Auth(&stubUserService{}), RequirePermissions("perm.write"), func(c *fiber.Ctx) error {
		return c.SendStatus(fiber.StatusOK)
	})

	req := httptest.NewRequest(fiber.MethodPost, "/reports", nil)
	req.Header.Set("X-API-Key", "lti_dev_prefix_secret")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.StatusCode != fiber.StatusUnauthorized {
		t.Fatalf("expected 401, got %d", resp.StatusCode)
	}
}

func TestAuthRejectsInvalidAPIKey(t *testing.T) {
	SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{err: apikeys.ErrInvalidAPIKey})
	defer SetAPIKeyAuthenticator(nil)

	app := fiber.New()
	app.Get("/reports", Auth(&stubUserService{}), func(c *fiber.Ctx) error {
		return c.SendStatus(fiber.StatusOK)
	})

	req := httptest.NewRequest(fiber.MethodGet, "/reports", nil)
	req.Header.Set("X-API-Key", "lti_dev_prefix_secret")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.StatusCode != fiber.StatusUnauthorized {
		t.Fatalf("expected 401, got %d", resp.StatusCode)
	}
}

func TestAuthRejectsInactiveAPIKey(t *testing.T) {
	SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{err: apikeys.ErrInactiveKey})
	defer SetAPIKeyAuthenticator(nil)

	app := fiber.New()
	app.Get("/reports", Auth(&stubUserService{}), func(c *fiber.Ctx) error {
		return c.SendStatus(fiber.StatusOK)
	})

	req := httptest.NewRequest(fiber.MethodGet, "/reports", nil)
	req.Header.Set("X-API-Key", "lti_dev_prefix_secret")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.StatusCode != fiber.StatusUnauthorized {
		t.Fatalf("expected 401, got %d", resp.StatusCode)
	}
}

func TestAuthRejectsMissingPermission(t *testing.T) {
	SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{
		principal: &apikeys.Principal{
			Name:        "dashboard",
			Permissions: []string{"perm.other"},
		},
	})
	defer SetAPIKeyAuthenticator(nil)

	app := fiber.New()
	app.Get("/reports", Auth(&stubUserService{}), RequirePermissions("perm.read"), func(c *fiber.Ctx) error {
		return c.SendStatus(fiber.StatusOK)
	})

	req := httptest.NewRequest(fiber.MethodGet, "/reports", nil)
	req.Header.Set("X-API-Key", "lti_dev_prefix_secret")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.StatusCode != fiber.StatusForbidden {
		t.Fatalf("expected 403, got %d", resp.StatusCode)
	}
}

func TestAuthAllowsBearerOnGet(t *testing.T) {
	previousVerify := verifyAccessTokenFunc
	previousProfile := fetchProfileFunc
	defer func() {
		verifyAccessTokenFunc = previousVerify
		fetchProfileFunc = previousProfile
	}()

	verifyAccessTokenFunc = func(_ string) (*sso.VerificationResult, error) {
		return &sso.VerificationResult{
			UserID: 1,
			Claims: &sso.AccessTokenClaims{
				RegisteredClaims: jwt.RegisteredClaims{
					IssuedAt: jwt.NewNumericDate(time.Now().UTC()),
				},
			},
		}, nil
	}
	fetchProfileFunc = func(_ context.Context, _ string, _ *sso.VerificationResult) (*sso.UserProfile, error) {
		return &sso.UserProfile{
			Permissions: []sso.Permission{{Name: "perm.read"}},
			LocationIDs: []uint{7},
		}, nil
	}

	app := fiber.New()
	app.Get("/reports", Auth(&stubUserService{user: &entity.User{Id: 9, Name: "API User"}}), RequirePermissions("perm.read"), func(c *fiber.Ctx) error {
		return c.JSON(fiber.Map{"principal": c.Locals(authContextLocalsKey).(*AuthContext).PrincipalType})
	})

	req := httptest.NewRequest(fiber.MethodGet, "/reports", nil)
	req.Header.Set("Authorization", "Bearer test-token")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.StatusCode != fiber.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}
}

func TestAuthReturnsServerErrorWhenAPIKeyVerifierFailsUnexpectedly(t *testing.T) {
	SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{err: errors.New("boom")})
	defer SetAPIKeyAuthenticator(nil)

	app := fiber.New()
	app.Get("/reports", Auth(&stubUserService{}), func(c *fiber.Ctx) error {
		return c.SendStatus(fiber.StatusOK)
	})

	req := httptest.NewRequest(fiber.MethodGet, "/reports", nil)
	req.Header.Set("X-API-Key", "lti_dev_prefix_secret")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.StatusCode != fiber.StatusInternalServerError {
		t.Fatalf("expected 500, got %d", resp.StatusCode)
	}
}
