package system

import (
	"context"
	"sort"
	"time"

	"gorm.io/gorm"
	"kra/internal/biz"
	"kra/pkg/database/pagination"
)

type versionPO struct {
	ID          uint `gorm:"primaryKey"`
	CreatedAt   time.Time
	UpdatedAt   time.Time
	DeletedAt   gorm.DeletedAt `gorm:"index"`
	Name        *string        `gorm:"column:version_name"`
	Code        *string        `gorm:"column:version_code"`
	Description *string
	Data        *string `gorm:"column:version_data;type:text"`
}

func (versionPO) TableName() string { return "sys_versions" }

type versionRepo struct{ data Provider }

func NewVersionRepo(data Provider) biz.VersionRepo { return &versionRepo{data: data} }
func versionFromPO(v versionPO) *biz.Version {
	return &biz.Version{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Code: v.Code, Description: v.Description, Data: v.Data}
}
func (r *versionRepo) CreateVersion(ctx context.Context, v *biz.Version) error {
	po := versionPO{Name: v.Name, Code: v.Code, Description: v.Description, Data: v.Data}
	if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil {
		return err
	}
	v.ID = po.ID
	return nil
}
func (r *versionRepo) DeleteVersions(ctx context.Context, ids []uint) error {
	return r.data.DB().WithContext(ctx).Where("id IN ?", ids).Delete(&versionPO{}).Error
}
func (r *versionRepo) FindVersion(ctx context.Context, id uint) (*biz.Version, error) {
	var po versionPO
	if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
		return nil, err
	}
	return versionFromPO(po), nil
}
func (r *versionRepo) ListVersions(ctx context.Context, page, size int, name, code string, createdAtRange []*time.Time) ([]*biz.Version, int64, error) {
	db := r.data.DB().WithContext(ctx).Model(&versionPO{})
	if len(createdAtRange) == 2 && createdAtRange[0] != nil && createdAtRange[1] != nil {
		db = db.Where("created_at BETWEEN ? AND ?", createdAtRange[0], createdAtRange[1])
	}
	if name != "" {
		db = db.Where("version_name LIKE ?", "%"+name+"%")
	}
	if code != "" {
		db = db.Where("version_code = ?", code)
	}
	var total int64
	if err := db.Count(&total).Error; err != nil {
		return nil, 0, err
	}
	var pos []versionPO
	if err := pagination.Apply(db, page, size, 100).Find(&pos).Error; err != nil {
		return nil, 0, err
	}
	out := make([]*biz.Version, 0, len(pos))
	for _, po := range pos {
		out = append(out, versionFromPO(po))
	}
	return out, total, nil
}
func (r *versionRepo) BuildVersionBundle(ctx context.Context, menuIDs, apiIDs, dictIDs []uint) (*biz.VersionBundle, error) {
	bundle := &biz.VersionBundle{}
	var menus []menuPO
	if len(menuIDs) > 0 {
		if err := r.data.DB().WithContext(ctx).Where("id IN ?", menuIDs).Order("sort,id").Find(&menus).Error; err != nil {
			return nil, &biz.VersionStageError{Stage: biz.VersionStageMenus, Err: err}
		}
	}
	byID := map[uint]*biz.Menu{}
	for _, po := range menus {
		v := menuFromPO(po)
		byID[po.ID] = v
	}
	if len(menuIDs) > 0 {
		var buttons []menuButtonPO
		if err := r.data.DB().WithContext(ctx).Where("sys_base_menu_id IN ?", menuIDs).Find(&buttons).Error; err != nil {
			return nil, &biz.VersionStageError{Stage: biz.VersionStageMenus, Err: err}
		}
		for _, button := range buttons {
			if menu := byID[button.MenuID]; menu != nil {
				menu.Buttons = append(menu.Buttons, &biz.MenuButton{Name: button.Name, Description: button.Description})
			}
		}
		var parameters []menuParameterPO
		if err := r.data.DB().WithContext(ctx).Where("sys_base_menu_id IN ?", menuIDs).Find(&parameters).Error; err != nil {
			return nil, &biz.VersionStageError{Stage: biz.VersionStageMenus, Err: err}
		}
		for _, parameter := range parameters {
			if menu := byID[parameter.MenuID]; menu != nil {
				menu.Parameters = append(menu.Parameters, &biz.MenuParameter{Type: parameter.Type, Key: parameter.Key, Value: parameter.Value})
			}
		}
	}
	for _, po := range menus {
		v := byID[po.ID]
		if parent := byID[po.ParentID]; parent != nil {
			parent.Children = append(parent.Children, v)
		} else if po.ParentID == 0 {
			bundle.Menus = append(bundle.Menus, v)
		}
	}
	var sortMenus func([]*biz.Menu)
	sortMenus = func(items []*biz.Menu) {
		sort.Slice(items, func(i, j int) bool { return items[i].Sort < items[j].Sort })
		for _, item := range items {
			sortMenus(item.Children)
		}
	}
	sortMenus(bundle.Menus)
	var cleanMenu func(*biz.Menu)
	cleanMenu = func(menu *biz.Menu) {
		menu.ID = 0
		menu.ParentID = 0
		for _, button := range menu.Buttons {
			button.ID = 0
			button.MenuID = 0
			button.AuthorityID = 0
		}
		for _, parameter := range menu.Parameters {
			parameter.ID = 0
			parameter.MenuID = 0
		}
		for _, child := range menu.Children {
			cleanMenu(child)
		}
	}
	for _, menu := range bundle.Menus {
		cleanMenu(menu)
	}
	var apis []apiPO
	if len(apiIDs) > 0 {
		if err := r.data.DB().WithContext(ctx).Where("id IN ?", apiIDs).Find(&apis).Error; err != nil {
			return nil, &biz.VersionStageError{Stage: biz.VersionStageAPIs, Err: err}
		}
	}
	for _, po := range apis {
		v := apiFromPO(po)
		v.ID = 0
		bundle.APIs = append(bundle.APIs, v)
	}
	var dictionaries []dictionaryPO
	if len(dictIDs) > 0 {
		if err := r.data.DB().WithContext(ctx).Where("id IN ?", dictIDs).Find(&dictionaries).Error; err != nil {
			return nil, &biz.VersionStageError{Stage: biz.VersionStageDictionaries, Err: err}
		}
	}
	dictionariesRepo := &dictionaryRepo{data: r.data}
	for _, po := range dictionaries {
		v := dictionaryFromPO(po)
		v.ID = 0
		details, _, err := dictionariesRepo.ListDictionaryDetails(ctx, 0, 0, biz.DictionaryDetailFilter{DictionaryID: po.ID})
		if err != nil {
			return nil, &biz.VersionStageError{Stage: biz.VersionStageDictionaries, Err: err}
		}
		for _, d := range details {
			d.ID = 0
			d.DictionaryID = 0
			d.ParentID = nil
			d.Path = ""
			d.Level = 0
		}
		v.Details = details
		bundle.Dictionaries = append(bundle.Dictionaries, v)
	}
	return bundle, nil
}
func (r *versionRepo) ImportVersionBundle(ctx context.Context, b *biz.VersionBundle) error {
	db := r.data.DB().WithContext(ctx)
	if len(b.Menus) > 0 {
		if err := db.Transaction(func(tx *gorm.DB) error { return importVersionMenus(tx, b.Menus, 0) }); err != nil {
			return &biz.VersionStageError{Stage: biz.VersionStageMenus, Err: err}
		}
	}
	if len(b.APIs) > 0 {
		if err := db.Transaction(func(tx *gorm.DB) error { return importVersionAPIs(tx, b.APIs) }); err != nil {
			return &biz.VersionStageError{Stage: biz.VersionStageAPIs, Err: err}
		}
	}
	if len(b.Dictionaries) > 0 {
		if err := db.Transaction(func(tx *gorm.DB) error { return importVersionDictionaries(tx, b.Dictionaries) }); err != nil {
			return &biz.VersionStageError{Stage: biz.VersionStageDictionaries, Err: err}
		}
	}
	return nil
}

func importVersionMenus(tx *gorm.DB, items []*biz.Menu, parentID uint) error {
	for _, item := range items {
		var existing menuPO
		if err := tx.Where("name = ? AND path = ?", item.Name, item.Path).First(&existing).Error; err == nil {
			if err = importVersionMenus(tx, item.Children, existing.ID); err != nil {
				return err
			}
			continue
		}
		po := menuToPO(item)
		po.ID = 0
		po.ParentID = parentID
		if err := tx.Create(&po).Error; err != nil {
			return err
		}
		for _, parameter := range item.Parameters {
			if err := tx.Create(&menuParameterPO{MenuID: po.ID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}).Error; err != nil {
				return err
			}
		}
		for _, button := range item.Buttons {
			if err := tx.Create(&menuButtonPO{Name: button.Name, Description: button.Description, MenuID: po.ID}).Error; err != nil {
				return err
			}
		}
		if err := importVersionMenus(tx, item.Children, po.ID); err != nil {
			return err
		}
	}
	return nil
}

func importVersionAPIs(tx *gorm.DB, items []*biz.API) error {
	for _, item := range items {
		var existing apiPO
		if err := tx.Where("path = ? AND method = ?", item.Path, item.Method).First(&existing).Error; err == nil {
			continue
		}
		if err := tx.Create(&apiPO{Path: item.Path, Description: item.Description, APIGroup: item.APIGroup, Method: item.Method}).Error; err != nil {
			return err
		}
	}
	return nil
}

func importVersionDictionaries(tx *gorm.DB, items []*biz.Dictionary) error {
	for _, item := range items {
		var existing dictionaryPO
		if err := tx.Where("type = ?", item.Type).First(&existing).Error; err == nil {
			continue
		}
		po := dictionaryPO{Name: item.Name, Type: item.Type, Status: item.Status, Desc: item.Desc}
		if err := tx.Create(&po).Error; err != nil {
			return err
		}
		for _, detail := range item.Details {
			value := dictionaryDetailPO{Label: detail.Label, Value: detail.Value, Extend: detail.Extend, Status: detail.Status, Sort: detail.Sort, DictionaryID: po.ID}
			if err := tx.Create(&value).Error; err != nil {
				return err
			}
		}
	}
	return nil
}
