package db

import (
	"fmt"
	stdpath "path"
	"strings"

	"github.com/alist-org/alist/v3/internal/conf"
	"github.com/alist-org/alist/v3/internal/model"
	"github.com/alist-org/alist/v3/pkg/utils"
	"github.com/pkg/errors"
	"gorm.io/gorm"
)

func whereInParent(parent string) *gorm.DB {
	if parent == "/" {
		return db.Where("1 = 1")
	}
	return db.Where(fmt.Sprintf("%s LIKE ?", columnName("parent")),
		fmt.Sprintf("%s/%%", parent)).
		Or(fmt.Sprintf("%s = ?", columnName("parent")), parent)
}

func CreateSearchNode(node *model.SearchNode) error {
	return db.Create(node).Error
}

func BatchCreateSearchNodes(nodes *[]model.SearchNode) error {
	return db.CreateInBatches(nodes, 1000).Error
}

func DeleteSearchNodesByParent(path string) error {
	path = utils.FixAndCleanPath(path)
	err := db.Where(whereInParent(path)).Delete(&model.SearchNode{}).Error
	if err != nil {
		return err
	}
	dir, name := stdpath.Split(path)
	return db.Where(fmt.Sprintf("%s = ? AND %s = ?",
		columnName("parent"), columnName("name")),
		dir, name).Delete(&model.SearchNode{}).Error
}

func ClearSearchNodes() error {
	return db.Where("1 = 1").Delete(&model.SearchNode{}).Error
}

func GetSearchNodesByParent(parent string) ([]model.SearchNode, error) {
	var nodes []model.SearchNode
	if err := db.Where(fmt.Sprintf("%s = ?",
		columnName("parent")), parent).Find(&nodes).Error; err != nil {
		return nil, err
	}
	return nodes, nil
}

func SearchNode(req model.SearchReq, useFullText bool) ([]model.SearchNode, int64, error) {
	var searchDB *gorm.DB
	if !useFullText || conf.Conf.Database.Type == "sqlite3" {
		keywordsClause := db.Where("1 = 1")
		for _, keyword := range strings.Fields(req.Keywords) {
			keywordsClause = keywordsClause.Where("name LIKE ?", fmt.Sprintf("%%%s%%", keyword))
		}
		searchDB = db.Model(&model.SearchNode{}).Where(whereInParent(req.Parent)).Where(keywordsClause)
	} else {
		switch conf.Conf.Database.Type {
		case "mysql":
			searchDB = db.Model(&model.SearchNode{}).Where(whereInParent(req.Parent)).
				Where("MATCH (name) AGAINST (? IN BOOLEAN MODE)", "'*"+req.Keywords+"*'")
		case "postgres":
			searchDB = db.Model(&model.SearchNode{}).Where(whereInParent(req.Parent)).
				Where("to_tsvector(name) @@ to_tsquery(?)", strings.Join(strings.Fields(req.Keywords), " & "))
		}
	}

	if req.Scope != 0 {
		isDir := req.Scope == 1
		searchDB.Where(db.Where("is_dir = ?", isDir))
	}

	var count int64
	if err := searchDB.Count(&count).Error; err != nil {
		return nil, 0, errors.Wrapf(err, "failed get search items count")
	}
	var files []model.SearchNode
	if err := searchDB.Order("name asc").Offset((req.Page - 1) * req.PerPage).Limit(req.PerPage).
		Find(&files).Error; err != nil {
		return nil, 0, err
	}
	return files, count, nil
}