package chaoshub

import (
	"context"
	"errors"
	"fmt"
	"os"
	"strconv"
	"time"

	"github.com/google/uuid"
	"github.com/jinzhu/copier"
	"github.com/litmuschaos/litmus/chaoscenter/graphql/server/graph/model"
	"github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/authorization"
	"github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/chaoshub/handler"
	chaosHubOps "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/chaoshub/ops"
	"github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb"
	dbSchemaChaosHub "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/chaos_hub"
	"github.com/litmuschaos/litmus/chaoscenter/graphql/server/utils"
	log "github.com/sirupsen/logrus"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
)

const (
	timeInterval               = 6 * time.Hour
	DefaultPath                = "/tmp/"
	DefaultHubID               = "6f39cea9-6264-4951-83a8-29976b614289"
	DefaultHubSyncTimeInterval = 6 * time.Hour
)

type Service interface {
	AddChaosHub(ctx context.Context, chaosHub model.CreateChaosHubRequest, projectID string) (*model.ChaosHub, error)
	AddRemoteChaosHub(ctx context.Context, chaosHub model.CreateRemoteChaosHub, projectID string) (*model.ChaosHub, error)
	SaveChaosHub(ctx context.Context, chaosHub model.CreateChaosHubRequest, projectID string) (*model.ChaosHub, error)
	SyncChaosHub(ctx context.Context, hubID string, projectID string) (string, error)
	UpdateChaosHub(ctx context.Context, chaosHub model.UpdateChaosHubRequest, projectID string) (*model.ChaosHub, error)
	DeleteChaosHub(ctx context.Context, hubID string, projectID string) (bool, error)
	ListChaosFaults(ctx context.Context, hubID string, projectID string) ([]*model.Chart, error)
	GetChaosFault(ctx context.Context, request model.ExperimentRequest, projectID string) (*model.FaultDetails, error)
	GetChaosHub(ctx context.Context, chaosHubID string, projectID string) (*model.ChaosHubStatus, error)
	ListChaosHubs(ctx context.Context, projectID string, request *model.ListChaosHubRequest) ([]*model.ChaosHubStatus, error)
	ListPredefinedExperiments(ctx context.Context, hubID string, projectID string) ([]*model.PredefinedExperimentList, error)
	GetPredefinedExperiment(ctx context.Context, hubID string, experiment []string, projectID string) ([]*model.PredefinedExperimentList, error)
	IsChaosHubAvailable(ctx context.Context, name string, projectID string) (bool, error)
	GetAllHubs(ctx context.Context) ([]*model.ChaosHub, error)
	RecurringHubSync()
	SyncDefaultChaosHubs()
	GetChaosHubStats(ctx context.Context, projectID string) (*model.GetChaosHubStatsResponse, error)
}

type chaosHubService struct {
	chaosHubOperator *dbSchemaChaosHub.Operator
}

// NewService returns a new instance of Service
func NewService(chaosHubOperator *dbSchemaChaosHub.Operator) Service {
	return &chaosHubService{
		chaosHubOperator: chaosHubOperator,
	}
}

// AddChaosHub is used for Adding a new ChaosHub
func (c *chaosHubService) AddChaosHub(ctx context.Context, chaosHub model.CreateChaosHubRequest, projectID string) (*model.ChaosHub, error) {
	if IsExist, err := c.IsChaosHubAvailable(ctx, chaosHub.Name, projectID); err != nil {
		return nil, err
	} else if IsExist == true {
		return nil, errors.New("name already exists")
	}
	currentTime := time.Now()
	cloneHub := NewCloningInputFrom(chaosHub)
	description := ""
	if chaosHub.Description != nil {
		description = *chaosHub.Description
	}

	tkn := ctx.Value(authorization.AuthKey).(string)
	username, err := authorization.GetUsername(tkn)
	if err != nil {
		log.Error("error getting username: ", err)
		return nil, err
	}

	newHub := &dbSchemaChaosHub.ChaosHub{
		ID:         uuid.New().String(),
		ProjectID:  projectID,
		RepoURL:    chaosHub.RepoURL,
		RepoBranch: chaosHub.RepoBranch,
		ResourceDetails: mongodb.ResourceDetails{
			Name:        chaosHub.Name,
			Description: description,
			Tags:        chaosHub.Tags,
		},
		IsPrivate:     chaosHub.IsPrivate,
		AuthType:      string(chaosHub.AuthType),
		HubType:       string(model.HubTypeGit),
		Token:         chaosHub.Token,
		UserName:      chaosHub.UserName,
		Password:      chaosHub.Password,
		SSHPrivateKey: chaosHub.SSHPrivateKey,
		SSHPublicKey:  chaosHub.SSHPublicKey,
		Audit: mongodb.Audit{
			CreatedAt: currentTime.UnixMilli(),
			UpdatedAt: currentTime.UnixMilli(),
			IsRemoved: false,
			CreatedBy: mongodb.UserDetailResponse{
				Username: username,
			},
			UpdatedBy: mongodb.UserDetailResponse{
				Username: username,
			},
		},
		LastSyncedAt: time.Now().UnixMilli(),
		IsDefault:    false,
	}

	// Adding the new hub into database with the given username.
	if err := c.chaosHubOperator.CreateChaosHub(ctx, newHub); err != nil {
		log.Error(err)
		return nil, err
	}

	// Cloning the repository at a path from ChaosHub link structure.
	if err := chaosHubOps.GitClone(cloneHub, projectID); err != nil {
		log.Error(err)
	}

	return newHub.GetOutputChaosHub(), nil
}

func (c *chaosHubService) AddRemoteChaosHub(ctx context.Context, chaosHub model.CreateRemoteChaosHub, projectID string) (*model.ChaosHub, error) {
	IsExist, err := c.IsChaosHubAvailable(ctx, chaosHub.Name, projectID)
	if err != nil {
		return nil, err
	}
	if IsExist == true {
		return nil, errors.New("name already exists")
	}
	description := ""
	if chaosHub.Description != nil {
		description = *chaosHub.Description
	}
	currentTime := time.Now()

	tkn := ctx.Value(authorization.AuthKey).(string)
	username, err := authorization.GetUsername(tkn)

	if err != nil {
		log.Error("error getting userID: ", err)
		return nil, err
	}

	newHub := &dbSchemaChaosHub.ChaosHub{
		ID:         uuid.New().String(),
		ProjectID:  projectID,
		RepoURL:    chaosHub.RepoURL,
		RepoBranch: "",
		ResourceDetails: mongodb.ResourceDetails{
			Name:        chaosHub.Name,
			Description: description,
			Tags:        chaosHub.Tags,
		},
		IsPrivate: false,
		HubType:   string(model.HubTypeRemote),
		AuthType:  string(model.AuthTypeNone),
		Audit: mongodb.Audit{
			CreatedAt: currentTime.UnixMilli(),
			UpdatedAt: currentTime.UnixMilli(),
			IsRemoved: false,
			CreatedBy: mongodb.UserDetailResponse{
				Username: username,
			},
			UpdatedBy: mongodb.UserDetailResponse{
				Username: username,
			},
		},
		LastSyncedAt: time.Now().UnixMilli(),
		IsDefault:    false,
	}

	// Adding the new hub into database with the given name.
	err = c.chaosHubOperator.CreateChaosHub(ctx, newHub)
	if err != nil {
		log.Error(err)
		return nil, err
	}

	err = handler.DownloadRemoteHub(chaosHub, projectID)
	if err != nil {
		err = fmt.Errorf("Hub configurations saved successfully. Failed to connect the remote repo: " + err.Error())
		log.Error(err)
		return nil, err
	}

	return newHub.GetOutputChaosHub(), nil
}

// SaveChaosHub is used for Adding a new ChaosHub
func (c *chaosHubService) SaveChaosHub(ctx context.Context, chaosHub model.CreateChaosHubRequest, projectID string) (*model.ChaosHub, error) {

	IsExist, err := c.IsChaosHubAvailable(ctx, chaosHub.Name, projectID)
	if err != nil {
		return nil, err
	}
	if IsExist == true {
		return nil, errors.New("name already exists")
	}

	// Initialize a UID for new Hub.
	uuid := uuid.New()
	tkn := ctx.Value(authorization.AuthKey).(string)
	username, err := authorization.GetUsername(tkn)

	if err != nil {
		log.Error("error getting userID: ", err)
		return nil, err
	}

	description := ""
	if chaosHub.Description != nil {
		description = *chaosHub.Description
	}
	currentTime := time.Now()
	newHub := &dbSchemaChaosHub.ChaosHub{
		ID:         uuid.String(),
		ProjectID:  projectID,
		RepoURL:    chaosHub.RepoURL,
		RepoBranch: chaosHub.RepoBranch,
		ResourceDetails: mongodb.ResourceDetails{
			Name:        chaosHub.Name,
			Description: description,
			Tags:        chaosHub.Tags,
		},
		IsPrivate:     chaosHub.IsPrivate,
		AuthType:      string(chaosHub.AuthType),
		Token:         chaosHub.Token,
		UserName:      chaosHub.UserName,
		Password:      chaosHub.Password,
		SSHPrivateKey: chaosHub.SSHPrivateKey,
		SSHPublicKey:  chaosHub.SSHPublicKey,
		Audit: mongodb.Audit{
			CreatedAt: currentTime.UnixMilli(),
			UpdatedAt: currentTime.UnixMilli(),
			IsRemoved: false,
			CreatedBy: mongodb.UserDetailResponse{
				Username: username,
			},
			UpdatedBy: mongodb.UserDetailResponse{
				Username: username,
			},
		},
		LastSyncedAt: time.Now().UnixMilli(),
	}

	// Adding the new hub into database with the given username without cloning.
	err = c.chaosHubOperator.CreateChaosHub(ctx, newHub)
	if err != nil {
		log.Error(err)
		return nil, err
	}

	return newHub.GetOutputChaosHub(), nil
}

// SyncChaosHub is used for syncing the hub again if some not present or some error happens.
func (c *chaosHubService) SyncChaosHub(ctx context.Context, hubID string, projectID string) (string, error) {
	chaosHub, err := c.chaosHubOperator.GetHubByID(ctx, hubID, projectID)
	if err != nil {
		return "", err
	}

	syncHubInput := model.CloningInput{
		Name:          chaosHub.Name,
		RepoURL:       chaosHub.RepoURL,
		RepoBranch:    chaosHub.RepoBranch,
		IsPrivate:     chaosHub.IsPrivate,
		UserName:      chaosHub.UserName,
		Password:      chaosHub.Password,
		AuthType:      model.AuthType(chaosHub.AuthType),
		Token:         chaosHub.Token,
		SSHPrivateKey: chaosHub.SSHPrivateKey,
		IsDefault:     false,
	}

	time := time.Now().UnixMilli()
	query := bson.D{{"hub_id", hubID}, {"is_removed", false}}
	update := bson.D{{"$set", bson.D{{"last_synced_at", time}}}}

	if chaosHub.HubType == string(model.HubTypeRemote) {
		err = handler.SyncRemoteRepo(syncHubInput, projectID)
		if err != nil {
			return "", err
		}
	} else {
		err = chaosHubOps.GitSyncHandlerForProjects(syncHubInput, projectID)
		if err != nil {
			return "", err
		}
	}
	// Updating the last_synced_at time using hubID
	err = c.chaosHubOperator.UpdateChaosHub(ctx, query, update)
	if err != nil {
		log.Error(err)
		return "", err
	}
	return "Successfully synced ChaosHub", nil
}

func (c *chaosHubService) UpdateChaosHub(ctx context.Context, chaosHub model.UpdateChaosHubRequest, projectID string) (*model.ChaosHub, error) {

	cloneHub := model.CloningInput{
		RepoBranch:    chaosHub.RepoBranch,
		RepoURL:       chaosHub.RepoURL,
		Name:          chaosHub.Name,
		IsPrivate:     chaosHub.IsPrivate,
		UserName:      chaosHub.UserName,
		Password:      chaosHub.Password,
		AuthType:      chaosHub.AuthType,
		Token:         chaosHub.Token,
		SSHPrivateKey: chaosHub.SSHPrivateKey,
		IsDefault:     false,
	}
	prevChaosHub, err := c.chaosHubOperator.GetHubByID(ctx, chaosHub.ID, projectID)
	if err != nil {
		return nil, err
	}
	clonePath := DefaultPath + prevChaosHub.ProjectID + "/" + prevChaosHub.Name
	if prevChaosHub.HubType == string(model.HubTypeRemote) {
		if prevChaosHub.Name != chaosHub.Name || prevChaosHub.RepoURL != chaosHub.RepoURL {
			remoteHub := model.CreateRemoteChaosHub{
				Name:    chaosHub.Name,
				RepoURL: chaosHub.RepoURL,
			}
			err = os.RemoveAll(clonePath)
			if err != nil {
				return nil, err
			}
			err = handler.DownloadRemoteHub(remoteHub, projectID)
			if err != nil {
				return nil, err
			}
		}
	} else {
		// Syncing/Cloning the repository at a path from ChaosHub link structure.
		if prevChaosHub.Name != chaosHub.Name || prevChaosHub.RepoURL != chaosHub.RepoURL || prevChaosHub.RepoBranch != chaosHub.RepoBranch || prevChaosHub.IsPrivate != chaosHub.IsPrivate || prevChaosHub.AuthType != chaosHub.AuthType.String() {
			err = os.RemoveAll(clonePath)
			if err != nil {
				return nil, err
			}
			err = chaosHubOps.GitClone(cloneHub, projectID)
			if err != nil {
				return nil, err
			}
		} else {
			err := chaosHubOps.GitSyncHandlerForProjects(cloneHub, projectID)
			if err != nil {
				return nil, err
			}
		}
	}

	time := time.Now().UnixMilli()
	tkn := ctx.Value(authorization.AuthKey).(string)
	username, err := authorization.GetUsername(tkn)

	query := bson.D{{"hub_id", chaosHub.ID}, {"is_removed", false}}
	update := bson.D{
		{"$set", bson.D{
			{"repo_url", chaosHub.RepoURL},
			{"repo_branch", chaosHub.RepoBranch},
			{"name", chaosHub.Name},
			{"description", chaosHub.Description},
			{"tags", chaosHub.Tags},
			{"is_private", chaosHub.IsPrivate},
			{"auth_type", chaosHub.AuthType},
			{"token", chaosHub.Token},
			{"username", chaosHub.UserName},
			{"password", chaosHub.Password},
			{"ssh_private_key", chaosHub.SSHPrivateKey},
			{"ssh_public_key", chaosHub.SSHPublicKey},
			{"updated_at", time},
			{"updated_by", mongodb.UserDetailResponse{
				Username: username,
			}},
		},
		},
	}

	// Updating the new hub into database with the given username.
	err = c.chaosHubOperator.UpdateChaosHub(ctx, query, update)
	if err != nil {
		log.Error(err)
		return nil, err
	}

	var newChaosHub model.ChaosHub
	copier.Copy(&newChaosHub, &chaosHub)

	newChaosHub.UpdatedAt = strconv.FormatInt(time, 10)

	return &newChaosHub, nil
}

func (c *chaosHubService) DeleteChaosHub(ctx context.Context, hubID string, projectID string) (bool, error) {
	tkn := ctx.Value(authorization.AuthKey).(string)
	username, err := authorization.GetUsername(tkn)
	if err != nil {
		return false, err
	}
	chaosHub, err := c.chaosHubOperator.GetHubByID(ctx, hubID, projectID)
	if err != nil {
		log.Error(err)
		return false, err
	}
	query := bson.D{
		{"hub_id", hubID},
		{"project_id", projectID},
	}
	update := bson.D{
		{"$set", bson.D{
			{"is_removed", true},
			{"updated_at", time.Now().UnixMilli()},
			{"updated_by", mongodb.UserDetailResponse{
				Username: username,
			}},
		},
		},
	}

	err = c.chaosHubOperator.UpdateChaosHub(ctx, query, update)
	if err != nil {
		log.Error(err)
		return false, err
	}
	clonePath := DefaultPath + projectID + "/" + chaosHub.Name
	err = os.RemoveAll(clonePath)
	if err != nil {
		log.Error(err)
		return false, err
	}

	return true, nil
}

// ListChaosFaults is responsible for getting the charts details
func (c *chaosHubService) ListChaosFaults(ctx context.Context, hubID string, projectID string) ([]*model.Chart, error) {

	chartsInput := model.CloningInput{}
	hub, err := c.getChaosHubDetails(ctx, hubID, projectID)
	if err != nil {
		return nil, err
	}
	chartsInput = model.CloningInput{
		Name:       hub.Name,
		RepoURL:    hub.RepoURL,
		RepoBranch: hub.RepoBranch,
	}

	ChartsPath := handler.GetChartsPath(chartsInput, projectID, hub.IsDefault)
	ChartsData, err := handler.GetChartsData(ChartsPath)
	if err != nil {
		return nil, err
	}

	return ChartsData, nil
}

// GetChaosFault is used for getting details of chartserviceversion.yaml.
func (c *chaosHubService) GetChaosFault(ctx context.Context, request model.ExperimentRequest, projectID string) (*model.FaultDetails, error) {
	chaosHub, err := c.getChaosHubDetails(ctx, request.HubID, projectID)
	if err != nil {
		return nil, err
	}
	var basePath string
	if chaosHub.IsDefault {
		basePath = "/tmp/default/" + chaosHub.Name + "/faults/" + request.Category + "/" + request.ExperimentName
	} else {
		basePath = DefaultPath + projectID + "/" + chaosHub.Name + "/faults/" + request.Category + "/" + request.ExperimentName
	}

	//Get fault chartserviceversion.yaml data
	csvPath := basePath + "/" + request.ExperimentName + ".chartserviceversion.yaml"
	csvYaml, err := os.ReadFile(csvPath)
	if err != nil {
		csvYaml = []byte("")
	}

	//Get engine.yaml data
	enginePath := basePath + "/" + "engine.yaml"
	engineYaml, err := os.ReadFile(enginePath)
	if err != nil {
		engineYaml = []byte("")
	}

	//Get fault.yaml data
	faultPath := basePath + "/" + "fault.yaml"
	faultYaml, err := os.ReadFile(faultPath)
	if err != nil {
		faultYaml = []byte("")
	}

	return &model.FaultDetails{
		CSV:    string(csvYaml),
		Engine: string(engineYaml),
		Fault:  string(faultYaml),
	}, nil
}

// ListChaosHubs returns the array of hub details with their current status.
func (c *chaosHubService) ListChaosHubs(ctx context.Context, projectID string, request *model.ListChaosHubRequest) ([]*model.ChaosHubStatus, error) {
	defaultHub := c.listDefaultHubs()
	updatedDefaultHub := dbSchemaChaosHub.ChaosHub{
		ID: defaultHub.ID,
		ResourceDetails: mongodb.ResourceDetails{
			Name: defaultHub.Name,
		},
		RepoURL:    defaultHub.RepoURL,
		RepoBranch: defaultHub.RepoBranch,
		IsDefault:  true,
	}

	var pipeline mongo.Pipeline

	// Match with identifiers
	matchIdentifierStage := bson.D{
		{"$match", bson.D{
			{"project_id", projectID},
			{"is_removed", false},
		}},
	}
	pipeline = append(pipeline, matchIdentifierStage)

	if request != nil {
		// Match the ChaosHub IDs from the input array
		if request.ChaosHubIDs != nil && len(request.ChaosHubIDs) != 0 {
			matchHubIdStage := bson.D{
				{"$match", bson.D{
					{"hub_id", bson.D{
						{"$in", request.ChaosHubIDs},
					}},
				}},
			}

			pipeline = append(pipeline, matchHubIdStage)
		}

		// Filtering based on given parameters
		if request.Filter != nil {
			// Filtering based on chaos_infra name
			if request.Filter.ChaosHubName != nil && *request.Filter.ChaosHubName != "" {
				matchHubNameStage := bson.D{
					{"$match", bson.D{
						{"name", bson.M{
							"$regex": request.Filter.ChaosHubName, "$options": "mix",
						}},
					}},
				}
				pipeline = append(pipeline, matchHubNameStage)
			}
			// Filtering based on description
			if request.Filter.Description != nil && *request.Filter.Description != "" {
				matchDescriptionStage := bson.D{
					{"$match", bson.D{
						{"description", bson.M{
							"$regex": request.Filter.Description, "$options": "mix",
						}},
					}},
				}
				pipeline = append(pipeline, matchDescriptionStage)
			}
			// Filtering based on tags
			if request.Filter.Tags != nil && len(request.Filter.Tags) > 0 {
				matchHubTagsStage := bson.D{
					{"$match", bson.D{
						{"tags", bson.D{
							{"$all", request.Filter.Tags},
						}},
					}},
				}
				pipeline = append(pipeline, matchHubTagsStage)
			}
		}
	}

	var allHubs []dbSchemaChaosHub.ChaosHub

	hubCursor, err := c.chaosHubOperator.GetAggregateChaosHubs(ctx, pipeline)
	if err != nil {
		return nil, err
	}

	err = hubCursor.All(context.Background(), &allHubs)
	if err != nil {
		return nil, err
	}

	var (
		hubDetails []*model.ChaosHubStatus
	)
	allHubs = append([]dbSchemaChaosHub.ChaosHub{updatedDefaultHub}, allHubs...)

	for _, hub := range allHubs {
		sum := 0
		experimentCount := 0
		isConfirmed := false
		var chartPath string
		if hub.IsDefault {
			chartPath = DefaultPath + "default/" + hub.Name + "/faults/"
		} else {
			chartPath = DefaultPath + hub.ProjectID + "/" + hub.Name + "/faults/"
		}
		chartsData, err := handler.GetChartsData(chartPath)
		if err != nil {
			sum = 0
		} else {
			for _, chart := range chartsData {
				sum = sum + len(chart.Spec.Faults)
			}
		}
		wfs, err := c.ListPredefinedExperiments(ctx, hub.ID, projectID)
		if err != nil {
			experimentCount = 0
		}
		experimentCount = len(wfs)

		isConfirmed, err = handler.ValidateLocalRepository(hub)
		if err != nil {
			isConfirmed = false
		}

		hubDesc := hub.Description

		hubDetail := &model.ChaosHubStatus{
			IsAvailable:      isConfirmed,
			ID:               hub.ID,
			RepoURL:          hub.RepoURL,
			IsDefault:        hub.IsDefault,
			IsPrivate:        hub.IsPrivate,
			Name:             hub.Name,
			Description:      &hubDesc,
			RepoBranch:       hub.RepoBranch,
			Tags:             hub.Tags,
			Token:            hub.Token,
			SSHPublicKey:     hub.SSHPublicKey,
			SSHPrivateKey:    hub.SSHPrivateKey,
			AuthType:         model.AuthType(hub.AuthType),
			LastSyncedAt:     strconv.FormatInt(hub.LastSyncedAt, 10),
			TotalFaults:      strconv.Itoa(sum),
			TotalExperiments: strconv.Itoa(experimentCount),
			CreatedAt:        strconv.Itoa(int(hub.CreatedAt)),
			UpdatedAt:        strconv.Itoa(int(hub.UpdatedAt)),
			CreatedBy:        &model.UserDetails{Username: hub.CreatedBy.Username},
			UpdatedBy:        &model.UserDetails{Username: hub.UpdatedBy.Username},
		}
		hubDetails = append(hubDetails, hubDetail)
	}

	return hubDetails, nil
}

// GetChaosHub returns details of a requested hub
func (c *chaosHubService) GetChaosHub(ctx context.Context, chaosHubID string, projectID string) (*model.ChaosHubStatus, error) {

	hub, err := c.chaosHubOperator.GetHubByID(ctx, chaosHubID, projectID)
	if err != nil {
		return &model.ChaosHubStatus{}, errors.New("DB fetch stage error: " + err.Error())
	}

	sum := 0
	experimentCount := 0
	var chartPath string
	if hub.IsDefault {
		chartPath = DefaultPath + "default/" + hub.Name
	} else {
		chartPath = DefaultPath + hub.ProjectID + "/" + hub.Name
	}
	chartsData, err := handler.GetChartsData(chartPath)
	if err != nil {
		sum = 0
	} else {
		for _, chart := range chartsData {
			sum = sum + len(chart.Spec.Faults)
		}
	}
	wfs, err := c.ListPredefinedExperiments(ctx, hub.ID, projectID)
	if err != nil {
		experimentCount = 0
	}
	experimentCount = len(wfs)

	isConfirmed, err := handler.ValidateLocalRepository(hub)
	if err != nil {
		isConfirmed = false
	}

	hubDesc := hub.Description

	hubDetail := &model.ChaosHubStatus{
		IsAvailable:      isConfirmed,
		ID:               hub.ID,
		RepoURL:          hub.RepoURL,
		Name:             hub.Name,
		Description:      &hubDesc,
		RepoBranch:       hub.RepoBranch,
		Tags:             hub.Tags,
		AuthType:         model.AuthType(hub.AuthType),
		LastSyncedAt:     strconv.FormatInt(hub.LastSyncedAt, 10),
		TotalFaults:      strconv.Itoa(sum),
		TotalExperiments: strconv.Itoa(experimentCount),
		CreatedAt:        strconv.Itoa(int(hub.CreatedAt)),
		UpdatedAt:        strconv.Itoa(int(hub.UpdatedAt)),
		CreatedBy:        &model.UserDetails{Username: hub.CreatedBy.Username},
		UpdatedBy:        &model.UserDetails{Username: hub.UpdatedBy.Username},
	}

	return hubDetail, nil
}

func (c *chaosHubService) ListPredefinedExperiments(ctx context.Context, hubID string, projectID string) ([]*model.PredefinedExperimentList, error) {
	hub, err := c.getChaosHubDetails(ctx, hubID, projectID)
	if err != nil {
		return nil, err
	}

	var hubPath string
	if hub.IsDefault {
		hubPath = "/tmp/default/" + hub.Name + "/experiments/"
	} else {
		hubPath = DefaultPath + projectID + "/" + hub.Name + "/experiments/"
	}
	var predefinedWorkflows []*model.PredefinedExperimentList
	files, err := os.ReadDir(hubPath)
	if err != nil {
		return nil, err
	}

	for _, file := range files {
		if file.Name() != "icons" {
			preDefinedWorkflow := c.getPredefinedExperimentDetails(hubPath, file.Name())
			if preDefinedWorkflow.ExperimentCSV != "" {
				predefinedWorkflows = append(predefinedWorkflows, preDefinedWorkflow)
			}
		}
	}
	return predefinedWorkflows, nil
}

func (c *chaosHubService) getChaosHubDetails(ctx context.Context, hubID string, projectID string) (model.ChaosHub, error) {

	defaultHub := c.listDefaultHubs()
	if defaultHub.ID == hubID {
		return *defaultHub, nil
	}

	hub, err := c.chaosHubOperator.GetHubByID(ctx, hubID, projectID)
	if err != nil {
		return model.ChaosHub{}, errors.New("DB fetch stage error: " + err.Error())
	}

	return model.ChaosHub{
		ID:           hub.ID,
		ProjectID:    hub.ProjectID,
		RepoURL:      hub.RepoURL,
		RepoBranch:   hub.RepoBranch,
		AuthType:     model.AuthType(hub.AuthType),
		Name:         hub.Name,
		CreatedAt:    strconv.Itoa(int(hub.CreatedAt)),
		UpdatedAt:    strconv.Itoa(int(hub.UpdatedAt)),
		LastSyncedAt: strconv.FormatInt(hub.LastSyncedAt, 10),
		Tags:         hub.Tags,
		Description:  &hub.Description,
		//TODO util functions for this
		CreatedBy: &model.UserDetails{Username: hub.CreatedBy.Username},
		UpdatedBy: &model.UserDetails{Username: hub.UpdatedBy.Username},
	}, nil
}

func (c *chaosHubService) GetPredefinedExperiment(ctx context.Context, hubID string, experiments []string, projectID string) ([]*model.PredefinedExperimentList, error) {
	hub, err := c.getChaosHubDetails(ctx, hubID, projectID)
	if err != nil {
		return nil, err
	}
	var hubPath string
	if hub.IsDefault {
		hubPath = "/tmp/default/" + hub.Name + "/experiments/"
	} else {
		hubPath = DefaultPath + projectID + "/" + hub.Name + "/experiments/"
	}
	var predefinedWorkflows []*model.PredefinedExperimentList

	for _, experiment := range experiments {
		preDefinedWorkflow := c.getPredefinedExperimentDetails(hubPath, experiment)
		if preDefinedWorkflow.ExperimentCSV != "" {
			predefinedWorkflows = append(predefinedWorkflows, preDefinedWorkflow)
		}
	}

	return predefinedWorkflows, nil
}

func (c *chaosHubService) getPredefinedExperimentDetails(experimentsPath string, experiment string) *model.PredefinedExperimentList {
	var (
		csvManifest        = ""
		workflowManifest   = ""
		path               = experimentsPath + experiment + "/" + experiment + ".chartserviceversion.yaml"
		isExist            = true
		preDefinedWorkflow = &model.PredefinedExperimentList{}
	)
	_, err := os.Stat(path)
	if err != nil {
		isExist = false
	}

	if isExist {
		yamlData, err := os.ReadFile(experimentsPath + experiment + "/" + experiment + ".chartserviceversion.yaml")
		if err != nil {
			csvManifest = ""
		}

		csvManifest = string(yamlData)

		yamlData, err = os.ReadFile(experimentsPath + experiment + "/" + "experiment.yaml")
		if err != nil {
			workflowManifest = ""
		}

		workflowManifest = string(yamlData)

		preDefinedWorkflow = &model.PredefinedExperimentList{
			ExperimentName:     experiment,
			ExperimentManifest: workflowManifest,
			ExperimentCSV:      csvManifest,
		}
	}

	return preDefinedWorkflow
}

// IsChaosHubAvailable is used for checking if hub already exist or not
func (c *chaosHubService) IsChaosHubAvailable(ctx context.Context, name string, projectID string) (bool, error) {
	chaosHubs, err := c.chaosHubOperator.GetChaosHubByProjectID(ctx, projectID)
	if err != nil {
		return true, err
	}

	for _, n := range chaosHubs {
		if n.Name == name {
			return true, nil
		}
	}
	return false, nil
}

// GetAllHubs ...
func (c *chaosHubService) GetAllHubs(ctx context.Context) ([]*model.ChaosHub, error) {

	chaosHubs, err := c.chaosHubOperator.GetHubs(ctx)
	if err != nil {
		return nil, err
	}

	var outputChaosHubs []*model.ChaosHub
	for _, chaosHub := range chaosHubs {
		outputChaosHubs = append(outputChaosHubs, chaosHub.GetOutputChaosHub())
	}

	return outputChaosHubs, nil
}

// RecurringHubSync is used for syncing
func (c *chaosHubService) RecurringHubSync() {
	for {
		// Started Syncing of hubs
		chaosHubs, _ := c.GetAllHubs(context.Background())

		for _, chaosHub := range chaosHubs {
			if !chaosHub.IsRemoved {
				chartsInput := model.CloningInput{
					Name:          chaosHub.Name,
					RepoURL:       chaosHub.RepoURL,
					RepoBranch:    chaosHub.RepoBranch,
					IsPrivate:     chaosHub.IsPrivate,
					AuthType:      chaosHub.AuthType,
					Token:         chaosHub.Token,
					UserName:      chaosHub.UserName,
					Password:      chaosHub.Password,
					SSHPrivateKey: chaosHub.SSHPrivateKey,
					IsDefault:     false,
				}
				if chaosHub.HubType != model.HubTypeRemote {
					err := chaosHubOps.GitSyncHandlerForProjects(chartsInput, chaosHub.ProjectID)
					if err != nil {
						log.Error(err)
					}
				} else {
					err := handler.SyncRemoteRepo(chartsInput, chaosHub.ProjectID)
					if err != nil {
						log.Error(err)
					}
				}
			}
		}

		// Syncing Completed
		time.Sleep(timeInterval)
	}
}

// GetChaosHubStats returns stats related to Chaos Hubs
func (c *chaosHubService) GetChaosHubStats(ctx context.Context, projectID string) (*model.GetChaosHubStatsResponse, error) {

	var pipeline mongo.Pipeline
	// Match with identifiers
	matchIdentifierStage := bson.D{
		{"$match", bson.D{
			{"project_id", projectID},
			{"is_removed", false},
		}},
	}

	facetStage := bson.D{
		{"$facet", bson.D{
			{"total_chaos_hubs", bson.A{
				matchIdentifierStage,
				bson.D{{"$count", "count"}},
			}},
		}},
	}

	pipeline = append(pipeline, facetStage)
	// Call aggregation on pipeline
	hubCursor, err := c.chaosHubOperator.GetAggregateChaosHubs(ctx, pipeline)
	if err != nil {
		return nil, err
	}
	var res []dbSchemaChaosHub.AggregatedChaosHubStats

	if err = hubCursor.All(ctx, &res); err != nil || len(res) == 0 || len(res[0].TotalChaosHubs) == 0 {
		return &model.GetChaosHubStatsResponse{
			TotalChaosHubs: 1,
		}, err
	}

	return &model.GetChaosHubStatsResponse{
		TotalChaosHubs: res[0].TotalChaosHubs[0].Count + 1,
	}, nil

}

func (c *chaosHubService) listDefaultHubs() *model.ChaosHub {
	defaultHubs := &model.ChaosHub{
		ID:         DefaultHubID,
		Name:       "Litmus ChaosHub",
		RepoURL:    utils.Config.DefaultHubGitURL,
		RepoBranch: utils.Config.DefaultHubBranchName,
		IsDefault:  true,
	}
	return defaultHubs
}

func (c *chaosHubService) SyncDefaultChaosHubs() {
	log.Infof("syncing default chaos hub directories")
	for {
		defaultHub := c.listDefaultHubs()

		chartsInput := model.CloningInput{
			Name:       defaultHub.Name,
			RepoURL:    defaultHub.RepoURL,
			RepoBranch: defaultHub.RepoBranch,
			IsDefault:  true,
		}
		err := chaosHubOps.GitSyncDefaultHub(chartsInput)
		if err != nil {
			log.WithFields(log.Fields{
				"repoUrl":    defaultHub.RepoURL,
				"repoBranch": defaultHub.RepoBranch,
				"hubName":    defaultHub.Name,
			}).WithError(err).Error("failed to sync default chaos hubs")
		}
		// Syncing Completed
		time.Sleep(DefaultHubSyncTimeInterval)
	}
}
