package base

import (
	"database/sql"
	"fmt"
	"log"
	"reflect"
	"unicode"

	"github.com/samber/do"
	"github.com/samber/lo"

	"git.fsdpf.net/go/contracts"
	"git.fsdpf.net/go/contracts/helper"
	"git.fsdpf.net/go/contracts/support"
	"git.fsdpf.net/go/db"
)

// 资源变更事件
const ResChangeEventTopic = "res-change-event-topic"

// 资源变更数据
const ResChangeRecordTopic = "res-change-record-topic"

type ResChangeEventTopicPayload struct {
	Type   string     // insert | delete | update
	Res    Resource   // 变更资源
	Result sql.Result // 执行结果
}

type ResChangeRecordTopicPayload struct {
	Type   string           // insert | delete | update
	User   contracts.User   // 操作用户
	Res    Resource         // 变更资源
	Result sql.Result       // 执行结果
	Old    []map[string]any // 旧数据
	New    []map[string]any // 新数据
}

// 资源
type Resource struct {
	container *do.Injector

	Uuid            string         `db:"uuid"`
	PUuid           string         `db:"pUuid"`
	Code            string         `db:"code"`
	Name            string         `db:"name"`
	IsResVirtual    bool           `db:"isVirtual"`
	Table           string         `db:"table"`
	Namespace       string         `db:"namespace"`
	Workspace       string         `db:"workspace"`
	Primarykey      string         `db:"primaryKey"`
	IsHistoryRecord bool           `db:"isHistoryRecord"`
	HistoryCacheMax int            `db:"historyCacheMax"`
	Fields          ResFields      `db:"fields"`
	Roles           map[string]any `db:"roles"`
	UpdatedAt       string         `db:"updated_at"`
	CreatedAt       string         `db:"created_at"`
}

func (this *Resource) InitContainer(container *do.Injector) {
	this.container = container
}

// 资源UUID
func (this Resource) GetUuid() string {
	return this.Uuid
}

// 资源CODE
func (this Resource) GetCode() string {
	return this.Code
}

// 资源名
func (this Resource) GetName() string {
	return this.Name
}

// 主键
func (this Resource) GetPrimarykey() string {
	return this.Primarykey
}

// 是否虚拟资源
func (this Resource) IsVirtual() bool {
	return this.IsResVirtual
}

// 是否系统资源
func (this Resource) IsSystem() bool {
	return this.Namespace == "Framework\\Service"
}

// 资源字段
func (this Resource) GetFields() (result []contracts.ResField) {
	for _, item := range this.Fields {
		result = append(result, item)
	}
	return result
}

// 资源字段
func (this Resource) GetField(code string) (contracts.ResField, bool) {
	return lo.Find(this.GetFields(), func(v contracts.ResField) bool {
		return v.GetCode() == code
	})
}

// 判断资源字段
func (this Resource) HasField(code string) bool {
	return lo.SomeBy(this.GetFields(), func(v contracts.ResField) bool {
		return v.GetCode() == code
	})
}

// 开启事物
func (this Resource) BeginTransaction() (*db.Transaction, error) {
	return this.GetDBConn().BeginTransaction()
}

// 获取资源链接
func (this Resource) GetDBConn() *db.Connection {
	db := do.MustInvoke[db.DB](this.container)

	if this.IsSystem() {
		return db.Connection("service-support")
	}

	return db.Connection("default")
}

// 获取资源对应的数据库连接
func (this Resource) GetDBBuilder() *db.Builder {
	return this.GetDBConn().Query()
}

// 获取资源对应的数据表
func (this Resource) GetTable() db.Expression {
	if this.IsVirtual() {
		return db.Raw("(" + this.Table + ")")
	}

	return db.Raw(this.Table)
}

func (this Resource) GetDBDriver() string {
	return this.GetDBConn().GetConfig().Driver
}

func (this Resource) GetAuthDBTable(u contracts.User, params ...any) *db.Builder {
	builder := this.GetDBTable(append(params, u)...)

	// 数据权限过滤
	builder.Before(func(b *db.Builder, t string, data ...map[string]any) {
		if t == db.TYPE_SELECT || t == db.TYPE_UPDATE || t == db.TYPE_DELETE {
			this.WithRolesCondition(b, t, u)
		}
	})

	return builder
}

// GetDBTable("Test", contracts.User)
func (this Resource) GetDBTable(params ...any) *db.Builder {
	builder := this.GetDBBuilder()

	var user contracts.User
	alias := this.Code

	for _, param := range params {
		switch v := param.(type) {
		case *db.Transaction:
			builder.Tx = v
		case string:
			alias = v
		case contracts.User:
			user = v
		}
	}

	// 格式化数据库存储数据
	builder.Before(func(b *db.Builder, t string, data ...map[string]any) {
		if t == db.TYPE_UPDATE {
			// 格式化保存数据
			this.formatSaveValue(data[0])
		}
		if t == db.TYPE_INSERT {
			// 移除 table alias
			b.Table(string(this.GetTable()))
			for i := 0; i < len(data); i++ {
				// 格式化保存数据
				this.formatSaveValue(data[i])
				// 填充保存数据
				this.fillSaveValue(data[i], user, db.TYPE_INSERT)
			}
		}
	})

	// 资源事件
	this.onResEvent(builder)

	// 用户事件
	if this.IsHistoryRecord {
		this.onUserEvent(builder, user)
	}

	// 虚拟资源暂时不考虑鉴权
	if !this.IsVirtual() {
		// 返回鉴权后的 DB Builder
		// return
	}

	return builder.Table(string(this.GetTable()), alias)
}

func (this Resource) WithRolesCondition(b *db.Builder, t string, u contracts.User) error {
	isFullRight := false
	isFullNot := false

	NewOrm := do.MustInvoke[helper.NewOrm](this.container)
	NewOrmModel := do.MustInvoke[helper.NewOrmModel](this.container)
	NewOrmJoin := do.MustInvoke[helper.NewOrmJoin](this.container)

	GetResRelationResource := do.MustInvoke[GetResRelationResource](this.container)
	GetResRelations := do.MustInvoke[GetResRelations](this.container)
	GetResource := do.MustInvoke[contracts.GetResource](this.container)
	GetOrmConditionByRes := do.MustInvoke[GetOrmConditionByRes](this.container)

	items := do.MustInvoke[GetResRoles](this.container)(this.GetUuid(), u.Roles()...)

	subTables := lo.Reduce(items, func(carry string, item ResRole, _ int) string {
		db := this.GetDBBuilder().Table(string(this.GetTable()), this.GetCode()).Select(db.Raw("distinct `" + this.GetCode() + "`.*"))

		joins := lo.Filter(GetResRelations(item.Uuid), func(item ResRelation, _ int) bool {
			return item.Type == "inner" || item.Type == "left" || item.Type == "right"
		})

		for i := 0; i < len(joins); i++ {
			oResource, ok := GetResource(joins[i].ResourceCode)
			if !ok {
				continue
			}
			rResource, ok := GetResRelationResource(joins[i])
			if !ok {
				continue
			}

			join := NewOrmJoin(contracts.RelationType(joins[i].Type), oResource, joins[i].Code, joins[i].RelationResource, joins[i].RelationField, joins[i].RelationForeignKey)
			// 关联扩展条件
			join.SetCondition(GetOrmConditionByRes(joins[i].Uuid, "关联扩展条件"))

			join.Inject(db, NewOrmModel(rResource, rResource.GetCode(), rResource.GetName()))
		}

		conditions := GetOrmConditionByRes(item.Uuid, "关联扩展条件")

		if len(joins) == 0 && conditions.IsEmpty() {
			// 无权限, 直接跳过这个 unoin 语句
			if carry != "" {
				return carry
			}
			// 第一个无权限除外, 避免所有用户所属角色都是无权限
			db.WhereRaw("false")
			isFullNot = true
		} else if len(joins) == 0 && conditions.IsNotEmpty() && conditions.IsAlwaysRight() /* 1=1 的这种条件*/ {
			// 只要有1个满权限, 直接返回单条语句
			isFullRight = true
			return db.ToSql()
		} else if conditions.IsNotEmpty() {
			oOrm := NewOrm(this, nil)
			oOrm.SetGlobalParams(support.NewGlobalParam("{}", u))

			db.Where(conditions.ToSql(oOrm.GetModel()))

			// 如果前面是无权限的sql查看, 这直接返回本次查询
			if isFullNot {
				isFullNot = false
				return db.ToSql()
			}
		}

		if carry != "" {
			carry += " UNION "
		}

		return fmt.Sprintf("%s(%s)", carry, db.ToSql())
	}, "")

	if isFullRight {
		return nil
	}

	if isFullNot {
		b.WhereRaw("false")
	} else if subTables != "" {
		if t == db.TYPE_SELECT {
			b.FromSub(subTables, b.TableAlias)
		} else {
			b.WhereRaw(fmt.Sprintf(
				"`%s`.`id` in (SELECT `temp`.`id` FROM (%s) as `temp`)",
				lo.Ternary(b.TableAlias != "", b.TableAlias, this.GetCode()),
				subTables,
			))
		}
	}

	return nil
}

// 格式化保存数据
func (this Resource) formatSaveValue(data map[string]any) {
	//
	for k, v := range data {
		if k == "id" || k == "created_user" || k == "created_at" || k == "deleted_at" || k == "updated_at" {
			delete(data, k)
		} else if val, ok := v.(db.Expression); ok {
			data[k] = val
		} else if field, ok := this.GetField(k); ok {
			data[k] = field.ToValue(v)
		}
	}
}

// 填充保存数据
func (this Resource) fillSaveValue(data map[string]any, u contracts.User, t string) {
	for _, field := range this.GetFields() {
		fCode := field.GetCode()

		if fCode == "id" || fCode == "created_user" || fCode == "created_at" || fCode == "deleted_at" || fCode == "updated_at" || fCode == "owned_user" {
			continue
		}

		// 只有新增默认字段
		if _, ok := data[fCode]; !ok {
			data[fCode] = field.GetRawDefault(this.GetDBDriver())
		}
	}

	// 拥有者
	if _, ok := data["owned_user"]; !ok {
		data["owned_user"] = u.Uuid()
	}
	// 创建者
	data["created_user"] = u.Uuid()
	if this.GetDBDriver() == "sqlite" {
		// 更新时间
		// sqlite 不能自动更新时间, "DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
		data["updated_at"] = db.Raw("CURRENT_TIMESTAMP")
	} else {

	}
}

func (this Resource) GetStruct(extends ...reflect.StructField) any {
	fields := []reflect.StructField{}

	for _, field := range this.Fields {
		if unicode.IsLetter(rune(field.Code[0])) {
			fields = append(fields, field.ToStructField())
		} else {
			log.Printf("资源字段错误, 必须以字母开头 <- %s", field.Code)
		}
	}

	fields = lo.UniqBy(append(fields, extends...), func(v reflect.StructField) string {
		return v.Name
	})

	t := reflect.StructOf(fields)

	return reflect.New(t).Interface()
}

func (this Resource) GetSliceStruct(extends ...reflect.StructField) any {
	t := reflect.TypeOf(this.GetStruct(extends...))

	st := reflect.SliceOf(t.Elem())

	return reflect.New(st).Interface()
}

// 资源事件
func (this Resource) onResEvent(builder *db.Builder) {
	builder.After(func(b *db.Builder, t string, result sql.Result, err error, data ...map[string]any) {
		if err != nil || t == db.TYPE_SELECT {
			return
		} else if num, err := result.RowsAffected(); num == 0 || err != nil {
			return
		}

		// 全局触发器
		// 1. 清除系统缓存
		if err := do.MustInvoke[contracts.Queue](this.container).Publish(ResChangeEventTopic, ResChangeEventTopicPayload{
			Type:   t,
			Res:    this,
			Result: result,
		}); err != nil {
			log.Println("Queue Publish Err:", ResChangeEventTopic, err)
		}
	})
}

// 用户事件
func (this Resource) onUserEvent(builder *db.Builder, user contracts.User) {
	old := []map[string]any{}

	builder.Before(func(b *db.Builder, t string, data ...map[string]any) {
		if t != db.TYPE_UPDATE && t != db.TYPE_DELETE {
			return
		}
		// 查询保存之前的数据
		if _, err := b.Get(&old); err != nil {
			panic(err)
		}
	})

	builder.After(func(b *db.Builder, t string, result sql.Result, err error, data ...map[string]any) {
		if err != nil || t == db.TYPE_SELECT {
			return
		} else if num, err := result.RowsAffected(); num == 0 || err != nil {
			return
		}

		if user == nil {
			user = GetAnonymous()
		}

		// 触发消息队列
		if err := do.MustInvoke[contracts.Queue](this.container).Publish(ResChangeRecordTopic, ResChangeRecordTopicPayload{
			Type:   t,
			User:   user,
			Res:    this,
			Old:    old,
			New:    data,
			Result: result,
		}); err != nil {
			log.Println("Queue Publish Err:", ResChangeRecordTopic, err)
		}
	})
}

func NewVirtualResource(pRes Resource, code, name, sql string, fields []ResField) contracts.Resource {
	return &Resource{
		Uuid:            code,
		PUuid:           pRes.Uuid,
		Code:            code,
		Name:            name,
		IsResVirtual:    true,
		Table:           sql,
		Namespace:       pRes.Namespace,
		Workspace:       pRes.Workspace,
		IsHistoryRecord: false,
		HistoryCacheMax: 0,
		Fields:          fields,
		Roles:           nil,
	}
}