package system

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"kra/internal/biz"
	"kra/pkg/database/pagination"

	"gorm.io/gorm"
)

type dictionaryRepo struct{ data Provider }

func NewDictionaryRepo(data Provider) biz.DictionaryRepo { return &dictionaryRepo{data: data} }

type dictionaryPO struct {
	ID        uint `gorm:"primaryKey"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt gorm.DeletedAt `gorm:"index"`
	Name      string
	Type      string
	Status    *bool
	Desc      string
	ParentID  *uint
}

func (dictionaryPO) TableName() string { return "sys_dictionaries" }

type dictionaryDetailPO struct {
	ID           uint `gorm:"primaryKey"`
	CreatedAt    time.Time
	UpdatedAt    time.Time
	DeletedAt    gorm.DeletedAt `gorm:"index"`
	Label        string
	Value        string
	Extend       string
	Status       *bool
	Sort         int
	DictionaryID uint `gorm:"column:sys_dictionary_id;index"`
	ParentID     *uint
	Level        int
	Path         string
}

func (dictionaryDetailPO) TableName() string { return "sys_dictionary_details" }

func dictionaryFromPO(po dictionaryPO) *biz.Dictionary {
	return &biz.Dictionary{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, Type: po.Type, Status: po.Status, Desc: po.Desc, ParentID: po.ParentID}
}
func detailFromPO(po dictionaryDetailPO) *biz.DictionaryDetail {
	return &biz.DictionaryDetail{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Label: po.Label, Value: po.Value, Extend: po.Extend, Status: po.Status, Sort: po.Sort, DictionaryID: po.DictionaryID, ParentID: po.ParentID, Level: po.Level, Path: po.Path}
}
func parameterFromPO(po parameterPO) *biz.SystemParameter {
	return &biz.SystemParameter{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, Key: po.Key, Value: po.Value, Desc: po.Desc}
}

func (r *dictionaryRepo) CreateDictionary(ctx context.Context, v *biz.Dictionary) error {
	var existing dictionaryPO
	if err := r.data.DB().WithContext(ctx).Where("type = ?", v.Type).First(&existing).Error; err == nil {
		return errors.New("存在相同的type，不允许创建")
	} else if !errors.Is(err, gorm.ErrRecordNotFound) {
		return err
	}
	po := dictionaryPO{Name: v.Name, Type: v.Type, Status: v.Status, Desc: v.Desc, ParentID: v.ParentID}
	if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil {
		return err
	}
	v.ID = po.ID
	v.CreatedAt, v.UpdatedAt = po.CreatedAt, po.UpdatedAt
	return nil
}

func (r *dictionaryRepo) ImportDictionary(ctx context.Context, dictionary *biz.Dictionary, details []*biz.DictionaryDetail) error {
	var existing dictionaryPO
	if err := r.data.DB().WithContext(ctx).Where("type = ?", dictionary.Type).First(&existing).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
		return errors.New("存在相同的type，不允许导入")
	}
	return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		po := dictionaryPO{Name: dictionary.Name, Type: dictionary.Type, Status: dictionary.Status, Desc: dictionary.Desc, ParentID: dictionary.ParentID}
		if err := tx.Create(&po).Error; err != nil {
			return err
		}
		dictionary.ID = po.ID
		idMap := make(map[uint]uint, len(details))
		created := make(map[uint]uint, len(details))
		for index, item := range details {
			// Incomplete detail rows are ignored to preserve import behavior.
			if item.Label == "" || item.Value == "" {
				continue
			}
			oldID := item.ID
			detail := dictionaryDetailPO{Label: item.Label, Value: item.Value, Extend: item.Extend, Status: item.Status, Sort: item.Sort, DictionaryID: po.ID, Level: item.Level, Path: item.Path}
			if err := tx.Create(&detail).Error; err != nil {
				return err
			}
			created[uint(index)] = detail.ID
			if oldID > 0 {
				idMap[oldID] = detail.ID
			}
		}
		for index, item := range details {
			if item.ID == 0 || item.ParentID == nil || *item.ParentID == 0 {
				continue
			}
			newID, exists := created[uint(index)]
			newParentID, parentExists := idMap[*item.ParentID]
			if !exists || !parentExists {
				continue
			}
			if err := tx.Model(&dictionaryDetailPO{}).Where("id = ?", newID).Update("parent_id", newParentID).Error; err != nil {
				return err
			}
		}
		return nil
	})
}
func (r *dictionaryRepo) UpdateDictionary(ctx context.Context, v *biz.Dictionary) error {
	db := r.data.DB().WithContext(ctx)
	var current dictionaryPO
	if err := db.Where("id = ?", v.ID).First(&current).Error; err != nil {
		return errors.New("查询字典数据失败")
	}
	if current.Type != v.Type {
		var duplicate dictionaryPO
		if err := db.Where("type = ?", v.Type).First(&duplicate).Error; err == nil {
			return errors.New("存在相同的type，不允许创建")
		} else if !errors.Is(err, gorm.ErrRecordNotFound) {
			return err
		}
	}
	if v.ParentID != nil && *v.ParentID != 0 {
		if err := r.checkDictionaryCircularReference(ctx, v.ID, *v.ParentID); err != nil {
			return err
		}
	}
	return db.Model(&current).Updates(map[string]any{"name": v.Name, "type": v.Type, "status": v.Status, "desc": v.Desc, "parent_id": v.ParentID}).Error
}

// checkDictionaryCircularReference mirrors the compatible recursive parent-chain
// check. A missing parent is allowed (the reference implementation permits a
// dangling parent ID); only a self-reference or an ancestor cycle is rejected.
func (r *dictionaryRepo) checkDictionaryCircularReference(ctx context.Context, currentID, parentID uint) error {
	if currentID == parentID {
		return errors.New("不能将字典设置为自己的父级")
	}
	var parent dictionaryPO
	err := r.data.DB().WithContext(ctx).Where("id = ?", parentID).First(&parent).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil
	}
	if err != nil {
		return err
	}
	if parent.ParentID != nil && *parent.ParentID != 0 {
		return r.checkDictionaryCircularReference(ctx, currentID, *parent.ParentID)
	}
	return nil
}
func (r *dictionaryRepo) DeleteDictionary(ctx context.Context, id uint) error {
	db := r.data.DB().WithContext(ctx)
	var dictionary dictionaryPO
	if err := db.First(&dictionary, id).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return errors.New("请不要搞事")
		}
		return err
	}
	if err := db.Delete(&dictionary).Error; err != nil {
		return err
	}
	return db.Where("sys_dictionary_id = ?", id).Delete(&dictionaryDetailPO{}).Error
}
func (r *dictionaryRepo) FindDictionary(ctx context.Context, id uint, typ string, status *bool, details bool) (*biz.Dictionary, error) {
	var po dictionaryPO
	db := r.data.DB().WithContext(ctx)
	active := true
	if status != nil {
		active = *status
	}
	err := db.Where("(type = ? OR id = ?) AND status = ?", typ, id, active).First(&po).Error
	if err != nil {
		return nil, err
	}
	out := dictionaryFromPO(po)
	if details {
		var detailPOs []dictionaryDetailPO
		if err = db.Where("sys_dictionary_id = ? AND status = ?", out.ID, true).Order("sort").Find(&detailPOs).Error; err != nil {
			return nil, err
		}
		out.Details = make([]*biz.DictionaryDetail, 0, len(detailPOs))
		for _, detail := range detailPOs {
			out.Details = append(out.Details, detailFromPO(detail))
		}
	}
	return out, err
}

func (r *dictionaryRepo) ExportDictionary(ctx context.Context, id uint) (*biz.Dictionary, error) {
	var po dictionaryPO
	if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
		return nil, err
	}
	value := dictionaryFromPO(po)
	var detailPOs []dictionaryDetailPO
	if err := r.data.DB().WithContext(ctx).Where("sys_dictionary_id = ?", id).Order("sort").Find(&detailPOs).Error; err != nil {
		return nil, err
	}
	value.Details = make([]*biz.DictionaryDetail, 0, len(detailPOs))
	for _, detail := range detailPOs {
		value.Details = append(value.Details, detailFromPO(detail))
	}
	return value, nil
}
func (r *dictionaryRepo) ListDictionaries(ctx context.Context, page, size int, name, typ string, details bool) ([]*biz.Dictionary, int64, error) {
	db := r.data.DB().WithContext(ctx).Model(&dictionaryPO{})
	if name != "" {
		like := "%" + name + "%"
		db = db.Where("name LIKE ? OR type LIKE ?", like, like)
	}
	var total int64
	if err := db.Count(&total).Error; err != nil {
		return nil, 0, err
	}
	var pos []dictionaryPO
	// Both dictionary-list endpoints are intentionally unpaged. The page and
	// size parameters remain in the repository seam for existing callers, but
	// the administration contract ignores them.
	if err := db.Find(&pos).Error; err != nil {
		return nil, 0, err
	}
	parentIDs := make([]uint, 0, len(pos))
	dictionaryIDs := make([]uint, 0, len(pos))
	for _, po := range pos {
		dictionaryIDs = append(dictionaryIDs, po.ID)
		// The reference list-with-details endpoint only preloads details;
		// its ordinary list endpoint preloads one level of dictionary children.
		if !details {
			parentIDs = append(parentIDs, po.ID)
		}
	}
	childrenByParent := make(map[uint][]dictionaryPO)
	if !details && len(parentIDs) > 0 {
		var children []dictionaryPO
		if err := r.data.DB().WithContext(ctx).Where("parent_id IN ?", parentIDs).Find(&children).Error; err != nil {
			return nil, 0, err
		}
		for _, child := range children {
			if child.ParentID != nil {
				childrenByParent[*child.ParentID] = append(childrenByParent[*child.ParentID], child)
			}
		}
	}
	detailsByDictionary := make(map[uint][]dictionaryDetailPO)
	if details && len(dictionaryIDs) > 0 {
		var values []dictionaryDetailPO
		if err := r.data.DB().WithContext(ctx).Where("sys_dictionary_id IN ?", dictionaryIDs).Order("sort").Find(&values).Error; err != nil {
			return nil, 0, err
		}
		for _, value := range values {
			detailsByDictionary[value.DictionaryID] = append(detailsByDictionary[value.DictionaryID], value)
		}
	}
	out := make([]*biz.Dictionary, 0, len(pos))
	for _, po := range pos {
		v := dictionaryFromPO(po)
		if details {
			v.Details = make([]*biz.DictionaryDetail, 0, len(detailsByDictionary[v.ID]))
			for _, detail := range detailsByDictionary[v.ID] {
				v.Details = append(v.Details, detailFromPO(detail))
			}
		} else {
			v.Children = make([]*biz.Dictionary, 0, len(childrenByParent[po.ID]))
			for _, child := range childrenByParent[po.ID] {
				v.Children = append(v.Children, dictionaryFromPO(child))
			}
		}
		out = append(out, v)
	}
	return out, total, nil
}

func (r *dictionaryRepo) CreateDictionaryDetail(ctx context.Context, v *biz.DictionaryDetail) error {
	po := dictionaryDetailPO{Label: v.Label, Value: v.Value, Extend: v.Extend, Status: v.Status, Sort: v.Sort, DictionaryID: v.DictionaryID, ParentID: v.ParentID}
	po.Level = 0
	po.Path = ""
	if v.ParentID != nil {
		var parent dictionaryDetailPO
		if err := r.data.DB().WithContext(ctx).First(&parent, *v.ParentID).Error; err != nil {
			return err
		}
		po.Level = parent.Level + 1
		po.Path = strings.Trim(strings.Join([]string{parent.Path, fmt.Sprint(parent.ID)}, ","), ",")
	}
	if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil {
		return err
	}
	v.ID = po.ID
	v.Level = po.Level
	v.Path = po.Path
	return nil
}
func (r *dictionaryRepo) UpdateDictionaryDetail(ctx context.Context, v *biz.DictionaryDetail) error {
	var po dictionaryDetailPO
	db := r.data.DB().WithContext(ctx)
	if err := db.First(&po, v.ID).Error; err != nil {
		return err
	}
	level, path := 0, ""
	if v.ParentID != nil {
		if *v.ParentID == v.ID {
			return errors.New("不能将字典详情设置为自己或其子项的父级")
		}
		var parent dictionaryDetailPO
		if err := db.First(&parent, *v.ParentID).Error; err != nil {
			return err
		}
		if strings.Contains(","+parent.Path+",", fmt.Sprintf(",%d,", v.ID)) {
			return errors.New("不能将字典详情设置为自己或其子项的父级")
		}
		level = parent.Level + 1
		path = strings.Trim(strings.Join([]string{parent.Path, fmt.Sprint(parent.ID)}, ","), ",")
	}
	if err := db.Model(&dictionaryDetailPO{}).Where("id = ?", v.ID).Updates(map[string]any{"label": v.Label, "value": v.Value, "extend": v.Extend, "status": v.Status, "sort": v.Sort, "sys_dictionary_id": v.DictionaryID, "parent_id": v.ParentID, "level": level, "path": path}).Error; err != nil {
		return err
	}
	return r.updateDictionaryDetailChildren(db, v.ID, level, path)
}

// updateDictionaryDetailChildren recomputes descendants after a move. Updating
// level as well as path matters when a subtree is moved beneath a different depth.
func (r *dictionaryRepo) updateDictionaryDetailChildren(db *gorm.DB, parentID uint, parentLevel int, parentPath string) error {
	var children []dictionaryDetailPO
	if err := db.Where("parent_id = ?", parentID).Find(&children).Error; err != nil {
		return err
	}
	for _, child := range children {
		childPath := strings.Trim(strings.Join([]string{parentPath, fmt.Sprint(parentID)}, ","), ",")
		if err := db.Model(&dictionaryDetailPO{}).Where("id = ?", child.ID).Updates(map[string]any{"level": parentLevel + 1, "path": childPath}).Error; err != nil {
			return err
		}
		if err := r.updateDictionaryDetailChildren(db, child.ID, parentLevel+1, childPath); err != nil {
			return err
		}
	}
	return nil
}
func (r *dictionaryRepo) DeleteDictionaryDetail(ctx context.Context, id uint) error {
	var count int64
	if err := r.data.DB().WithContext(ctx).Model(&dictionaryDetailPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil {
		return err
	}
	if count > 0 {
		return errors.New("该字典详情下还有子项，无法删除")
	}
	return r.data.DB().WithContext(ctx).Delete(&dictionaryDetailPO{}, id).Error
}
func (r *dictionaryRepo) FindDictionaryDetail(ctx context.Context, id uint) (*biz.DictionaryDetail, error) {
	var po dictionaryDetailPO
	if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
		return nil, err
	}
	return detailFromPO(po), nil
}
func (r *dictionaryRepo) ListDictionaryDetails(ctx context.Context, page, size int, filter biz.DictionaryDetailFilter) ([]*biz.DictionaryDetail, int64, error) {
	db := r.data.DB().WithContext(ctx).Model(&dictionaryDetailPO{})
	if filter.DictionaryID != 0 {
		db = db.Where("sys_dictionary_id = ?", filter.DictionaryID)
	}
	if filter.Label != "" {
		db = db.Where("label LIKE ?", "%"+filter.Label+"%")
	}
	if filter.Value != "" {
		db = db.Where("value = ?", filter.Value)
	}
	if filter.Status != nil {
		db = db.Where("status = ?", *filter.Status)
	}
	if filter.ParentID != nil {
		db = db.Where("parent_id = ?", *filter.ParentID)
	}
	if filter.Level != nil {
		db = db.Where("level = ?", *filter.Level)
	}
	var total int64
	if err := db.Count(&total).Error; err != nil {
		return nil, 0, err
	}
	var pos []dictionaryDetailPO
	if err := pagination.ApplyRequired(db.Order("sort,id"), page, size, 100).Find(&pos).Error; err != nil {
		return nil, 0, err
	}
	out := make([]*biz.DictionaryDetail, 0, len(pos))
	for _, po := range pos {
		out = append(out, detailFromPO(po))
	}
	return out, total, nil
}
func (r *dictionaryRepo) DictionaryDetailTree(ctx context.Context, dictionaryID uint, typ string) ([]*biz.DictionaryDetail, error) {
	if dictionaryID == 0 {
		// The tree-by-type endpoint resolves only by dictionary type. Unlike the
		// public dictionary lookup, it does not require the dictionary itself to
		// be enabled.
		var dictionary dictionaryPO
		if err := r.data.DB().WithContext(ctx).Where("type = ?", typ).First(&dictionary).Error; err != nil {
			return nil, err
		}
		dictionaryID = dictionary.ID
	}
	var pos []dictionaryDetailPO
	if err := r.data.DB().WithContext(ctx).Where("sys_dictionary_id = ? AND parent_id IS NULL", dictionaryID).Order("sort").Find(&pos).Error; err != nil {
		return nil, err
	}
	roots := make([]*biz.DictionaryDetail, 0, len(pos))
	for _, po := range pos {
		item := detailFromPO(po)
		if err := r.loadDictionaryDetailChildren(ctx, item); err != nil {
			return nil, err
		}
		roots = append(roots, item)
	}
	return roots, nil
}

func (r *dictionaryRepo) DictionaryDetailsByParent(ctx context.Context, dictionaryID uint, parentID *uint, includeChildren bool) ([]*biz.DictionaryDetail, error) {
	db := r.data.DB().WithContext(ctx).Where("sys_dictionary_id = ?", dictionaryID)
	if parentID == nil {
		db = db.Where("parent_id IS NULL")
	} else {
		db = db.Where("parent_id = ?", *parentID)
	}
	var pos []dictionaryDetailPO
	if err := db.Order("sort").Find(&pos).Error; err != nil {
		return nil, err
	}
	items := make([]*biz.DictionaryDetail, 0, len(pos))
	for _, po := range pos {
		item := detailFromPO(po)
		if includeChildren {
			if err := r.loadDictionaryDetailChildren(ctx, item); err != nil {
				return nil, err
			}
		}
		items = append(items, item)
	}
	return items, nil
}

func (r *dictionaryRepo) loadDictionaryDetailChildren(ctx context.Context, parent *biz.DictionaryDetail) error {
	var pos []dictionaryDetailPO
	if err := r.data.DB().WithContext(ctx).Where("parent_id = ?", parent.ID).Order("sort").Find(&pos).Error; err != nil {
		return err
	}
	parent.Children = make([]*biz.DictionaryDetail, 0, len(pos))
	for _, po := range pos {
		child := detailFromPO(po)
		if err := r.loadDictionaryDetailChildren(ctx, child); err != nil {
			return err
		}
		parent.Children = append(parent.Children, child)
	}
	return nil
}
