package subscribe

import (
	"context"
	"fmt"
	"strings"

	"github.com/perfect-panel/server/pkg/tool"
	"github.com/redis/go-redis/v9"
	"gorm.io/gorm"
)

type FilterParams struct {
	Page            int      // Page Number
	Size            int      // Page Size
	Ids             []int64  // Subscribe IDs
	Node            []int64  // Node IDs
	Tags            []string // Node Tags
	Show            bool     // Show Portal Page
	Sell            bool     // Sell
	Language        string   // Language
	DefaultLanguage bool     // Default Subscribe Language Data
	Search          string   // Search Keywords
	NodeGroupId     *int64   // Node Group ID
}

type FilterByNodeGroupsParams struct {
	Page         int     // Page Number
	Size         int     // Page Size
	NodeGroupIds []int64 // Node Group IDs (multiple)
}

func (p *FilterParams) Normalize() {
	if p.Page <= 0 {
		p.Page = 1
	}
	if p.Size <= 0 {
		p.Size = 10
	}
}

type customSubscribeLogicModel interface {
	FilterList(ctx context.Context, params *FilterParams) (int64, []*Subscribe, error)
	FilterListByNodeGroups(ctx context.Context, params *FilterByNodeGroupsParams) (int64, []*Subscribe, error)
	ClearCache(ctx context.Context, id ...int64) error
	QuerySubscribeMinSortByIds(ctx context.Context, ids []int64) (int64, error)
}

// NewModel returns a model for the database table.
func NewModel(conn *gorm.DB, c *redis.Client) Model {
	return &customSubscribeModel{
		defaultSubscribeModel: newSubscribeModel(conn, c),
	}
}

func (m *customSubscribeModel) QuerySubscribeMinSortByIds(ctx context.Context, ids []int64) (int64, error) {
	var minSort int64
	err := m.QueryNoCacheCtx(ctx, &minSort, func(conn *gorm.DB, v interface{}) error {
		return conn.Model(&Subscribe{}).Where("id IN ?", ids).Select("COALESCE(MIN(sort), 0)").Scan(v).Error
	})
	return minSort, err
}

func (m *customSubscribeModel) ClearCache(ctx context.Context, ids ...int64) error {
	if len(ids) <= 0 {
		return nil
	}

	var cacheKeys []string
	for _, id := range ids {
		data, err := m.FindOne(ctx, id)
		if err != nil {
			return err
		}
		cacheKeys = append(cacheKeys, m.getCacheKeys(data)...)
	}
	return m.CachedConn.DelCacheCtx(ctx, cacheKeys...)
}

// FilterList Filter Subscribe List
func (m *customSubscribeModel) FilterList(ctx context.Context, params *FilterParams) (int64, []*Subscribe, error) {
	if params == nil {
		params = &FilterParams{}
	}
	params.Normalize()

	var list []*Subscribe
	var total int64

	// 构建查询函数
	buildQuery := func(conn *gorm.DB, lang string) *gorm.DB {
		query := conn.Model(&Subscribe{})

		if params.Search != "" {
			s := "%" + params.Search + "%"
			query = query.Where("`name` LIKE ? OR `description` LIKE ?", s, s)
		}
		if params.Show {
			query = query.Where("`show` = true")
		}
		if params.Sell {
			query = query.Where("`sell` = true")
		}

		if len(params.Ids) > 0 {
			query = query.Where("id IN ?", params.Ids)
		}
		if len(params.Node) > 0 {
			query = query.Scopes(InSet("nodes", tool.Int64SliceToStringSlice(params.Node)))
		}

		if len(params.Tags) > 0 {
			query = query.Scopes(InSet("node_tags", params.Tags))
		}
		if params.NodeGroupId != nil {
			// Filter by node_group_ids using JSON_CONTAINS
			// JSON_CONTAINS requires a JSON string, not a bare integer
			jsonVal := fmt.Sprintf("%d", *params.NodeGroupId)
			query = query.Where("(node_group_ids IS NOT NULL AND JSON_CONTAINS(node_group_ids, ?))", jsonVal)
		}
		if lang != "" {
			query = query.Where("language = ?", lang)
		} else if params.DefaultLanguage {
			query = query.Where("language = ''")
		}

		return query
	}

	// 查询数据
	queryFunc := func(lang string) error {
		return m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error {
			query := buildQuery(conn, lang)
			if err := query.Count(&total).Error; err != nil {
				return err
			}
			return query.Order("sort ASC").
				Limit(params.Size).
				Offset((params.Page - 1) * params.Size).
				Find(v).Error
		})
	}

	err := queryFunc(params.Language)
	if err != nil {
		return 0, nil, err
	}

	// fallback 默认语言
	if params.DefaultLanguage && total == 0 {
		err = queryFunc("")
		if err != nil {
			return 0, nil, err
		}
	}

	return total, list, nil
}

func InSet(field string, values []string) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		if len(values) == 0 {
			return db
		}

		query := db.Where("1=0")
		for _, v := range values {
			query = query.Or("FIND_IN_SET(?, "+field+")", v)
		}
		return query
	}
}

// FilterListByNodeGroups Filter subscribes by node groups
// Match if subscribe's node_group_id OR node_group_ids contains any of the provided node group IDs
func (m *customSubscribeModel) FilterListByNodeGroups(ctx context.Context, params *FilterByNodeGroupsParams) (int64, []*Subscribe, error) {
	if params == nil {
		params = &FilterByNodeGroupsParams{
			Page: 1,
			Size: 10,
		}
	}
	if params.Page <= 0 {
		params.Page = 1
	}
	if params.Size <= 0 {
		params.Size = 10
	}

	var list []*Subscribe
	var total int64

	err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error {
		query := conn.Model(&Subscribe{})

		// Filter by node groups: match if node_group_id or node_group_ids contains any of the provided IDs
		if len(params.NodeGroupIds) > 0 {
			var conditions []string
			var args []interface{}

			// Condition 1: node_group_id IN (...)
			placeholders := make([]string, len(params.NodeGroupIds))
			for i, id := range params.NodeGroupIds {
				placeholders[i] = "?"
				args = append(args, id)
			}
			conditions = append(conditions, "node_group_id IN ("+strings.Join(placeholders, ",")+")")

			// Condition 2: JSON_CONTAINS(node_group_ids, id) for each id
			for _, id := range params.NodeGroupIds {
				// JSON_CONTAINS requires a JSON string value, not a bare integer
				jsonVal := fmt.Sprintf("%d", id)
				conditions = append(conditions, "node_group_ids IS NOT NULL AND JSON_CONTAINS(node_group_ids, ?)")
				args = append(args, jsonVal)
			}

			// Combine with OR: (node_group_id IN (...) OR JSON_CONTAINS(node_group_ids, id1) OR ...)
			query = query.Where("("+strings.Join(conditions, " OR ")+")", args...)
		}

		// Count total
		if err := query.Count(&total).Error; err != nil {
			return err
		}

		// Find with pagination
		return query.Order("sort ASC").
			Limit(params.Size).
			Offset((params.Page - 1) * params.Size).
			Find(v).Error
	})

	if err != nil {
		return 0, nil, err
	}

	return total, list, nil
}
