package api

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

	"github.com/tidbcloud/account/internel/svc/token"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/errors"
	"github.com/shopspring/decimal"
	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/account/admin/forms"
	"github.com/tidbcloud/account/admin/models"
	"github.com/tidbcloud/account/commons/database"
	account_proto "github.com/tidbcloud/account/idl/pbgen/proto/account"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/account/internel/svc/admin/grafana"
	tenantmodel "github.com/tidbcloud/account/internel/svc/org/tenantctx"
	"github.com/tidbcloud/account/internel/svc/rbac"
	"github.com/tidbcloud/account/thirdparty/account"
	"github.com/tidbcloud/account/thirdparty/billing2"
	"github.com/tidbcloud/account/thirdparty/salesforce"
	panicIf "github.com/tidbcloud/pkgv2/panicif"
	"github.com/tidbcloud/pkgv2/secret/encrypt"
	"github.com/tidbcloud/pkgv2/zendesk"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"gopkg.in/auth0.v4/management"
	"gorm.io/gorm"
)

var (
	router *gin.Engine
)

func genURL(tpl string, args ...interface{}) string {
	return fmt.Sprintf("/api/v1/admin%s", fmt.Sprintf(tpl, args...))
}

func initFakeData() error {
	var err error

	// init fake role
	fakeRole = &entity.Role{
		Name:     "UnitTest Role",
		TenantID: entity.PingCAPTenantID,
		Privileges: entity.Privileges{
			Privileges: []entity.Privilege{
				{
					Resource:  rbac.AllResource,
					Operation: rbac.AllOperation,
				},
			},
		},
	}
	if err := database.Create(fakeRole); err != nil {
		return err
	}

	// init fake user
	dbUser := &entity.User{
		FirstName: "FirstName",
		LastName:  "LastName",
		Email:     "Email",
		Status:    entity.UserStatusActive,
	}
	dbUser.ID = entity.ID(0)
	dbUser.Roles = append(dbUser.Roles, *fakeRole)
	if err = database.Create(dbUser); err != nil {
		return err
	}
	fakeUser = "unittest@pingcap.com"

	return nil
}

func revokeFakeData() {
	err := database.Transaction(func(tx *gorm.DB) error {
		return tx.Delete(fakeRole).Error
	})

	if err != nil {
		tilog.Info(context.Background(), "failed to revoke fake data", zap.Error(err))
	}
}

func TestSuccessOrFail(t *testing.T) {
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/ping", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, 200, w.Code)

	w = httptest.NewRecorder()
	req, _ = http.NewRequest("GET", "/ping?shouldFail=true", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, 401, w.Code)
}

func TestPagination(t *testing.T) {
	s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	ctx := context.Background()

	limitOffset := forms.LimitAndOffset{Limit: 10, Offset: 0}
	paged := Pagination(ctx, s, limitOffset)
	pagedSlice := paged.([]int)
	assert.Equal(t, 10, len(pagedSlice))
	assert.Equal(t, 0, pagedSlice[0])
	assert.Equal(t, 9, pagedSlice[len(pagedSlice)-1])

	limitOffset = forms.LimitAndOffset{Limit: 3, Offset: 10}
	paged = Pagination(ctx, s, limitOffset)
	pagedSlice = paged.([]int)
	assert.Equal(t, 1, len(pagedSlice))
	assert.Equal(t, 10, pagedSlice[0])

	limitOffset = forms.LimitAndOffset{Limit: 3, Offset: 20}
	paged = Pagination(ctx, s, limitOffset)
	pagedSlice = paged.([]int)
	assert.Equalf(t, 0, len(pagedSlice), fmt.Sprintf("%+v", pagedSlice))

	// test the bad case
	paged = Pagination(ctx, 1, limitOffset)
	result := paged.(int)
	assert.Equal(t, 1, result)
}

type fakeGrafanaCollector struct{}

func (f fakeGrafanaCollector) Search(ctx context.Context, params ...grafana.SearchParam) ([]grafana.FoundBoard, error) {
	panic("implement me")
}

func newTokenSession() *token.Session {
	aesCrypt, err := encrypt.NewAESCrypt(make([]byte, 16))
	if err != nil {
		panicIf.Error(errors.Trace(err))
	}
	session := token.NewSession(aesCrypt)
	return session
}

type MockAuth0UserManagementHandler struct {
	tenantmodel.Auth0UserManagementHandler
}

func (*MockAuth0UserManagementHandler) ListByEmail(email string, opts ...management.ListOption) ([]*management.User, error) {
	return []*management.User{}, nil
}

type MockZendeskHandler struct{}

func (*MockZendeskHandler) CreateOrUpdateOrganization(input zendesk.Organization) (zendesk.Organization, error) {
	return input, nil
}

func (*MockZendeskHandler) CreateOrganization(input zendesk.Organization) (zendesk.Organization, error) {
	return input, nil
}

type MockSalesforceHandler struct {
}

func (m *MockSalesforceHandler) UpdateAccount(ctx context.Context, account salesforce.Account) error {
	return nil
}

func (m *MockSalesforceHandler) SyncLeads(ctx context.Context, lead salesforce.Lead) error {
	return nil
}

func (m *MockSalesforceHandler) SyncPocApplication(ctx context.Context, pocApplication salesforce.PocApplication) error {
	return nil
}

func (m *MockSalesforceHandler) SyncPOCCredits(context.Context, salesforce.POCCredits) error {
	return nil
}

type FakeBillingHandler struct {
	billing2.BillingHandler
}

func (*FakeBillingHandler) GetTenantPlanInfo(ctx context.Context, tenantID uint64) (*billing2.TenantPlanInfo, error) {
	return &billing2.TenantPlanInfo{
		POCCredits:        decimal.Zero,
		EffectiveDiscount: decimal.Zero,
		EffectivePlan:     "on_demand",
		DevTierPlanInfo: &billing2.PlanInfo{
			Status: "active",
		},
	}, nil
}

func (*FakeBillingHandler) GetMonthlyBills(ctx context.Context, tenantID uint64, year int, month time.Month) (*billing2.CostExplorerResult, error) {
	return &billing2.CostExplorerResult{}, nil
}

func TestMain(m *testing.M) {
	ctx := context.Background()
	tilog.Info(ctx, "start to running test cases...")

	// set isUnitTest to true
	isUnitTest = true
	models.InitMySQL(isUnitTest)
	if err := initFakeData(); err != nil {
		tilog.Fatal(ctx, "failed to init data", zap.Error(err))
	}
	defer revokeFakeData()

	grafanaClient := &fakeGrafanaCollector{}
	salesforceClient := &MockSalesforceHandler{}
	auth0Client := &MockAuth0UserManagementHandler{}
	zendeskClient := &MockZendeskHandler{}

	billing2.SetHandler(&FakeBillingHandler{})

	// init fake runtime manager/billing/mail service...etc.
	if err := models.InitFakeAdminModel(tilog.WithContextL(ctx),
		grafanaClient,
		salesforceClient,
		newTokenSession(),
		auth0Client,
		zendeskClient,
		&FakeAccountAPIHandler{},
	); err != nil {
		tilog.Fatal(ctx, "failed to init fake clients for admin model", zap.Error(err))
	}

	router = InitRouter()

	os.Exit(m.Run())
}

var _ account.AccountAPIHandler = &FakeAccountAPIHandler{}

type FakeAccountAPIHandler struct {
}

func (h *FakeAccountAPIHandler) GetUserByEmail(ctx context.Context, req *account_proto.GetUserByEmailRequest) (*account_proto.GetUserByEmailResponse, error) {
	return &account_proto.GetUserByEmailResponse{
		User: &account_proto.User{
			Id:    1,
			Email: req.Email,
		},
	}, nil
}

func (h *FakeAccountAPIHandler) GetUserByID(ctx context.Context, req *account_proto.GetUserByIDRequest) (*account_proto.GetUserByIDResponse, error) {
	return &account_proto.GetUserByIDResponse{}, nil
}

func (h *FakeAccountAPIHandler) GetAuthUser(ctx context.Context, req *account_proto.GetAuthUserRequest) (*account_proto.GetAuthUserResponse, error) {
	return &account_proto.GetAuthUserResponse{}, nil
}

func (h *FakeAccountAPIHandler) SendEmail(ctx context.Context, req *account_proto.SendEmailReq) (*account_proto.SendEmailResp, error) {
	return &account_proto.SendEmailResp{}, nil
}

func (h *FakeAccountAPIHandler) SendEmailByLabels(ctx context.Context, req *account_proto.SendEmailByLabelsReq) (*account_proto.SendEmailByLabelsResp, error) {
	return &account_proto.SendEmailByLabelsResp{}, nil
}

func (h *FakeAccountAPIHandler) HoldEmailUniqueID(ctx context.Context, req *account_proto.HoldEmailUniqueIDReq) (*account_proto.HoldEmailUniqueIDResp, error) {
	return &account_proto.HoldEmailUniqueIDResp{}, nil
}

func (h *FakeAccountAPIHandler) ClearEmailUniqueID(ctx context.Context, req *account_proto.ClearEmailUniqueIDReq) (*account_proto.ClearEmailUniqueIDResp, error) {
	return &account_proto.ClearEmailUniqueIDResp{}, nil
}

func (h *FakeAccountAPIHandler) QueryEmailUniqueID(ctx context.Context, req *account_proto.QueryEmailUniqueIDReq) (*account_proto.QueryEmailUniqueIDResp, error) {
	return &account_proto.QueryEmailUniqueIDResp{}, nil
}

func (h *FakeAccountAPIHandler) GetApiKeyByAccessKey(ctx context.Context, req *account_proto.GetApiKeyByAccessKeyReq) (*account_proto.GetApiKeyByAccessKeyRsp, error) {
	return &account_proto.GetApiKeyByAccessKeyRsp{}, nil
}

func (h *FakeAccountAPIHandler) AddImpersonateUser(ctx context.Context, in *account_proto.AddImpersonateUserReq) (*account_proto.AddImpersonateUserRsp, error) {
	return &account_proto.AddImpersonateUserRsp{}, nil
}

func (h *FakeAccountAPIHandler) DeleteImpersonateUser(ctx context.Context, in *account_proto.DeleteImpersonateUserReq) (*account_proto.DeleteImpersonateUserRsp, error) {
	return &account_proto.DeleteImpersonateUserRsp{}, nil
}

func (h *FakeAccountAPIHandler) CreateApiKey(ctx context.Context, in *account_proto.CreateApiKeyReq) (*account_proto.CreateApiKeyRsp, error) {
	return &account_proto.CreateApiKeyRsp{}, nil
}

func (h *FakeAccountAPIHandler) GetApiKeysByResource(ctx context.Context, in *account_proto.GetApiKeysByResourceReq) (*account_proto.GetApiKeysByResourceRsp, error) {
	return &account_proto.GetApiKeysByResourceRsp{}, nil
}

func (h *FakeAccountAPIHandler) ListHitFeatureFlagNames(ctx context.Context, in *account_proto.ListHitFeatureFlagNamesReq) (*account_proto.ListHitFeatureFlagNamesRsp, error) {
	return &account_proto.ListHitFeatureFlagNamesRsp{}, nil
}
