package model

import (
	"context"
	"database/sql"
	"fmt"
	"strings"
	"time"

	"git.i2edu.net/i2/i2-bill-erp/transform"
	"git.i2edu.net/i2/i2-bill-erp/transformclient"
	"github.com/xormplus/xorm"

	"git.i2edu.net/i2/go-zero/core/stores/cache"
	"git.i2edu.net/i2/go-zero/core/stores/sqlc"
	"git.i2edu.net/i2/go-zero/core/stores/sqlx"
	"git.i2edu.net/i2/go-zero/core/stringx"
	"git.i2edu.net/i2/go-zero/tools/goctl/model/sql/builderx"
)

var (
	UserFieldNames          = builderx.RawFieldNames(&User{})
	UserRows                = strings.Join(UserFieldNames, ",")
	UserRowsExpectAutoSet   = strings.Join(stringx.Remove(UserFieldNames, "`id`", "`create_time`", "`update_time`"), ",")
	UserRowsWithPlaceHolder = strings.Join(stringx.Remove(UserFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?"

	cacheUserIdPrefix           = "cache:user:id:"
	cacheUserUsernamePrefix     = "cache:user:username:"
	cacheUserWeiXinOpenIdPrefix = "cache:user:weixin_openid:"
)

type (
	Cache interface {
		SetCache(key string, v interface{}) error
		GetCache(key string, v interface{}) error
		SetWithExpire(key string, v interface{}, expire time.Duration) error
	}
	UserModel interface {
		Insert(data User) (sql.Result, error)
		FindOne(id int64) (*User, error)
		FindOneByUsername(username string) (*User, error)
		FindOneByWeiXinOpenId(openid string) (*User, error)
		Update(data User) error
		Delete(id int64) error
		CacheStorage() Cache
	}

	defaultUserModel struct {
		sqlc.CachedConn
		table string
	}
	User struct {
		Mobile        string `db:"mobile"`
		Avatar        string `db:"avatar"`
		WeixinOpenid  string `db:"weixin_openid"`
		Password      string `db:"password"`
		Birthday      int64  `db:"birthday"`
		RegisterTime  int64  `db:"register_time"`
		LastLoginTime int64  `db:"last_login_time"`
		Nickname      string `db:"nickname"`
		Id            int64  `db:"id"`
		ErpId         string `db:"erp_id"`
		Username      string `db:"username"`
		Gender        int64  `db:"gender"`
		UserLevelId   int64  `db:"user_level_id"`
		RegisterIp    string `db:"register_ip"`
		LastLoginIp   string `db:"last_login_ip"`
	}
)

func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf) UserModel {
	return &defaultUserModel{
		CachedConn: sqlc.NewConn(conn, c),
		table:      "`i2bill_user`",
	}
}

type UserXorm struct {
	Mobile        string `json:"mobile"`
	Avatar        string `json:"avatar"`
	WeixinOpenid  string `json:"weixin_openid"`
	Password      string `json:"password"`
	Birthday      int64  `json:"birthday"`
	RegisterTime  int64  `json:"register_time"`
	LastLoginTime int64  `json:"last_login_time"`
	Nickname      string `json:"nickname"`
	Id            int64  `json:"id"`
	ErpId         string `json:"erp_id"`
	Username      string `json:"username"`
	Gender        int64  `json:"gender"`
	UserLevelId   int64  `json:"user_level_id"`
	RegisterIp    string `json:"register_ip"`
	LastLoginIp   string `json:"last_login_ip"`
}

func (t *UserXorm) TableName() string {
	return "i2bill_user"
}

func (m *defaultUserModel) Insert(data User) (sql.Result, error) {
	userUsernameKey := fmt.Sprintf("%s%v", cacheUserUsernamePrefix, data.Username)
	ret, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
		query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, UserRowsExpectAutoSet)
		return conn.Exec(query, data.Mobile, data.Avatar, data.WeixinOpenid, data.Password, data.Birthday, data.RegisterTime, data.LastLoginTime, data.Nickname, data.Username, data.Gender, data.UserLevelId, data.RegisterIp, data.LastLoginIp)
	}, userUsernameKey)
	return ret, err
}

func (m *defaultUserModel) FindOne(id int64) (*User, error) {
	userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
	var resp User
	err := m.QueryRow(&resp, userIdKey, func(conn sqlx.SqlConn, v interface{}) error {
		query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", UserRows, m.table)
		return conn.QueryRow(v, query, id)
	})
	switch err {
	case nil:
		return &resp, nil
	case sqlc.ErrNotFound:
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (m *defaultUserModel) FindOneByWeiXinOpenId(id string) (*User, error) {
	userIdKey := fmt.Sprintf("%s%v", cacheUserWeiXinOpenIdPrefix, id)
	var resp User
	err := m.QueryRow(&resp, userIdKey, func(conn sqlx.SqlConn, v interface{}) error {
		query := fmt.Sprintf("select %s from %s where `weixin_openid` = ? limit 1", UserRows, m.table)
		return conn.QueryRow(v, query, id)
	})
	switch err {
	case nil:
		return &resp, nil
	case sqlc.ErrNotFound:
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (m *defaultUserModel) FindOneByUsername(username string) (*User, error) {
	userUsernameKey := fmt.Sprintf("%s%v", cacheUserUsernamePrefix, username)
	var resp User
	err := m.QueryRowIndex(&resp, userUsernameKey, m.formatPrimary, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
		query := fmt.Sprintf("select %s from %s where `username` = ? limit 1", UserRows, m.table)
		if err := conn.QueryRow(&resp, query, username); err != nil {
			return nil, err
		}
		return resp.Id, nil
	}, m.queryPrimary)
	switch err {
	case nil:
		return &resp, nil
	case sqlc.ErrNotFound:
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (m *defaultUserModel) Update(data User) error {
	userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id)
	userUsernameKey := fmt.Sprintf("%s%v", cacheUserUsernamePrefix, data.Username)
	_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
		query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, UserRowsWithPlaceHolder)
		return conn.Exec(query, data.Mobile, data.Avatar, data.WeixinOpenid, data.Password, data.Birthday, data.RegisterTime, data.LastLoginTime, data.Nickname, data.Username, data.Gender, data.UserLevelId, data.RegisterIp, data.LastLoginIp, data.Id)
	}, userIdKey, userUsernameKey)
	return err
}

func (m *defaultUserModel) Delete(id int64) error {
	data, err := m.FindOne(id)
	if err != nil {
		return err
	}

	userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
	userUsernameKey := fmt.Sprintf("%s%v", cacheUserUsernamePrefix, data.Username)
	_, err = m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
		query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
		return conn.Exec(query, id)
	}, userUsernameKey, userIdKey)
	return err
}

func (m *defaultUserModel) formatPrimary(primary interface{}) string {
	return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary)
}

func (m *defaultUserModel) queryPrimary(conn sqlx.SqlConn, v, primary interface{}) error {
	query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", UserRows, m.table)
	return conn.QueryRow(v, query, primary)
}

func (m *defaultUserModel) SetCache(key string, v interface{}) error {
	return m.CachedConn.SetCache(key, v)
}

func (m *defaultUserModel) GetCache(key string, v interface{}) error {
	err := m.CachedConn.GetCache(key, v)
	if err == sql.ErrNoRows {
		err = ErrRdsNotFound
	}
	return err
}

func (m *defaultUserModel) SetWithExpire(key string, v interface{}, expire time.Duration) error {
	return m.CachedConn.SetWithExpire(key, v, expire)
}

func (m *defaultUserModel) CacheStorage() Cache {
	return m
}

//获取用户信息
func GetI2bilUserInfo(userId int64, engine *xorm.Engine) (*UserXorm, error) {
	user := new(UserXorm)
	_, err := engine.SQL("select * from i2bill_user where id = ?", userId).Get(user)
	if err != nil {
		return nil, err
	}
	return user, nil
}

func GetAcquirePerm(userId int64, rpcClient transformclient.Transform, engine *xorm.Engine, ctx context.Context) (*transform.GetErpRoleRes, error) {
	var res = new(transform.GetErpRoleRes)
	partUser, err := GetPartTimeXormByUserId(userId, engine)

	if err != nil {
		return nil, err
	}
	if partUser.Id != 0 && partUser.MkId != "" {
		res, err = GetErpUser("", partUser.MkId, rpcClient, ctx)
		if err != nil {
			return nil, err
		}
	}

	user, err := GetI2bilUserInfo(userId, engine)
	if err != nil {
		return nil, err
	}

	resMobile, err := GetErpUser(user.Mobile, "", rpcClient, ctx)
	if err != nil {
		return nil, err
	}
	if resMobile != nil && resMobile.UserId != "" {
		res = resMobile
	}
	return res, nil
}

type AcquirePermInfo struct {
	*transform.GetErpRoleRes
	Role int64 // 1 兼职，2mk
}

func GetAcquirePermInfo(userId int64, rpcClient transformclient.Transform, engine *xorm.Engine, ctx context.Context) (*AcquirePermInfo, error) {
	//mk
	user, err := GetI2bilUserInfo(userId, engine)
	if err != nil {
		return nil, err
	}

	erpInfo, err := GetErpUser(user.Mobile, "", rpcClient, ctx)
	if err != nil {
		return nil, err
	}
	if erpInfo != nil && erpInfo.UserId != "" {
		return &AcquirePermInfo{erpInfo, 2}, nil
	}

	//兼职
	partUser, err := GetPartTimeXormByUserId(userId, engine)
	if err != nil {
		return nil, err
	}
	if partUser.Id != 0 && partUser.MkId != "" {

		erpInfo, err := GetErpUser("", partUser.MkId, rpcClient, ctx)
		if err != nil {
			return nil, err
		}
		if erpInfo != nil && erpInfo.UserId != "" {
			return &AcquirePermInfo{erpInfo, 1}, nil
		}
	}
	return nil, nil
}

func GetErpUser(mobile string, erpUserId string, rpcClient transformclient.Transform, ctx context.Context) (*transform.GetErpRoleRes, error) {
	in := new(transform.GetErpRoleReq)
	in.Mobile = mobile
	in.UserId = erpUserId
	erpRoles, err := rpcClient.GetErpRole(ctx, in)
	if err != nil {
		return nil, err
	}
	if erpRoles.UserId != "" {
		roles := strings.Split(erpRoles.Role, ",")
		for _, r := range roles {
			if r == "LAMK" || r == "LAMKM" || r == "student/readingStudent" {
				return erpRoles, nil
			}
		}

	}
	return nil, nil
}

//获取校区权限
func GetUserSchoolPerm(erpUserId string, rpcClient transformclient.Transform, engine *xorm.Engine, ctx context.Context) ([]*transform.OrganSchool, error) {
	in := erpUserId
	result, err := rpcClient.GetErpOrganSchPerByUserId(ctx, &transform.GetErpOrganSchPerByUserIdReq{UserId: in})
	if err != nil {
		return nil, err
	}
	return result.School, nil
}
