package imagerepo

import (
	"errors"
	"time"

	V1ClusterRepo "github.com/KubeOperator/kubepi/internal/model/v1/clusterrepo"
	V1ImageRepo "github.com/KubeOperator/kubepi/internal/model/v1/imagerepo"
	"github.com/KubeOperator/kubepi/internal/service/v1/common"
	costomStorm "github.com/KubeOperator/kubepi/pkg/storm"
	repoClient "github.com/KubeOperator/kubepi/pkg/util/imagerepo"
	"github.com/KubeOperator/kubepi/pkg/util/imagerepo/repos"
	"github.com/KubeOperator/kubepi/pkg/util/lang"
	"github.com/asdine/storm/v3"
	"github.com/asdine/storm/v3/q"
	"github.com/google/uuid"
)

type Service interface {
	common.DBService
	ListInternalRepos(repo V1ImageRepo.ImageRepo, page, limit int, search string) (names []string, err error)
	Search(num, size int, conditions common.Conditions, options common.DBOptions) (result []V1ImageRepo.ImageRepo, count int, err error)
	Create(repo *V1ImageRepo.ImageRepo, options common.DBOptions) (err error)
	Delete(name string, options common.DBOptions) (err error)
	GetByName(name string, options common.DBOptions) (repo V1ImageRepo.ImageRepo, err error)
	UpdateRepo(name string, repo *V1ImageRepo.ImageRepo, options common.DBOptions) (err error)
	ListByCluster(cluster string, options common.DBOptions) (result []V1ImageRepo.ImageRepo, err error)
	ListImages(repo, cluster string, options common.DBOptions) (names []string, err error)
	ListImagesByRepo(repo string, page, limit int, search, token string, options common.DBOptions) (res V1ImageRepo.RepoResponse, err error)
}

func NewService() Service {
	return &service{}
}

type service struct {
	common.DefaultDBService
}

func (s *service) ListInternalRepos(repo V1ImageRepo.ImageRepo, page, limit int, search string) (names []string, err error) {

	client := repoClient.NewClient(repos.Config{
		Type:     repo.Type,
		EndPoint: repo.EndPoint,
		Credential: repos.Credential{
			Username: repo.Credential.Username,
			Password: repo.Credential.Password,
		},
		Version: repo.Version,
	})
	if client == nil {
		return nil, errors.New("repo client is not found")
	}
	request := repos.ProjectRequest{
		Name:  search,
		Page:  page,
		Limit: limit,
	}

	return client.ListRepos(request)
}

func (s *service) ListImages(repo, cluster string, options common.DBOptions) (names []string, err error) {
	db := s.GetDB(options)
	query := db.Select(q.And(q.Eq("Cluster", cluster), q.Eq("Repo", repo)))
	var cRepo V1ClusterRepo.ClusterRepo
	if err = query.First(&cRepo); err != nil {
		return
	}
	rp, err := s.GetByName(repo, options)
	if err != nil {
		return
	}
	client := repoClient.NewClient(repos.Config{
		Type:     rp.Type,
		EndPoint: rp.EndPoint,
		Credential: repos.Credential{
			Username: rp.Credential.Username,
			Password: rp.Credential.Password,
		},
		Version: rp.Version,
	})
	images, err := client.ListImagesWithoutPage(rp.RepoName)
	if err != nil {
		return
	}
	for _, image := range images {
		names = append(names, rp.DownloadUrl+"/"+image)
	}
	return
}

func (s *service) ListImagesByRepo(repo string, page, limit int, search, token string, options common.DBOptions) (response V1ImageRepo.RepoResponse, err error) {
	rp, err1 := s.GetByName(repo, options)
	if err1 != nil {
		err = err1
		return
	}
	client := repoClient.NewClient(repos.Config{
		Type:     rp.Type,
		EndPoint: rp.EndPoint,
		Credential: repos.Credential{
			Username: rp.Credential.Username,
			Password: rp.Credential.Password,
		},
		Version: rp.Version,
	})
	request := repos.RepoRequest{
		Repo:          rp.RepoName,
		Page:          page,
		Limit:         limit,
		Search:        search,
		ContinueToken: token,
	}
	res, err2 := client.ListImages(request)
	if err2 != nil {
		err = err2
		return
	}
	var names []string
	for _, image := range res.Items {
		names = append(names, rp.DownloadUrl+"/"+image)
	}
	response.Items = names
	response.ContinueToken = res.ContinueToken
	return
}

func (s *service) ListByCluster(cluster string, options common.DBOptions) (result []V1ImageRepo.ImageRepo, err error) {
	db := s.GetDB(options)
	query := db.Select(q.Eq("Cluster", cluster))
	var clusterrepos []V1ClusterRepo.ClusterRepo
	if err = query.Find(&clusterrepos); err != nil && err != storm.ErrNotFound {
		return
	}
	if len(clusterrepos) > 0 {
		group := make([]string, 0)
		for _, repo := range clusterrepos {
			group = append(group, repo.Repo)
		}
		query2 := db.Select(q.Not(q.In("Name", group)))
		if err = query2.Find(&result); err != nil {
			return
		}
	} else {
		if err = db.All(&result); err != nil {
			return
		}
	}
	return
}

func (s *service) Search(num, size int, conditions common.Conditions, options common.DBOptions) (result []V1ImageRepo.ImageRepo, count int, err error) {
	db := s.GetDB(options)
	var ms []q.Matcher
	for k := range conditions {
		if conditions[k].Field == "quick" {
			ms = append(ms, q.Or(
				costomStorm.Like("Name", conditions[k].Value),
			))
		} else {
			field := lang.FirstToUpper(conditions[k].Field)
			value := lang.ParseValueType(conditions[k].Value)

			switch conditions[k].Operator {
			case "eq":
				ms = append(ms, q.Eq(field, value))
			case "ne":
				ms = append(ms, q.Not(q.Eq(field, value)))
			case "like":
				ms = append(ms, costomStorm.Like(field, value.(string)))
			case "not like":
				ms = append(ms, q.Not(costomStorm.Like(field, value.(string))))
			}
		}
	}
	query := db.Select(ms...).OrderBy("CreateAt").Reverse()
	count, err = query.Count(&V1ImageRepo.ImageRepo{})
	if err != nil {
		return
	}
	if size != 0 {
		query.Limit(size).Skip((num - 1) * size)
	}
	if err = query.Find(&result); err != nil {
		return
	}
	return
}

func (s *service) Create(repo *V1ImageRepo.ImageRepo, options common.DBOptions) (err error) {
	db := s.GetDB(options)
	repo.UUID = uuid.New().String()
	repo.CreateAt = time.Now()
	repo.UpdateAt = time.Now()
	return db.Save(repo)
}

func (s *service) Delete(name string, options common.DBOptions) (err error) {
	db := s.GetDB(options)
	item, err1 := s.GetByName(name, options)
	if err1 != nil {
		err = err1
		return
	}
	return db.DeleteStruct(&item)
}

func (s *service) GetByName(name string, options common.DBOptions) (repo V1ImageRepo.ImageRepo, err error) {
	db := s.GetDB(options)
	query := db.Select(q.Eq("Name", name))
	if err = query.First(&repo); err != nil {
		return
	}
	return
}

func (s *service) UpdateRepo(name string, repo *V1ImageRepo.ImageRepo, options common.DBOptions) (err error) {
	db := s.GetDB(options)
	old, err1 := s.GetByName(name, options)
	if err1 != nil {
		err = err1
		return
	}
	repo.UUID = old.UUID
	repo.CreateAt = old.CreateAt
	repo.UpdateAt = time.Now()

	if !old.Auth {
		repo.Credential.Password = ""
		repo.Credential.Username = ""
		repo.Credential = V1ImageRepo.Credential{}
		err = db.UpdateField(repo, "Credential", repo.Credential)
		if err != nil {
			return
		}
	}

	if old.AllowAnonymous != repo.AllowAnonymous {
		err = db.UpdateField(repo, "AllowAnonymous", repo.AllowAnonymous)
		if err != nil {
			return
		}
	}

	if old.Auth != repo.Auth {
		err = db.UpdateField(repo, "Auth", repo.Auth)
		if err != nil {
			return
		}
	}

	return db.Update(repo)
}
