package models

import (
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/timeutil"
	"fmt"
	"xorm.io/builder"
)

type OfficialTag struct {
	ID          int64              `xorm:"pk autoincr"`
	Name        string             `xorm:"NOT NULL"`
	Code        string             `xorm:"NOT NULL"`
	Limit       int                `xorm:"NOT NULL default(-1)"`
	Status      int                `xorm:"NOT NULL default(0)"`
	CreatedUnix timeutil.TimeStamp `xorm:"created"`
	UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
}

type OfficialTagRepos struct {
	ID          int64              `xorm:"pk autoincr"`
	OrgID       int64              `xorm:"NOT NULL INDEX"`
	TagID       int64              `xorm:"NOT NULL"`
	RepoID      int64              `xorm:"NOT NULL INDEX"`
	CreatedUnix timeutil.TimeStamp `xorm:"created"`
	UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
}

type TagReposBrief struct {
	RepoID   int64
	RepoName string
	Alias    string
	TagID    int64
}

type TagReposSelected struct {
	RepoID   int64
	RepoName string
	Selected bool
}

type TagsDetail struct {
	TagId    int64
	TagName  string
	TagLimit int
	RepoList []*Repository
}

func GetTagByID(id int64) (*OfficialTag, error) {
	r := &OfficialTag{
		ID: id,
	}
	has, err := x.Get(r)
	if err != nil {
		return nil, err
	} else if !has {
		return nil, ErrTagNotExist{0}
	}
	return r, nil
}

func UpdateTagReposByID(tagID, orgID int64, repoIdList []int64) error {
	sess := x.NewSession()
	defer sess.Close()

	if err := sess.Begin(); err != nil {
		return fmt.Errorf("UpdateTagReposByID[tagId: %d, orgID: %d,error:%v", tagID, orgID, err)
	}
	//delete old tag repos
	r := &OfficialTagRepos{
		TagID: tagID,
		OrgID: orgID,
	}
	_, err := sess.Delete(r)
	if err != nil {
		return err
	}

	if len(repoIdList) == 0 {
		return sess.Commit()
	}

	//add new tag repos
	data := make([]*OfficialTagRepos, 0)
	for _, repoId := range repoIdList {
		data = append(data, &OfficialTagRepos{
			OrgID:  orgID,
			TagID:  tagID,
			RepoID: repoId,
		})
	}
	_, err = sess.Insert(&data)
	if err != nil {
		sess.Rollback()
		return err
	}
	return sess.Commit()
}

func GetTagRepos(tagID, orgID int64) ([]TagReposSelected, error) {
	t := make([]TagReposBrief, 0)
	const SQLCmd = "select t1.id as repo_id,t1.name as repo_name,t1.alias,t2.id as tag_id from repository t1 left join official_tag_repos t2 on (t1.id = t2.repo_id and t2.tag_id = ?) where t1.owner_id = ? and t1.is_private = false order by t1.updated_unix desc"

	if err := x.SQL(SQLCmd, tagID, orgID).Find(&t); err != nil {
		return nil, err
	}
	r := make([]TagReposSelected, 0)
	for _, v := range t {
		selected := false
		if v.TagID > 0 {
			selected = true
		}
		repoName := v.Alias
		if v.Alias == "" {
			repoName = v.RepoName
		}
		r = append(r, TagReposSelected{
			RepoID:   v.RepoID,
			RepoName: repoName,
			Selected: selected,
		})
	}
	return r, nil
}

func GetAllOfficialTagRepos(orgID int64, isOwner bool) ([]TagsDetail, error) {
	result := make([]TagsDetail, 0)
	tags, err := GetAllOfficialTags()
	if err != nil {
		return nil, err
	}
	for _, tag := range tags {
		repos, err := GetOfficialTagDetail(orgID, tag.ID)
		if err != nil {
			return nil, err
		}
		if len(repos) == 0 && !isOwner {
			continue
		}
		result = append(result, TagsDetail{
			TagId:    tag.ID,
			TagName:  tag.Name,
			TagLimit: tag.Limit,
			RepoList: repos,
		})
	}
	return result, nil
}

func GetOfficialTagDetail(orgID, tagId int64) ([]*Repository, error) {
	t := make([]*Repository, 0)
	const SQLCmd = "select t2.* from official_tag_repos t1 inner join repository t2 on t1.repo_id = t2.id where t1.org_id = ? and t1.tag_id=? order by t2.updated_unix desc"

	if err := x.SQL(SQLCmd, orgID, tagId).Find(&t); err != nil {
		return nil, err
	}
	return t, nil
}

func GetAllOfficialTags() ([]OfficialTag, error) {
	//todo  redis?
	o := make([]OfficialTag, 0)
	err := x.Where("status = ?", 0).OrderBy("updated_unix desc").Find(&o)
	if err != nil {
		log.Error("GetAllOfficialTags error,%v", err)
		return nil, err
	}
	return o, nil
}

type FindSelectedReposOpts struct {
	ListOptions
	OrgId      int64
	OnlyPublic bool
}

func GetSelectedRepos(opts FindSelectedReposOpts) ([]*Repository, error) {
	if opts.Page < 1 {
		opts.Page = 1
	}
	var cond = builder.NewCond()
	cond = cond.And(builder.Eq{"official_tag.code": "selected"})
	if opts.OrgId > 0 {
		cond = cond.And(builder.Eq{"official_tag_repos.org_id": opts.OrgId})
	}
	if opts.OnlyPublic {
		cond = cond.And(builder.Eq{"repository.is_private": false})
	}
	t := make([]*Repository, 0)
	err := x.Join("inner", "official_tag_repos", "repository.id = official_tag_repos.repo_id").
		Join("inner", "official_tag", "official_tag.id = official_tag_repos.tag_id").
		Where(cond).OrderBy("repository.updated_unix desc").Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).Find(&t)

	if err != nil {
		return nil, err
	}

	return t, nil
}
