package interceptor

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

	"bindbox-game/internal/pkg/core"
	"bindbox-game/internal/pkg/jwtoken"
	"bindbox-game/internal/pkg/logger"
	"bindbox-game/internal/proposal"
	"bindbox-game/internal/repository/mysql"
	"bindbox-game/internal/repository/mysql/model"
)

func setupDB(t *testing.T) mysql.Repo {
	repo, err := mysql.NewSQLiteRepoForTest()
	if err != nil {
		t.Fatal(err)
	}
	if err := repo.GetDbR().AutoMigrate(&model.Admin{}, &model.RoleUsers{}, &model.RoleActions{}, &model.MenuActions{}); err != nil {
		t.Fatal(err)
	}
	return repo
}

func newLogger(t *testing.T) logger.CustomLogger {
	l, err := logger.NewCustomLogger(nil, logger.WithOutputInConsole())
	if err != nil {
		t.Fatal(err)
	}
	return l
}

func signToken(secret string, info proposal.SessionUserInfo) string {
	token, _ := jwtoken.New(secret).Sign(info, time.Hour)
	return token
}

func TestRequireAdminRole_NoToken(t *testing.T) {
	repo := setupDB(t)
	l := newLogger(t)
	intc := New(l, repo)
	m, err := core.New(l)
	if err != nil {
		t.Fatal(err)
	}
	g := m.Group("/api/admin", core.WrapAuthHandler(intc.AdminTokenAuthVerify))
	g.GET("/ping", intc.RequireAdminRole(), func(ctx core.Context) { ctx.Payload(map[string]string{"message": "ok"}) })
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/api/admin/ping", nil)
	m.ServeHTTP(w, req)
	if w.Code != http.StatusUnauthorized {
		t.Fatalf("expect 401 got %d", w.Code)
	}
}

func TestRequireAdminRole_NoRole(t *testing.T) {
	repo := setupDB(t)
	l := newLogger(t)
	intc := New(l, repo)
	secret := "testsecret"
	os.Setenv("ADMIN_JWT_SECRET", secret)
	info := proposal.SessionUserInfo{Id: 1, UserName: "u", IsSuper: 0}
	token := signToken(secret, info)
	h := token
	if err := repo.GetDbR().Create(&model.Admin{ID: 1, Username: "u", Nickname: "n", Avatar: "a", Mobile: "m", Password: "p", IsSuper: 0, LoginStatus: 1, LastLoginTime: time.Now(), LastLoginIP: "0.0.0.0", LastLoginHash: h, CreatedUser: "t", UpdatedUser: "t"}).Error; err != nil {
		t.Fatal(err)
	}
	m, err := core.New(l)
	if err != nil {
		t.Fatal(err)
	}
	g := m.Group("/api/admin", core.WrapAuthHandler(intc.AdminTokenAuthVerify))
	g.GET("/ping", intc.RequireAdminRole(), func(ctx core.Context) { ctx.Payload(map[string]string{"message": "ok"}) })
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/api/admin/ping", nil)
	req.Header.Set("Authorization", token)
	m.ServeHTTP(w, req)
	if w.Code != http.StatusForbidden {
		t.Fatalf("expect 403 got %d", w.Code)
	}
}

func TestRequireAdminAction_AllowedByRole(t *testing.T) {
	repo := setupDB(t)
	l := newLogger(t)
	intc := New(l, repo)
	secret := "testsecret"
	os.Setenv("ADMIN_JWT_SECRET", secret)
	info := proposal.SessionUserInfo{Id: 2, UserName: "u2", IsSuper: 0}
	token := signToken(secret, info)
	h := token
	if err := repo.GetDbR().Create(&model.Admin{ID: 2, Username: "u2", Nickname: "n", Avatar: "a", Mobile: "m", Password: "p", IsSuper: 0, LoginStatus: 1, LastLoginTime: time.Now(), LastLoginIP: "0.0.0.0", LastLoginHash: h, CreatedUser: "t", UpdatedUser: "t"}).Error; err != nil {
		t.Fatal(err)
	}
	if err := repo.GetDbR().Create(&model.RoleUsers{RoleID: 10, AdminID: 2, CreatedUser: "t", UpdatedUser: "t"}).Error; err != nil {
		t.Fatal(err)
	}
	if err := repo.GetDbR().Create(&model.MenuActions{ID: 100, MenuID: 1, ActionMark: "guild:create", ActionName: "create", Status: true, CreatedUser: "t", UpdatedUser: "t"}).Error; err != nil {
		t.Fatal(err)
	}
	if err := repo.GetDbR().Create(&model.RoleActions{RoleID: 10, ActionID: 100, CreatedUser: "t", UpdatedUser: "t"}).Error; err != nil {
		t.Fatal(err)
	}
	m, err := core.New(l)
	if err != nil {
		t.Fatal(err)
	}
	g := m.Group("/api/admin", core.WrapAuthHandler(intc.AdminTokenAuthVerify))
	g.POST("/guilds", intc.RequireAdminRole(), intc.RequireAdminAction("guild:create"), func(ctx core.Context) { ctx.Payload(map[string]string{"message": "ok"}) })
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("POST", "/api/admin/guilds", nil)
	req.Header.Set("Authorization", token)
	m.ServeHTTP(w, req)
	if w.Code != http.StatusOK {
		t.Fatalf("expect 200 got %d", w.Code)
	}
}

func TestRequireAdminAction_Forbidden(t *testing.T) {
	repo := setupDB(t)
	l := newLogger(t)
	intc := New(l, repo)
	secret := "testsecret"
	os.Setenv("ADMIN_JWT_SECRET", secret)
	info := proposal.SessionUserInfo{Id: 3, UserName: "u3", IsSuper: 0}
	token := signToken(secret, info)
	h := token
	if err := repo.GetDbR().Create(&model.Admin{ID: 3, Username: "u3", Nickname: "n", Avatar: "a", Mobile: "m", Password: "p", IsSuper: 0, LoginStatus: 1, LastLoginTime: time.Now(), LastLoginIP: "0.0.0.0", LastLoginHash: h, CreatedUser: "t", UpdatedUser: "t"}).Error; err != nil {
		t.Fatal(err)
	}
	if err := repo.GetDbR().Create(&model.RoleUsers{RoleID: 11, AdminID: 3, CreatedUser: "t", UpdatedUser: "t"}).Error; err != nil {
		t.Fatal(err)
	}
	if err := repo.GetDbR().Create(&model.MenuActions{ID: 101, MenuID: 1, ActionMark: "guild:delete", ActionName: "delete", Status: true, CreatedUser: "t", UpdatedUser: "t"}).Error; err != nil {
		t.Fatal(err)
	}
	m, err := core.New(l)
	if err != nil {
		t.Fatal(err)
	}
	g := m.Group("/api/admin", core.WrapAuthHandler(intc.AdminTokenAuthVerify))
	g.DELETE("/guilds/1", intc.RequireAdminRole(), intc.RequireAdminAction("guild:delete"), func(ctx core.Context) { ctx.Payload(map[string]string{"message": "ok"}) })
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("DELETE", "/api/admin/guilds/1", nil)
	req.Header.Set("Authorization", token)
	m.ServeHTTP(w, req)
	if w.Code != http.StatusForbidden {
		t.Fatalf("expect 403 got %d", w.Code)
	}
}
