package middleware_test

// T119 HTTP middleware cases: AU-05 / AU-06 / AU-12 / AU-13 / AD-13

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"apps/backend/internal/middleware"
	"apps/backend/internal/module/member/domain"
	tokenUC "apps/backend/internal/module/token/usecase"
	"apps/backend/internal/response"

	"github.com/stretchr/testify/require"
)

// mwRepo is a minimal Repository for AuthMiddleware tests.
type mwRepo struct {
	byUID map[int64]*domain.Member
}

func (m *mwRepo) FindByUID(_ context.Context, uid int64) (*domain.Member, error) {
	x, ok := m.byUID[uid]
	if !ok {
		return nil, domain.ErrNotFound
	}
	cp := *x
	return &cp, nil
}

func (m *mwRepo) NextUID(context.Context) (int64, error)             { return 0, nil }
func (m *mwRepo) CreateMember(context.Context, *domain.Member) error { return nil }
func (m *mwRepo) FindByEmail(context.Context, string) (*domain.Member, error) {
	return nil, domain.ErrNotFound
}
func (m *mwRepo) FindByPhone(context.Context, string) (*domain.Member, error) {
	return nil, domain.ErrNotFound
}
func (m *mwRepo) FindByInviteCode(context.Context, string) (*domain.Member, error) {
	return nil, domain.ErrNotFound
}
func (m *mwRepo) UpdateMember(context.Context, *domain.Member) error { return nil }
func (m *mwRepo) ListPage(context.Context, int, int, string, string) ([]*domain.Member, int64, error) {
	return nil, 0, nil
}
func (m *mwRepo) ListAllMembers(context.Context) ([]*domain.Member, error) { return nil, nil }
func (m *mwRepo) CountActiveAdmins(context.Context) (int64, error)         { return 0, nil }
func (m *mwRepo) CreateIdentity(context.Context, *domain.Identity) error {
	return nil
}
func (m *mwRepo) FindIdentity(context.Context, string, string) (*domain.Identity, error) {
	return nil, domain.ErrIdentityNotFound
}
func (m *mwRepo) ListIdentitiesByUID(context.Context, int64) ([]*domain.Identity, error) {
	return nil, nil
}
func (m *mwRepo) DeleteIdentity(context.Context, string, string) error { return nil }
func (m *mwRepo) UpdateIdentityPassword(context.Context, string, string, string) error {
	return nil
}
func (m *mwRepo) SetCode(string, string, string, time.Duration) bool { return true }
func (m *mwRepo) CheckCode(string, string, string) bool              { return false }
func (m *mwRepo) PeekCode(string, string, string) bool               { return false }
func (m *mwRepo) SaveRefresh(string, int64, time.Time)               {}
func (m *mwRepo) ConsumeRefresh(string) (int64, bool)                { return 0, false }
func (m *mwRepo) RevokeRefresh(string)                               {}
func (m *mwRepo) RevokeAllRefreshByUID(context.Context, int64) error {
	return nil
}
func (m *mwRepo) GetSettings(context.Context, int64) (*domain.UserSettings, error) {
	return nil, nil
}
func (m *mwRepo) SaveSettings(context.Context, int64, *domain.UserSettings) error {
	return nil
}

func decodeEnv(t *testing.T, rr *httptest.ResponseRecorder) response.Envelope {
	t.Helper()
	var env response.Envelope
	require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &env))
	return env
}

func TestAU_05_MeMissingAuthorization(t *testing.T) {
	issuer := tokenUC.NewJWTIssuer("mw-a", "mw-r", 3600, 86400)
	repo := &mwRepo{byUID: map[int64]*domain.Member{}}
	h := middleware.AuthMiddleware(issuer, repo)(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(`{"code":102000}`))
	})
	rr := httptest.NewRecorder()
	h.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/api/v1/auth/me", nil))
	require.Equal(t, http.StatusUnauthorized, rr.Code)
	env := decodeEnv(t, rr)
	require.NotEqual(t, 102000, env.Code)
	require.NotEmpty(t, env.Message)
}

func TestAU_06_MeExpiredAccess(t *testing.T) {
	// access expire 1 second — issue then sleep
	issuer := tokenUC.NewJWTIssuer("mw-a2", "mw-r2", 1, 86400)
	uid := int64(1_000_100)
	repo := &mwRepo{byUID: map[int64]*domain.Member{
		uid: {UID: uid, Email: "exp@test.local", Roles: []string{domain.RoleMember}, Status: domain.StatusActive},
	}}
	pair, _, _, err := issuer.Issue(uid, []string{domain.RoleMember})
	require.NoError(t, err)
	time.Sleep(1100 * time.Millisecond)
	h := middleware.AuthMiddleware(issuer, repo)(func(w http.ResponseWriter, r *http.Request) {
		t.Fatal("should not reach handler")
	})
	req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/me", nil)
	req.Header.Set("Authorization", "Bearer "+pair.AccessToken)
	rr := httptest.NewRecorder()
	h.ServeHTTP(rr, req)
	require.Equal(t, http.StatusUnauthorized, rr.Code)
	env := decodeEnv(t, rr)
	require.NotEqual(t, 102000, env.Code)
}

func TestAU_12_NonAdminForbidden(t *testing.T) {
	issuer := tokenUC.NewJWTIssuer("mw-a3", "mw-r3", 3600, 86400)
	uid := int64(1_000_101)
	repo := &mwRepo{byUID: map[int64]*domain.Member{
		uid: {UID: uid, Email: "mem@test.local", Roles: []string{domain.RoleMember}, Status: domain.StatusActive},
	}}
	pair, _, _, err := issuer.Issue(uid, []string{domain.RoleMember})
	require.NoError(t, err)
	inner := middleware.AdminMiddleware(func(w http.ResponseWriter, r *http.Request) {
		t.Fatal("member must not pass admin middleware")
	})
	h := middleware.AuthMiddleware(issuer, repo)(inner)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/members", nil)
	req.Header.Set("Authorization", "Bearer "+pair.AccessToken)
	rr := httptest.NewRecorder()
	h.ServeHTTP(rr, req)
	require.Equal(t, http.StatusForbidden, rr.Code)
	env := decodeEnv(t, rr)
	require.EqualValues(t, 403002, env.Code)
}

func TestAU_13_AdminAllowed(t *testing.T) {
	issuer := tokenUC.NewJWTIssuer("mw-a4", "mw-r4", 3600, 86400)
	uid := int64(1_000_102)
	repo := &mwRepo{byUID: map[int64]*domain.Member{
		uid: {UID: uid, Email: "adm@test.local", Roles: []string{domain.RoleAdmin, domain.RoleMember}, Status: domain.StatusActive},
	}}
	pair, _, _, err := issuer.Issue(uid, []string{domain.RoleAdmin, domain.RoleMember})
	require.NoError(t, err)
	called := false
	inner := middleware.AdminMiddleware(func(w http.ResponseWriter, r *http.Request) {
		called = true
		response.OK(w, map[string]bool{"ok": true})
	})
	h := middleware.AuthMiddleware(issuer, repo)(inner)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/members", nil)
	req.Header.Set("Authorization", "Bearer "+pair.AccessToken)
	rr := httptest.NewRecorder()
	h.ServeHTTP(rr, req)
	require.True(t, called)
	require.Equal(t, http.StatusOK, rr.Code)
	env := decodeEnv(t, rr)
	require.EqualValues(t, 102000, env.Code)
}

func TestAD_13_MemberCreateMemberForbidden(t *testing.T) {
	// Same stack as AU-12: member hits admin route → 403002
	TestAU_12_NonAdminForbidden(t)
}
