package system

import (
	"context"
	"errors"
	"time"

	"kra/internal/biz"

	"gorm.io/gorm"
)

type mediaPO struct {
	ID         uint `gorm:"primaryKey"`
	CreatedAt  time.Time
	UpdatedAt  time.Time
	DeletedAt  gorm.DeletedAt `gorm:"index"`
	Name       string
	CategoryID int `gorm:"column:class_id"`
	URL        string
	Tag        string
	Key        string `gorm:"index"`
	Size       int64
	Mime       string
	MD5        string `gorm:"index;column:md5"`
	UserID     uint   `gorm:"index"`
}

func (mediaPO) TableName() string { return "media_file_upload_and_downloads" }

type categoryPO struct {
	ID        uint `gorm:"primaryKey"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt gorm.DeletedAt `gorm:"index"`
	Name      string
	ParentID  uint `gorm:"column:pid"`
}

func (categoryPO) TableName() string { return "media_attachment_category" }

type mediaRepo struct{ data Provider }

func NewMediaRepo(data Provider) biz.MediaRepo { return &mediaRepo{data: data} }
func mediaFromPO(v mediaPO) *biz.MediaFile {
	return &biz.MediaFile{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, CategoryID: v.CategoryID, URL: v.URL, Tag: v.Tag, Key: v.Key, Size: v.Size, Mime: v.Mime, MD5: v.MD5, UserID: v.UserID}
}
func mediaToPO(v *biz.MediaFile) mediaPO {
	return mediaPO{ID: v.ID, Name: v.Name, CategoryID: v.CategoryID, URL: v.URL, Tag: v.Tag, Key: v.Key, Size: v.Size, Mime: v.Mime, MD5: v.MD5, UserID: v.UserID}
}
func (r *mediaRepo) CreateMedia(ctx context.Context, v *biz.MediaFile) error {
	po := mediaToPO(v)
	if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil {
		return err
	}
	v.ID = po.ID
	v.CreatedAt = po.CreatedAt
	v.UpdatedAt = po.UpdatedAt
	return nil
}
func (r *mediaRepo) FindMedia(ctx context.Context, id uint) (*biz.MediaFile, error) {
	var po mediaPO
	if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
		return nil, err
	}
	return mediaFromPO(po), nil
}
func (r *mediaRepo) FindMediaByHash(ctx context.Context, userID uint, hash string) (*biz.MediaFile, error) {
	var po mediaPO
	if err := r.data.DB().WithContext(ctx).Where("user_id = ? AND md5 = ?", userID, hash).First(&po).Error; err != nil {
		return nil, err
	}
	return mediaFromPO(po), nil
}
func (r *mediaRepo) ListMedia(ctx context.Context, filter biz.MediaFilter) ([]*biz.MediaFile, int64, error) {
	db := r.data.DB().WithContext(ctx).Model(&mediaPO{})
	if filter.Keyword != "" {
		db = db.Where("name LIKE ?", "%"+filter.Keyword+"%")
	}
	if filter.CategoryID > 0 {
		db = db.Where("class_id = ?", filter.CategoryID)
	}
	if filter.Tag != "" {
		db = db.Where("tag = ?", filter.Tag)
	}
	if filter.UserID > 0 {
		db = db.Where("user_id = ?", filter.UserID)
	}
	if !filter.StartCreatedAt.IsZero() {
		db = db.Where("created_at >= ?", filter.StartCreatedAt)
	}
	if !filter.EndCreatedAt.IsZero() {
		db = db.Where("created_at < ?", filter.EndCreatedAt)
	}
	var total int64
	if err := db.Count(&total).Error; err != nil {
		return nil, 0, err
	}
	var pos []mediaPO
	orderKey := filter.OrderKey
	if orderKey != "id" && orderKey != "size" && orderKey != "created_at" && orderKey != "name" {
		orderKey = "id"
	}
	if filter.Desc {
		orderKey += " desc"
	}
	limit := filter.PageSize
	if limit > 100 {
		limit = 100
	}
	offset := 0
	if limit > 0 {
		page := filter.Page
		if page <= 0 {
			page = 1
		}
		offset = limit * (page - 1)
	}
	if err := db.Limit(limit).Offset(offset).Order(orderKey).Find(&pos).Error; err != nil {
		return nil, 0, err
	}
	out := make([]*biz.MediaFile, 0, len(pos))
	for _, po := range pos {
		out = append(out, mediaFromPO(po))
	}
	return out, total, nil
}
func (r *mediaRepo) UpdateMediaName(ctx context.Context, id uint, name string) error {
	// The compatible flow performs a First before Update, so a missing record returns a
	// not-found error instead of silently succeeding with RowsAffected=0.
	var po mediaPO
	if err := r.data.DB().WithContext(ctx).Where("id = ?", id).First(&po).Error; err != nil {
		return err
	}
	return r.data.DB().WithContext(ctx).Model(&po).Update("name", name).Error
}
func (r *mediaRepo) DeleteMedia(ctx context.Context, id uint) error {
	return r.data.DB().WithContext(ctx).Unscoped().Delete(&mediaPO{}, id).Error
}
func (r *mediaRepo) MediaKeyReferences(ctx context.Context, key string) (int64, error) {
	var count int64
	err := r.data.DB().WithContext(ctx).Model(&mediaPO{}).Where(map[string]any{"key": key}).Count(&count).Error
	return count, err
}
func (r *mediaRepo) CreateMediaBatch(ctx context.Context, items []*biz.MediaFile) error {
	pos := make([]mediaPO, 0, len(items))
	for _, v := range items {
		pos = append(pos, mediaToPO(v))
	}
	return r.data.DB().WithContext(ctx).Create(&pos).Error
}
func (r *mediaRepo) SaveCategory(ctx context.Context, v *biz.AttachmentCategory) error {
	db := r.data.DB().WithContext(ctx)
	var count int64
	query := db.Model(&categoryPO{}).Where("name = ? AND pid = ?", v.Name, v.ParentID)
	if err := query.Count(&count).Error; err != nil {
		return err
	}
	if count > 0 {
		return errors.New("分类名称已存在")
	}
	if v.ID == 0 {
		po := categoryPO{Name: v.Name, ParentID: v.ParentID}
		if err := db.Create(&po).Error; err != nil {
			return err
		}
		v.ID = po.ID
		return nil
	}
	return db.Model(&categoryPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "pid": v.ParentID}).Error
}
func (r *mediaRepo) DeleteCategory(ctx context.Context, id uint) error {
	var count int64
	if err := r.data.DB().WithContext(ctx).Model(&categoryPO{}).Where("pid = ?", id).Count(&count).Error; err != nil {
		return err
	}
	if count > 0 {
		return errors.New("请先删除子级")
	}
	return r.data.DB().WithContext(ctx).Unscoped().Delete(&categoryPO{}, id).Error
}
func (r *mediaRepo) ListCategories(ctx context.Context) ([]*biz.AttachmentCategory, error) {
	var pos []categoryPO
	if err := r.data.DB().WithContext(ctx).Find(&pos).Error; err != nil {
		return nil, err
	}
	byID := map[uint]*biz.AttachmentCategory{}
	for _, po := range pos {
		// Children remains nil for leaf nodes, which serializes as null.
		byID[po.ID] = &biz.AttachmentCategory{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, ParentID: po.ParentID}
	}
	roots := []*biz.AttachmentCategory{}
	for _, po := range pos {
		v := byID[po.ID]
		if parent := byID[po.ParentID]; parent != nil {
			parent.Children = append(parent.Children, v)
		} else if po.ParentID == 0 {
			// The tree starts at parent id 0; non-zero orphan nodes are
			// intentionally omitted instead of being promoted to roots.
			roots = append(roots, v)
		}
	}
	return roots, nil
}
