package system

import (
	"context"
	"errors"
	"strconv"

	"kra/internal/biz/system"
	"kra/internal/data/model"

	"gorm.io/gorm"
)

type dictionaryRepo struct {
	db *gorm.DB
}

// NewDictionaryRepo 创建字典仓储
func NewDictionaryRepo(db *gorm.DB) system.DictionaryRepo {
	return &dictionaryRepo{db: db}
}

func (r *dictionaryRepo) Create(ctx context.Context, dict *system.Dictionary) error {
	m := toModelDictionary(dict)
	if err := r.db.WithContext(ctx).Create(m).Error; err != nil {
		return err
	}
	dict.ID = uint(m.ID)
	return nil
}

func (r *dictionaryRepo) Update(ctx context.Context, dict *system.Dictionary) error {
	updates := map[string]interface{}{
		"name":      dict.Name,
		"type":      dict.Type,
		"status":    dict.Status,
		"desc":      dict.Desc,
		"parent_id": dict.ParentID,
	}
	return r.db.WithContext(ctx).Model(&model.SysDictionary{}).Where("id = ?", dict.ID).Updates(updates).Error
}

func (r *dictionaryRepo) Delete(ctx context.Context, id uint) error {
	return r.db.WithContext(ctx).Delete(&model.SysDictionary{}, id).Error
}

func (r *dictionaryRepo) FindByID(ctx context.Context, id uint) (*system.Dictionary, error) {
	var m model.SysDictionary
	if err := r.db.WithContext(ctx).First(&m, id).Error; err != nil {
		return nil, err
	}
	return toBizDictionary(&m), nil
}

func (r *dictionaryRepo) FindByType(ctx context.Context, typ string) (*system.Dictionary, error) {
	var m model.SysDictionary
	if err := r.db.WithContext(ctx).Where("type = ?", typ).First(&m).Error; err != nil {
		return nil, err
	}
	return toBizDictionary(&m), nil
}

func (r *dictionaryRepo) GetByTypeOrID(ctx context.Context, typ string, id uint, status *bool) (*system.Dictionary, error) {
	flag := true
	if status != nil {
		flag = *status
	}
	var m model.SysDictionary
	db := r.db.WithContext(ctx).Where("(type = ? OR id = ?) AND status = ?", typ, id, flag)
	if err := db.First(&m).Error; err != nil {
		return nil, err
	}
	dict := toBizDictionary(&m)
	// 加载详情
	var details []model.SysDictionaryDetail
	r.db.WithContext(ctx).Where("sys_dictionary_id = ? AND status = ?", m.ID, true).Order("sort").Find(&details)
	// 详情在service层处理
	return dict, nil
}

func (r *dictionaryRepo) List(ctx context.Context, name string) ([]*system.Dictionary, error) {
	db := r.db.WithContext(ctx).Model(&model.SysDictionary{})
	if name != "" {
		db = db.Where("name LIKE ? OR type LIKE ?", "%"+name+"%", "%"+name+"%")
	}
	// 预加载子字典
	db = db.Preload("Children")
	var list []model.SysDictionary
	if err := db.Find(&list).Error; err != nil {
		return nil, err
	}
	result := make([]*system.Dictionary, len(list))
	for i, m := range list {
		result[i] = toBizDictionary(&m)
	}
	return result, nil
}

func (r *dictionaryRepo) CheckCircularReference(ctx context.Context, currentID, parentID uint) error {
	if currentID == parentID {
		return errors.New("不能将字典设置为自己的父级")
	}
	var parent model.SysDictionary
	if err := r.db.WithContext(ctx).Where("id = ?", parentID).First(&parent).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil
		}
		return err
	}
	if parent.ParentID != 0 {
		return r.CheckCircularReference(ctx, currentID, uint(parent.ParentID))
	}
	return nil
}

type dictionaryDetailRepo struct {
	db *gorm.DB
}

// NewDictionaryDetailRepo 创建字典详情仓储
func NewDictionaryDetailRepo(db *gorm.DB) system.DictionaryDetailRepo {
	return &dictionaryDetailRepo{db: db}
}

func (r *dictionaryDetailRepo) Create(ctx context.Context, detail *system.DictionaryDetail) error {
	m := toModelDictionaryDetail(detail)
	if err := r.db.WithContext(ctx).Create(m).Error; err != nil {
		return err
	}
	detail.ID = uint(m.ID)
	return nil
}

func (r *dictionaryDetailRepo) Update(ctx context.Context, detail *system.DictionaryDetail) error {
	m := toModelDictionaryDetail(detail)
	return r.db.WithContext(ctx).Save(m).Error
}

func (r *dictionaryDetailRepo) Delete(ctx context.Context, id uint) error {
	return r.db.WithContext(ctx).Delete(&model.SysDictionaryDetail{}, id).Error
}

func (r *dictionaryDetailRepo) FindByID(ctx context.Context, id uint) (*system.DictionaryDetail, error) {
	var m model.SysDictionaryDetail
	if err := r.db.WithContext(ctx).First(&m, id).Error; err != nil {
		return nil, err
	}
	return toBizDictionaryDetail(&m), nil
}

func (r *dictionaryDetailRepo) List(ctx context.Context, page, pageSize int, filters map[string]interface{}) ([]*system.DictionaryDetail, int64, error) {
	db := r.db.WithContext(ctx).Model(&model.SysDictionaryDetail{})
	if v, ok := filters["label"]; ok && v != "" {
		db = db.Where("label LIKE ?", "%"+v.(string)+"%")
	}
	if v, ok := filters["value"]; ok && v != "" {
		db = db.Where("value = ?", v)
	}
	if v, ok := filters["status"]; ok {
		db = db.Where("status = ?", v)
	}
	if v, ok := filters["sys_dictionary_id"]; ok && v.(uint) != 0 {
		db = db.Where("sys_dictionary_id = ?", v)
	}
	if v, ok := filters["parent_id"]; ok {
		db = db.Where("parent_id = ?", v)
	}
	if v, ok := filters["level"]; ok {
		db = db.Where("level = ?", v)
	}

	var total int64
	if err := db.Count(&total).Error; err != nil {
		return nil, 0, err
	}

	offset := (page - 1) * pageSize
	var list []model.SysDictionaryDetail
	if err := db.Offset(offset).Limit(pageSize).Order("sort").Order("id").Find(&list).Error; err != nil {
		return nil, 0, err
	}

	result := make([]*system.DictionaryDetail, len(list))
	for i, m := range list {
		result[i] = toBizDictionaryDetail(&m)
	}
	return result, total, nil
}

func (r *dictionaryDetailRepo) GetListByDictionaryID(ctx context.Context, dictionaryID uint) ([]*system.DictionaryDetail, error) {
	var list []model.SysDictionaryDetail
	if err := r.db.WithContext(ctx).Where("sys_dictionary_id = ?", dictionaryID).Find(&list).Error; err != nil {
		return nil, err
	}
	result := make([]*system.DictionaryDetail, len(list))
	for i, m := range list {
		result[i] = toBizDictionaryDetail(&m)
	}
	return result, nil
}

func (r *dictionaryDetailRepo) GetTreeList(ctx context.Context, dictionaryID uint) ([]*system.DictionaryDetail, error) {
	var list []model.SysDictionaryDetail
	if err := r.db.WithContext(ctx).Where("sys_dictionary_id = ? AND parent_id IS NULL", dictionaryID).Order("sort").Find(&list).Error; err != nil {
		return nil, err
	}
	result := make([]*system.DictionaryDetail, len(list))
	for i, m := range list {
		result[i] = toBizDictionaryDetail(&m)
		r.setDisabled(result[i])
		if err := r.loadChildren(ctx, result[i]); err != nil {
			return nil, err
		}
	}
	return result, nil
}

func (r *dictionaryDetailRepo) loadChildren(ctx context.Context, detail *system.DictionaryDetail) error {
	var children []model.SysDictionaryDetail
	if err := r.db.WithContext(ctx).Where("parent_id = ?", detail.ID).Order("sort").Find(&children).Error; err != nil {
		return err
	}
	for _, m := range children {
		child := toBizDictionaryDetail(&m)
		r.setDisabled(child)
		if err := r.loadChildren(ctx, child); err != nil {
			return err
		}
		detail.Children = append(detail.Children, child)
	}
	return nil
}

func (r *dictionaryDetailRepo) setDisabled(detail *system.DictionaryDetail) {
	if detail.Status != nil {
		detail.Disabled = !*detail.Status
	} else {
		detail.Disabled = false
	}
}

func (r *dictionaryDetailRepo) GetByParent(ctx context.Context, dictionaryID uint, parentID *uint, includeChildren bool) ([]*system.DictionaryDetail, error) {
	db := r.db.WithContext(ctx).Model(&model.SysDictionaryDetail{}).Where("sys_dictionary_id = ?", dictionaryID)
	if parentID != nil {
		db = db.Where("parent_id = ?", *parentID)
	} else {
		db = db.Where("parent_id IS NULL")
	}
	var list []model.SysDictionaryDetail
	if err := db.Order("sort").Find(&list).Error; err != nil {
		return nil, err
	}
	result := make([]*system.DictionaryDetail, len(list))
	for i, m := range list {
		result[i] = toBizDictionaryDetail(&m)
		r.setDisabled(result[i])
		if includeChildren {
			if err := r.loadChildren(ctx, result[i]); err != nil {
				return nil, err
			}
		}
	}
	return result, nil
}

func (r *dictionaryDetailRepo) GetByDictionaryIDAndValue(ctx context.Context, dictionaryID uint, value string) (*system.DictionaryDetail, error) {
	var m model.SysDictionaryDetail
	if err := r.db.WithContext(ctx).Where("sys_dictionary_id = ? AND value = ?", dictionaryID, value).First(&m).Error; err != nil {
		return nil, err
	}
	return toBizDictionaryDetail(&m), nil
}

func (r *dictionaryDetailRepo) GetByTypeAndValue(ctx context.Context, typ, value string) (*system.DictionaryDetail, error) {
	var m model.SysDictionaryDetail
	if err := r.db.WithContext(ctx).Model(&model.SysDictionaryDetail{}).
		Joins("JOIN sys_dictionaries ON sys_dictionaries.id = sys_dictionary_details.sys_dictionary_id").
		Where("sys_dictionaries.type = ? AND sys_dictionary_details.value = ?", typ, value).
		First(&m).Error; err != nil {
		return nil, err
	}
	return toBizDictionaryDetail(&m), nil
}

func (r *dictionaryDetailRepo) GetTreeListByType(ctx context.Context, typ string) ([]*system.DictionaryDetail, error) {
	var list []model.SysDictionaryDetail
	if err := r.db.WithContext(ctx).Model(&model.SysDictionaryDetail{}).
		Joins("JOIN sys_dictionaries ON sys_dictionaries.id = sys_dictionary_details.sys_dictionary_id").
		Where("sys_dictionaries.type = ? AND sys_dictionary_details.parent_id IS NULL", typ).
		Order("sys_dictionary_details.sort").
		Find(&list).Error; err != nil {
		return nil, err
	}
	result := make([]*system.DictionaryDetail, len(list))
	for i, m := range list {
		result[i] = toBizDictionaryDetail(&m)
		r.setDisabled(result[i])
		if err := r.loadChildren(ctx, result[i]); err != nil {
			return nil, err
		}
	}
	return result, nil
}

func (r *dictionaryDetailRepo) GetListByType(ctx context.Context, typ string) ([]*system.DictionaryDetail, error) {
	var list []model.SysDictionaryDetail
	if err := r.db.WithContext(ctx).Model(&model.SysDictionaryDetail{}).
		Joins("JOIN sys_dictionaries ON sys_dictionaries.id = sys_dictionary_details.sys_dictionary_id").
		Where("sys_dictionaries.type = ?", typ).
		Find(&list).Error; err != nil {
		return nil, err
	}
	result := make([]*system.DictionaryDetail, len(list))
	for i, m := range list {
		result[i] = toBizDictionaryDetail(&m)
	}
	return result, nil
}

func (r *dictionaryDetailRepo) HasChildren(ctx context.Context, id uint) (bool, error) {
	var count int64
	if err := r.db.WithContext(ctx).Model(&model.SysDictionaryDetail{}).Where("parent_id = ?", id).Count(&count).Error; err != nil {
		return false, err
	}
	return count > 0, nil
}

func (r *dictionaryDetailRepo) CheckCircularReference(ctx context.Context, id, parentID uint) bool {
	if id == parentID {
		return true
	}
	var parent model.SysDictionaryDetail
	if err := r.db.WithContext(ctx).First(&parent, parentID).Error; err != nil {
		return false
	}
	if parent.ParentID == 0 {
		return false
	}
	return r.CheckCircularReference(ctx, id, uint(parent.ParentID))
}

func (r *dictionaryDetailRepo) UpdateChildrenLevelAndPath(ctx context.Context, parentID uint) error {
	var children []model.SysDictionaryDetail
	if err := r.db.WithContext(ctx).Where("parent_id = ?", parentID).Find(&children).Error; err != nil {
		return err
	}
	var parent model.SysDictionaryDetail
	if err := r.db.WithContext(ctx).First(&parent, parentID).Error; err != nil {
		return err
	}
	for _, child := range children {
		child.Level = parent.Level + 1
		if parent.Path == "" {
			child.Path = strconv.Itoa(int(parent.ID))
		} else {
			child.Path = parent.Path + "," + strconv.Itoa(int(parent.ID))
		}
		if err := r.db.WithContext(ctx).Save(&child).Error; err != nil {
			return err
		}
		if err := r.UpdateChildrenLevelAndPath(ctx, uint(child.ID)); err != nil {
			return err
		}
	}
	return nil
}

func (r *dictionaryDetailRepo) GetPath(ctx context.Context, id uint) ([]*system.DictionaryDetail, error) {
	var m model.SysDictionaryDetail
	if err := r.db.WithContext(ctx).First(&m, id).Error; err != nil {
		return nil, err
	}
	detail := toBizDictionaryDetail(&m)
	path := []*system.DictionaryDetail{detail}
	if m.ParentID != 0 {
		parentPath, err := r.GetPath(ctx, uint(m.ParentID))
		if err != nil {
			return nil, err
		}
		path = append(parentPath, path...)
	}
	return path, nil
}

func (r *dictionaryDetailRepo) DeleteByDictionaryID(ctx context.Context, dictionaryID uint) error {
	return r.db.WithContext(ctx).Where("sys_dictionary_id = ?", dictionaryID).Delete(&model.SysDictionaryDetail{}).Error
}

func (r *dictionaryDetailRepo) BatchCreate(ctx context.Context, details []*system.DictionaryDetail) error {
	models := make([]*model.SysDictionaryDetail, len(details))
	for i, d := range details {
		models[i] = toModelDictionaryDetail(d)
	}
	return r.db.WithContext(ctx).Create(&models).Error
}

func (r *dictionaryDetailRepo) BatchUpdateParentID(ctx context.Context, idMap map[uint]uint) error {
	// idMap: oldID -> newID
	// 这里需要根据实际导入逻辑处理
	return nil
}

// 转换函数
func toModelDictionary(d *system.Dictionary) *model.SysDictionary {
	m := &model.SysDictionary{
		ID:   int64(d.ID),
		Name: d.Name,
		Type: d.Type,
		Desc: d.Desc,
	}
	if d.Status != nil {
		m.Status = *d.Status
	}
	if d.ParentID != nil {
		m.ParentID = int64(*d.ParentID)
	}
	return m
}

func toBizDictionary(m *model.SysDictionary) *system.Dictionary {
	d := &system.Dictionary{
		ID:     uint(m.ID),
		Name:   m.Name,
		Type:   m.Type,
		Status: &m.Status,
		Desc:   m.Desc,
	}
	if m.ParentID != 0 {
		pid := uint(m.ParentID)
		d.ParentID = &pid
	}
	return d
}

func toModelDictionaryDetail(d *system.DictionaryDetail) *model.SysDictionaryDetail {
	m := &model.SysDictionaryDetail{
		ID:              int64(d.ID),
		Label:           d.Label,
		Value:           d.Value,
		Extend:          d.Extend,
		Sort:            int64(d.Sort),
		SysDictionaryID: int64(d.SysDictionaryID),
		Level:           int64(d.Level),
		Path:            d.Path,
	}
	if d.Status != nil {
		m.Status = *d.Status
	}
	if d.ParentID != nil {
		m.ParentID = int64(*d.ParentID)
	}
	return m
}

func toBizDictionaryDetail(m *model.SysDictionaryDetail) *system.DictionaryDetail {
	d := &system.DictionaryDetail{
		ID:              uint(m.ID),
		Label:           m.Label,
		Value:           m.Value,
		Extend:          m.Extend,
		Status:          &m.Status,
		Sort:            int(m.Sort),
		SysDictionaryID: uint(m.SysDictionaryID),
		Level:           int(m.Level),
		Path:            m.Path,
	}
	if m.ParentID != 0 {
		pid := uint(m.ParentID)
		d.ParentID = &pid
	}
	return d
}
