package server

import (
	"context"
	"fmt"
	"strings"

	"github.com/perfect-panel/server/internal/config"
	"gorm.io/gorm"
)

type customServerLogicModel interface {
	FindServerListByFilter(ctx context.Context, filter *ServerFilter) (total int64, list []*Server, err error)
	ClearCache(ctx context.Context, id int64) error
	QueryServerCountByServerGroups(ctx context.Context, groupIds []int64) (int64, error)
	QueryAllGroup(ctx context.Context) ([]*Group, error)
	BatchDeleteNodeGroup(ctx context.Context, ids []int64) error
	InsertGroup(ctx context.Context, data *Group) error
	FindOneGroup(ctx context.Context, id int64) (*Group, error)
	UpdateGroup(ctx context.Context, data *Group) error
	DeleteGroup(ctx context.Context, id int64) error
	FindServerDetailByGroupIdsAndIds(ctx context.Context, groupId, ids []int64) ([]*Server, error)
	FindServerListByGroupIds(ctx context.Context, groupId []int64) ([]*Server, error)
	FindAllServer(ctx context.Context) ([]*Server, error)
	FindNodeByServerAddrAndProtocol(ctx context.Context, serverAddr string, protocol string) ([]*Server, error)
	FindServerMinSortByIds(ctx context.Context, ids []int64) (int64, error)
	FindServerListByIds(ctx context.Context, ids []int64) ([]*Server, error)
	InsertRuleGroup(ctx context.Context, data *RuleGroup) error
	FindOneRuleGroup(ctx context.Context, id int64) (*RuleGroup, error)
	UpdateRuleGroup(ctx context.Context, data *RuleGroup) error
	DeleteRuleGroup(ctx context.Context, id int64) error
	QueryAllRuleGroup(ctx context.Context) ([]*RuleGroup, error)
	FindServersByTag(ctx context.Context, tag string) ([]*Server, error)
	FindServerTags(ctx context.Context) ([]string, error)
}

var (
	CacheServerDetailPrefix     = "cache:server:detail:"
	cacheServerGroupAllKeys     = "cache:serverGroup:all"
	cacheServerRuleGroupAllKeys = "cache:serverRuleGroup:all"
)

// ClearCache Clear Cache
func (m *customServerModel) ClearCache(ctx context.Context, id int64) error {
	serverIdKey := fmt.Sprintf("%s%v", cacheServerIdPrefix, id)
	configKey := fmt.Sprintf("%s%d", config.ServerConfigCacheKey, id)

	return m.DelCacheCtx(ctx, serverIdKey, configKey)
}

// QueryServerCountByServerGroups Query  Server Count By Server Groups
func (m *customServerModel) QueryServerCountByServerGroups(ctx context.Context, groupIds []int64) (int64, error) {
	var count int64
	err := m.QueryNoCacheCtx(ctx, &count, func(conn *gorm.DB, v interface{}) error {
		return conn.Model(&Server{}).Where("group_id IN ?", groupIds).Count(&count).Error
	})
	return count, err
}

// QueryAllGroup returns all groups.
func (m *customServerModel) QueryAllGroup(ctx context.Context) ([]*Group, error) {
	var groups []*Group
	err := m.QueryCtx(ctx, &groups, cacheServerGroupAllKeys, func(conn *gorm.DB, v interface{}) error {
		return conn.Find(&groups).Error
	})
	return groups, err
}

// BatchDeleteNodeGroup deletes multiple groups.
func (m *customServerModel) BatchDeleteNodeGroup(ctx context.Context, ids []int64) error {
	return m.Transaction(ctx, func(tx *gorm.DB) error {
		for _, id := range ids {
			if err := m.Delete(ctx, id); err != nil {
				return err
			}
		}
		return nil
	})
}

// InsertGroup inserts a group.
func (m *customServerModel) InsertGroup(ctx context.Context, data *Group) error {
	return m.ExecCtx(ctx, func(conn *gorm.DB) error {
		return conn.Create(data).Error
	}, cacheServerGroupAllKeys)
}

// FindOneGroup finds a group.
func (m *customServerModel) FindOneGroup(ctx context.Context, id int64) (*Group, error) {
	var group Group
	err := m.QueryCtx(ctx, &group, fmt.Sprintf("cache:serverGroup:%v", id), func(conn *gorm.DB, v interface{}) error {
		return conn.Model(&Group{}).Where("id = ?", id).First(&group).Error
	})
	return &group, err
}

// UpdateGroup updates a group.
func (m *customServerModel) UpdateGroup(ctx context.Context, data *Group) error {
	return m.ExecCtx(ctx, func(conn *gorm.DB) error {
		return conn.Model(&Group{}).Where("id = ?", data.Id).Updates(data).Error
	}, cacheServerGroupAllKeys, fmt.Sprintf("cache:serverGroup:%v", data.Id))
}

// DeleteGroup deletes a group.
func (m *customServerModel) DeleteGroup(ctx context.Context, id int64) error {
	return m.ExecCtx(ctx, func(conn *gorm.DB) error {
		return conn.Where("id = ?", id).Delete(&Group{}).Error
	}, cacheServerGroupAllKeys, fmt.Sprintf("cache:serverGroup:%v", id))
}

// FindServerDetailByGroupIdsAndIds finds server details by group IDs and IDs.
func (m *customServerModel) FindServerDetailByGroupIdsAndIds(ctx context.Context, groupId, ids []int64) ([]*Server, error) {
	if len(groupId) == 0 && len(ids) == 0 {
		return []*Server{}, nil
	}
	var list []*Server
	err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error {
		conn = conn.
			Model(&Server{}).
			Where("`enable` = ?", true)
		if len(groupId) > 0 && len(ids) > 0 {
			// OR is used to connect group_id and id conditions
			conn = conn.Where("(`group_id` IN ? OR `id` IN ?)", groupId, ids)
		} else if len(groupId) > 0 {
			conn = conn.Where("`group_id` IN ?", groupId)
		} else if len(ids) > 0 {
			conn = conn.Where("`id` IN ?", ids)
		}

		return conn.Order("sort ASC").Find(v).Error
	})
	return list, err
}

func (m *customServerModel) FindServerListByGroupIds(ctx context.Context, groupId []int64) ([]*Server, error) {
	var data []*Server
	err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error {
		return conn.Model(&Server{}).Where("group_id IN ?", groupId).Find(v).Error
	})
	return data, err
}

func (m *customServerModel) FindAllServer(ctx context.Context) ([]*Server, error) {
	var data []*Server
	err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error {
		return conn.Model(&Server{}).Order("sort ASC").Find(v).Error
	})
	return data, err
}

func (m *customServerModel) FindNodeByServerAddrAndProtocol(ctx context.Context, serverAddr string, protocol string) ([]*Server, error) {
	var data []*Server
	err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error {
		return conn.Model(&Server{}).Where("server_addr = ?  and protocol = ?", serverAddr, protocol).Order("sort ASC").Find(v).Error
	})
	return data, err
}

func (m *customServerModel) FindServerMinSortByIds(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(&Server{}).Where("id IN ?", ids).Select("COALESCE(MIN(sort), 0)").Scan(v).Error
	})
	return minSort, err
}

func (m *customServerModel) FindServerListByIds(ctx context.Context, ids []int64) ([]*Server, error) {
	var list []*Server
	err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error {
		return conn.Model(&Server{}).Where("id IN ?", ids).Find(v).Error
	})
	return list, err
}

// InsertRuleGroup inserts a group.
func (m *customServerModel) InsertRuleGroup(ctx context.Context, data *RuleGroup) error {
	return m.ExecCtx(ctx, func(conn *gorm.DB) error {
		return conn.Where(&RuleGroup{}).Create(data).Error
	}, cacheServerRuleGroupAllKeys, fmt.Sprintf("cache:serverRuleGroup:%v", data.Id))
}

// FindOneRuleGroup finds a group.
func (m *customServerModel) FindOneRuleGroup(ctx context.Context, id int64) (*RuleGroup, error) {
	var group RuleGroup
	err := m.QueryCtx(ctx, &group, fmt.Sprintf("cache:serverRuleGroup:%v", id), func(conn *gorm.DB, v interface{}) error {
		return conn.Where(&RuleGroup{}).Model(&RuleGroup{}).Where("id = ?", id).First(&group).Error
	})
	return &group, err
}

// UpdateRuleGroup updates a group.
func (m *customServerModel) UpdateRuleGroup(ctx context.Context, data *RuleGroup) error {
	return m.ExecCtx(ctx, func(conn *gorm.DB) error {
		return conn.Where(&RuleGroup{}).Model(&RuleGroup{}).Where("id = ?", data.Id).Save(data).Error
	}, cacheServerRuleGroupAllKeys, fmt.Sprintf("cache:serverRuleGroup:%v", data.Id))
}

// DeleteRuleGroup deletes a group.
func (m *customServerModel) DeleteRuleGroup(ctx context.Context, id int64) error {
	return m.ExecCtx(ctx, func(conn *gorm.DB) error {
		return conn.Where(&RuleGroup{}).Where("id = ?", id).Delete(&RuleGroup{}).Error
	}, cacheServerRuleGroupAllKeys, fmt.Sprintf("cache:serverRuleGroup:%v", id))
}

// QueryAllRuleGroup returns all rule groups.
func (m *customServerModel) QueryAllRuleGroup(ctx context.Context) ([]*RuleGroup, error) {
	var groups []*RuleGroup
	err := m.QueryCtx(ctx, &groups, cacheServerRuleGroupAllKeys, func(conn *gorm.DB, v interface{}) error {
		return conn.Where(&RuleGroup{}).Find(&groups).Error
	})
	return groups, err
}

func (m *customServerModel) FindServerListByFilter(ctx context.Context, filter *ServerFilter) (total int64, list []*Server, err error) {
	var data []*Server
	if filter == nil {
		filter = &ServerFilter{
			Page: 1,
			Size: 10,
		}
	}

	if filter.Page <= 0 {
		filter.Page = 1
	}
	if filter.Size <= 0 {
		filter.Size = 10
	}

	err = m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error {
		query := conn.Model(&Server{}).Order("sort ASC")
		if filter.Group > 0 {
			query = conn.Where("group_id = ?", filter.Group)
		}
		if filter.Search != "" {
			query = query.Where("name LIKE ? OR server_addr LIKE ? OR tags LIKE ?", "%"+filter.Search+"%", "%"+filter.Search+"%", "%"+filter.Search+"%")
		}
		if len(filter.Tags) > 0 {
			for i, tag := range filter.Tags {
				if i == 0 {
					query = query.Where("tags LIKE ?", "%"+tag+"%")
				} else {
					query = query.Or("tags LIKE ?", "%"+tag+"%")
				}
			}
		}
		return query.Count(&total).Limit(filter.Size).Offset((filter.Page - 1) * filter.Size).Find(v).Error
	})
	if err != nil {
		return 0, nil, err
	}
	return total, data, nil
}

func (m *customServerModel) FindServerTags(ctx context.Context) ([]string, error) {
	var data []string
	err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error {
		return conn.Model(&Server{}).Distinct("tags").Pluck("tags", v).Error
	})
	var tags []string
	for _, tag := range data {
		if strings.Contains(tag, ",") {
			tags = append(tags, strings.Split(tag, ",")...)
		} else {
			tags = append(tags, tag)
		}
	}
	return tags, err
}

func (m *customServerModel) FindServersByTag(ctx context.Context, tag string) ([]*Server, error) {
	var data []*Server
	err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error {
		return conn.Model(&Server{}).Where("FIND_IN_SET(?, tags)", tag).Order("sort ASC").Find(v).Error
	})
	return data, err
}
