package mysql

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

	"gorm.io/gorm"
	"gorm.io/gorm/clause"

	"wucher/internal/domain/dul"
	filemanager "wucher/internal/domain/file_manager"
	"wucher/internal/shared/pkg/uuidv7"
)

type DULRepository struct {
	db *gorm.DB
}

func NewDULRepository(db *gorm.DB) *DULRepository {
	return &DULRepository{db: db}
}

func (r *DULRepository) WithTransaction(ctx context.Context, fn func(repo dul.Repository) error) error {
	if fn == nil {
		return nil
	}
	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		return fn(&DULRepository{db: tx})
	})
}

func (r *DULRepository) Create(ctx context.Context, row *dul.DUL) error {
	if actor := actorUserIDFromContext(ctx); len(actor) > 0 {
		row.CreatedBy = actor
		row.UpdatedBy = actor
	}
	return r.db.WithContext(ctx).Omit(clause.Associations).Create(row).Error
}

func (r *DULRepository) Update(ctx context.Context, row *dul.DUL) error {
	if actor := actorUserIDFromContext(ctx); len(actor) > 0 {
		row.UpdatedBy = actor
	}
	return r.db.WithContext(ctx).Omit(clause.Associations).Save(row).Error
}

func (r *DULRepository) Delete(ctx context.Context, id []byte) error {
	updates := map[string]any{"deleted_at": time.Now().UTC()}
	if actor := actorUserIDFromContext(ctx); len(actor) > 0 {
		updates["deleted_by"] = actor
		updates["updated_by"] = actor
	}
	return r.db.WithContext(ctx).Model(&dul.DUL{}).Where("id = ? AND deleted_at IS NULL", id).Updates(updates).Error
}

func (r *DULRepository) GetByID(ctx context.Context, id []byte) (*dul.DUL, error) {
	var row dul.DUL
	err := r.db.WithContext(ctx).
		Preload("Base").
		Preload("Base.BaseCategory").
		Where("id = ? AND deleted_at IS NULL", id).
		First(&row).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	_ = r.loadImages(ctx, []*dul.DUL{&row})
	return &row, nil
}

func (r *DULRepository) List(ctx context.Context, filter, sort string, limit, offset int, baseIDs [][]byte) ([]dul.DUL, int64, error) {
	var rows []dul.DUL
	var total int64

	query := r.db.WithContext(ctx).Model(&dul.DUL{}).Where("deleted_at IS NULL")
	if len(baseIDs) > 0 {
		query = query.Where("base_id IN ?", baseIDs)
	}
	if s := strings.TrimSpace(filter); s != "" {
		like := "%" + s + "%"
		query = query.Where("name LIKE ? OR info LIKE ?", like, like)
	}
	if err := query.Count(&total).Error; err != nil {
		return nil, 0, err
	}
	if strings.TrimSpace(sort) != "" {
		query = query.Order(sort)
	} else {
		query = query.Order("base_id ASC").Order("no ASC")
	}
	if limit > 0 {
		query = query.Limit(limit).Offset(offset)
	}
	if err := query.Preload("Base").Preload("Base.BaseCategory").Find(&rows).Error; err != nil {
		return nil, 0, err
	}
	ptrs := make([]*dul.DUL, 0, len(rows))
	for i := range rows {
		ptrs = append(ptrs, &rows[i])
	}
	_ = r.loadImages(ctx, ptrs)
	return rows, total, nil
}

func (r *DULRepository) loadImages(ctx context.Context, rows []*dul.DUL) error {
	if len(rows) == 0 {
		return nil
	}
	refIDs := make([]string, 0, len(rows))
	byRef := map[string]*dul.DUL{}
	for _, row := range rows {
		if row == nil || len(row.ID) != 16 {
			continue
		}
		refID, _ := uuidv7.BytesToString(row.ID)
		refIDs = append(refIDs, refID)
		byRef[refID] = row
		row.Images = nil
	}
	if len(refIDs) == 0 {
		return nil
	}
	var attachments []filemanager.Attachment
	if err := r.db.WithContext(ctx).
		Preload("File").
		Where("ref_type = ? AND ref_id IN ?", "dul", refIDs).
		Order("sort_order ASC, created_at ASC").
		Find(&attachments).Error; err != nil {
		return err
	}
	for i := range attachments {
		att := attachments[i]
		row := byRef[att.RefID]
		if row == nil {
			continue
		}
		clone := att
		row.Images = append(row.Images, &clone)
	}
	return nil
}
