package api

import (
	"math/rand"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/pingcap/dbaas/admin/forms"
	"github.com/pingcap/dbaas/central/db"
	"github.com/pingcap/dbaas/central/db/entity"
	"github.com/pingcap/log"
	"github.com/stretchr/testify/assert"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

func createTenant(t *testing.T) *entity.Tenant {
	tenant := entity.Tenant{
		Name:          "unittest tenant",
		Email:         "unittest@pingcap.com",
		Country:       "CN",
		TimeZone:      "8",
		Status:        entity.TenantStatusActive,
		Plan:          entity.TenantPlanFreeTrial,
		AcceptService: false,
		ZendeskID:     0,
		Quotas:        nil,
		BillingEmail:  "",
	}
	if err := db.Create(&tenant); err != nil {
		t.Fatalf("failed to create tenant for test")
	}

	return &tenant
}

func deleteTenant(tenant *entity.Tenant) {
	if err := db.Delete(tenant, "id=?", tenant.ID); err != nil {
		log.Info("failed to delete tenant", zap.Error(err))
	}
}

func queryTenant(tenantID entity.ID) *entity.Tenant {
	var tenant entity.Tenant

	err := db.Transaction(func(tx *gorm.DB) error {
		return tx.Find(&tenant, "id=?", tenantID).Error
	})
	if err != nil {
		log.Info("failed to query tenant", zap.Error(err))
	}

	return &tenant
}

func createTenantUser(tenant *entity.Tenant) *entity.User {
	user := entity.User{
		TenantID:    tenant.ID,
		FirstName:   "Test",
		LastName:    "Unit",
		Email:       "unittest@pingcap.com",
		Status:      entity.UserStatusActive,
		InvitedAt:   nil,
		Job:         "",
		PhoneNumber: "",
		Roles:       nil,
		Role:        "member",
	}

	if err := db.Create(&user); err != nil {
		log.Info("failed to create user", zap.Error(err))
	}

	return &user
}

func deleteTenantUser(user *entity.User) {
	if err := db.Delete(user, "id = ?", user.ID); err != nil {
		log.Info("failed to delete user", zap.Error(err))
	}
}

func queryTenantUser(userID entity.ID) *entity.User {
	user := entity.User{}

	err := db.Transaction(func(tx *gorm.DB) error {
		return tx.First(&user, "id = ?", userID).Error
	})
	if err != nil {
		log.Info("failed to query tenant user", zap.Error(err))
	}

	return &user
}

func TestListTenants(t *testing.T) {
	tenant := createTenant(t)
	defer deleteTenant(tenant)
	tenantUser := createTenantUser(tenant)
	defer deleteTenantUser(tenantUser)

	w := httptest.NewRecorder()
	url := genURL("/orgs")

	req, _ := http.NewRequest("GET", url, nil)
	router.ServeHTTP(w, req)

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

func TestListTenantsBrief(t *testing.T) {
	tenant := createTenant(t)
	defer deleteTenant(tenant)
	tenantUser := createTenantUser(tenant)
	defer deleteTenantUser(tenantUser)

	w := httptest.NewRecorder()
	url := genURL("/orgs?brief=true&no-limit=true")

	req, _ := http.NewRequest("GET", url, nil)
	router.ServeHTTP(w, req)

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

func TestGetTenantById(t *testing.T) {
	tenant := createTenant(t)
	defer deleteTenant(tenant)

	// should found
	url := genURL("/orgs/%d", tenant.ID)
	w := httptest.NewRecorder()

	req, _ := http.NewRequest("GET", url, nil)
	router.ServeHTTP(w, req)

	assert.Equalf(t, 200, w.Code, w.Body.String())

	// should not found
	url = genURL("/orgs/%d", int(tenant.ID)+rand.Int())
	w = httptest.NewRecorder()

	req, _ = http.NewRequest("GET", url, nil)
	router.ServeHTTP(w, req)

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

func TestDeleteTenant(t *testing.T) {
	tenant := createTenant(t)
	defer deleteTenant(tenant)

	url := genURL("/orgs/%d", tenant.ID)
	w := httptest.NewRecorder()

	req, _ := http.NewRequest("DELETE", url, nil)
	router.ServeHTTP(w, req)

	assert.Equalf(t, 200, w.Code, w.Body.String())

	tenant = queryTenant(tenant.ID)
	assert.NotNil(t, tenant)
	assert.Equal(t, entity.TenantStatusDeleted, tenant.Status)
}

func TestListTenantUsers(t *testing.T) {
	tenant := createTenant(t)
	defer deleteTenant(tenant)

	url := genURL("/orgs/%d/users?page=1&per_page=200", tenant.ID)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", url, nil)
	router.ServeHTTP(w, req)

	assert.Equalf(t, 200, w.Code, w.Body.String())
}

func TestAddTenantUser(t *testing.T) {
	tenant := createTenant(t)
	defer deleteTenant(tenant)
	role := createRole(tenant.ID)
	defer deleteRole(role)

	url := genURL("/orgs/%d/users", tenant.ID)
	form := forms.TenantUser{
		OrgID:     uint64(tenant.ID),
		Email:     "unittest@pingcap.com",
		FirstName: "Test",
		LastName:  "Unit",
		Status:    string(entity.TenantStatusActive),
		Roles:     []string{role.Name},
		Role:      string(entity.UserRoleMember),
	}

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("POST", url, encodeJSON(&form))
	router.ServeHTTP(w, req)

	assert.Equalf(t, 200, w.Code, w.Body.String())

	result := forms.TenantUser{}
	decodeJSON(w.Body.Bytes(), &result)
	user := queryTenantUser(entity.ID(result.ID))
	deleteTenantUser(user)
}

func TestUpdateTenantUser(t *testing.T) {
	tenant := createTenant(t)
	defer deleteTenant(tenant)
	tenantUser := createTenantUser(tenant)
	defer deleteTenantUser(tenantUser)

	form := forms.TenantUser{
		Email:     "HAHA_" + tenantUser.Email,
		FirstName: tenantUser.FirstName,
		LastName:  tenantUser.LastName,
		Status:    string(tenantUser.Status),
		Roles:     []string{},
		Role:      string(tenantUser.Role),
	}
	url := genURL("/orgs/%d/users/%d", tenant.ID, tenantUser.ID)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("PUT", url, encodeJSON(&form))
	router.ServeHTTP(w, req)

	assert.Equalf(t, 200, w.Code, w.Body.String())
	result := forms.TenantUser{}
	decodeJSON(w.Body.Bytes(), &result)
	assert.Containsf(t, result.Email, "HAHA_", result.Email)
}

func TestDeleteTenantUser(t *testing.T) {
	tenant := createTenant(t)
	defer deleteTenant(tenant)
	tenantUser := createTenantUser(tenant)
	defer deleteTenantUser(tenantUser)

	url := genURL("/orgs/%d/users/%d", tenant.ID, tenantUser.ID)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("DELETE", url, nil)
	router.ServeHTTP(w, req)

	assert.Equalf(t, 200, w.Code, w.Body.String())
	tenantUser = queryTenantUser(tenantUser.ID)
	assert.Equal(t, 0, int(tenantUser.ID))
}

func TestGetTenantUser(t *testing.T) {
	tenant := createTenant(t)
	defer deleteTenant(tenant)
	tenantUser := createTenantUser(tenant)
	defer deleteTenantUser(tenantUser)

	url := genURL("/orgs/%d/users/%d", tenant.ID, tenantUser.ID)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", url, nil)
	router.ServeHTTP(w, req)

	assert.Equalf(t, 200, w.Code, w.Body.String())
}

/*
func TestListTenantClusters(t *testing.T) {
	tenant := createTenant(t)
	defer deleteTenant(tenant)

	url := genURL("/orgs/%d/clusters", tenant.ID)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", url, nil)
	router.ServeHTTP(w, req)

	assert.Equalf(t, 200, w.Code, w.Body.String())
}
*/

func TestCRUDCentralUsers(t *testing.T) {
	role := createRole(entity.PingCAPTenantID)
	defer deleteRole(role)

	form := forms.CentralUser{
		Email:     "unittest@pingcap.com",
		FirstName: "Test",
		LastName:  "Unit",
		Roles:     []string{role.Name},
	}

	// create
	url := genURL("/users")
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("POST", url, encodeJSON(&form))
	router.ServeHTTP(w, req)
	assert.Equalf(t, 200, w.Code, w.Body.String())
	result := forms.CentralUser{}
	decodeJSON(w.Body.Bytes(), &result)

	// list
	url = genURL("/users")
	w = httptest.NewRecorder()
	req, _ = http.NewRequest("GET", url, nil)
	router.ServeHTTP(w, req)
	assert.Equalf(t, 200, w.Code, w.Body.String())

	// get
	url = genURL("/users/%d", result.ID)
	w = httptest.NewRecorder()
	req, _ = http.NewRequest("GET", url, nil)
	router.ServeHTTP(w, req)
	assert.Equalf(t, 200, w.Code, w.Body.String())

	// update
	url = genURL("/users/%d", result.ID)
	form = forms.CentralUser{
		Email:     "HAHA_" + result.Email,
		FirstName: result.FirstName,
		LastName:  result.LastName,
		Roles:     result.Roles,
	}
	w = httptest.NewRecorder()
	req, _ = http.NewRequest("PUT", url, encodeJSON(form))
	router.ServeHTTP(w, req)
	assert.Equalf(t, 200, w.Code, w.Body.String())
	decodeJSON(w.Body.Bytes(), &result)
	assert.Contains(t, result.Email, "HAHA_")

	// delete
	url = genURL("/users/%d", result.ID)
	w = httptest.NewRecorder()
	req, _ = http.NewRequest("DELETE", url, nil)
	router.ServeHTTP(w, req)
	assert.Equalf(t, 200, w.Code, w.Body.String())
}
