package dao

import (
	"context"
	"errors"
	"fmt"
	"net/url"
	"regexp"
	"strings"
	"time"

	"github.com/content-services/content-sources-backend/pkg/api"
	"github.com/content-services/content-sources-backend/pkg/clients/candlepin_client"
	"github.com/content-services/content-sources-backend/pkg/clients/feature_service_client"
	"github.com/content-services/content-sources-backend/pkg/clients/pulp_client"
	"github.com/content-services/content-sources-backend/pkg/config"
	ce "github.com/content-services/content-sources-backend/pkg/errors"
	"github.com/content-services/content-sources-backend/pkg/event"
	"github.com/content-services/content-sources-backend/pkg/models"
	"github.com/jackc/pgx/v5/pgconn"
	"golang.org/x/exp/slices"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

type templateDaoImpl struct {
	db         *gorm.DB
	pulpClient pulp_client.PulpClient
	fsClient   feature_service_client.FeatureServiceClient
}

func GetTemplateDao(db *gorm.DB, pulpClient pulp_client.PulpClient, fsClient feature_service_client.FeatureServiceClient) TemplateDao {
	return &templateDaoImpl{
		db:         db,
		pulpClient: pulpClient,
		fsClient:   fsClient,
	}
}

func TemplateDBToApiError(e error, uuid *string) *ce.DaoError {
	var dupKeyName string
	if e == nil {
		return nil
	}

	pgError, ok := e.(*pgconn.PgError)
	if ok {
		if pgError.Code == "23505" {
			switch pgError.ConstraintName {
			case "name_org_id_not_deleted_unique":
				dupKeyName = "name"
			}
			return &ce.DaoError{AlreadyExists: true, Message: "Template with this " + dupKeyName + " already belongs to organization"}
		}
		if pgError.Code == "22021" {
			return &ce.DaoError{BadValidation: true, Message: "Request parameters contain invalid syntax"}
		}
	}
	dbError, ok := e.(models.Error)
	if ok {
		daoError := ce.DaoError{BadValidation: dbError.Validation, Message: dbError.Message}
		daoError.Wrap(e)
		return &daoError
	}
	daoError := ce.DaoError{}
	if errors.Is(e, gorm.ErrRecordNotFound) {
		msg := "Template not found"
		if uuid != nil {
			msg = fmt.Sprintf("Template with UUID %s not found", *uuid)
		}
		daoError = ce.DaoError{
			Message:  msg,
			NotFound: true,
		}
	} else {
		daoError = ce.DaoError{
			Message:  e.Error(),
			NotFound: ce.HttpCodeForDaoError(e) == 404, // Check if isNotFoundError
		}
	}
	daoError.Wrap(e)
	return &daoError
}

func (t templateDaoImpl) Create(ctx context.Context, reqTemplate api.TemplateRequest) (api.TemplateResponse, error) {
	var resp api.TemplateResponse
	var err error

	_ = t.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		resp, err = t.create(ctx, tx, reqTemplate)
		return err
	})
	return resp, err
}

func (t templateDaoImpl) create(ctx context.Context, tx *gorm.DB, reqTemplate api.TemplateRequest) (api.TemplateResponse, error) {
	var modelTemplate models.Template
	var respTemplate api.TemplateResponse

	if err := t.validateRepositoryUUIDs(ctx, *reqTemplate.OrgID, reqTemplate.RepositoryUUIDS); err != nil {
		return api.TemplateResponse{}, err
	}

	// Create a template
	templatesCreateApiToModel(reqTemplate, &modelTemplate)
	err := tx.Create(&modelTemplate).Error
	if err != nil {
		return api.TemplateResponse{}, TemplateDBToApiError(err, nil)
	}

	// Associate the template to repositories
	if reqTemplate.RepositoryUUIDS == nil {
		return api.TemplateResponse{}, &ce.DaoError{
			Message:       "template must include repository uuids",
			BadValidation: true,
		}
	}

	err = t.insertTemplateRepoConfigsAndSnapshots(tx, ctx, *reqTemplate.OrgID, modelTemplate, reqTemplate.RepositoryUUIDS)
	if err != nil {
		return api.TemplateResponse{}, err
	}

	templatesModelToApi(modelTemplate, &respTemplate)
	respTemplate.RepositoryUUIDS = reqTemplate.RepositoryUUIDS

	event.SendTemplateEvent(*reqTemplate.OrgID, event.TemplateCreated, []api.TemplateResponse{respTemplate})

	return respTemplate, nil
}

func (t templateDaoImpl) validateRepositoryUUIDs(ctx context.Context, orgId string, uuids []string) error {
	var count int64
	resp := t.db.WithContext(ctx).Model(models.RepositoryConfiguration{}).Where("org_id = ? or org_id = ?", orgId, config.RedHatOrg).Where("uuid in ?", UuidifyStrings(uuids)).Count(&count)
	if resp.Error != nil {
		return fmt.Errorf("could not query repository uuids: %w", resp.Error)
	}
	if count != int64(len(uuids)) {
		return &ce.DaoError{NotFound: true, Message: "One or more Repository UUIDs was invalid."}
	}

	var snapshotCount int64
	resp = t.db.WithContext(ctx).Model(models.RepositoryConfiguration{}).
		Where("org_id = ? or org_id = ?", orgId, config.RedHatOrg).
		Where("uuid in ?", UuidifyStrings(uuids)).
		Where("last_snapshot_uuid IS NOT NULL").
		Count(&snapshotCount)
	if resp.Error != nil {
		return fmt.Errorf("could not query repository uuids: %w", resp.Error)
	}
	if snapshotCount != int64(len(uuids)) {
		return &ce.DaoError{NotFound: true, Message: "One or more repositories does not have a snapshot."}
	}

	return nil
}

func (t templateDaoImpl) insertTemplateRepoConfigsAndSnapshots(tx *gorm.DB, ctx context.Context, orgId string, template models.Template, repoUUIDs []string) error {
	templateRepoConfigs := make([]models.TemplateRepositoryConfiguration, len(repoUUIDs))

	var templateDate time.Time
	if template.UseLatest {
		templateDate = time.Now()
	} else {
		templateDate = template.Date
	}

	sDao := snapshotDaoImpl{db: tx}
	req := api.ListSnapshotByDateRequest{Date: templateDate, RepositoryUUIDS: repoUUIDs}
	snapshots, err := sDao.FetchSnapshotsModelByDateAndRepository(ctx, orgId, req)
	if err != nil {
		return err
	}

	for i, repo := range repoUUIDs {
		snapIndex := slices.IndexFunc(snapshots, func(s models.Snapshot) bool {
			return s.RepositoryConfigurationUUID == repo
		})

		templateRepoConfigs[i].TemplateUUID = template.UUID
		templateRepoConfigs[i].RepositoryConfigurationUUID = repo
		templateRepoConfigs[i].SnapshotUUID = snapshots[snapIndex].UUID
	}

	err = tx.Clauses(clause.OnConflict{
		Columns:   []clause.Column{{Name: "template_uuid"}, {Name: "repository_configuration_uuid"}},
		DoUpdates: clause.AssignmentColumns([]string{"deleted_at", "snapshot_uuid"}),
	}).Create(&templateRepoConfigs).Error
	if err != nil {
		return TemplateDBToApiError(err, nil)
	}
	return nil
}

func (t templateDaoImpl) DeleteTemplateRepoConfigs(ctx context.Context, templateUUID string, keepRepoConfigUUIDs []string) error {
	err := t.db.WithContext(ctx).Unscoped().Where("template_uuid = ? AND repository_configuration_uuid not in ?", UuidifyString(templateUUID), UuidifyStrings(keepRepoConfigUUIDs)).
		Delete(models.TemplateRepositoryConfiguration{}).Error

	if err != nil {
		return TemplateDBToApiError(err, nil)
	}
	return nil
}

func (t templateDaoImpl) softDeleteTemplateRepoConfigs(tx *gorm.DB, templateUUID string, keepRepoConfigUUIDs []string) error {
	err := tx.Debug().Where("template_uuid = ? AND repository_configuration_uuid not in ?", UuidifyString(templateUUID), UuidifyStrings(keepRepoConfigUUIDs)).
		Delete(&models.TemplateRepositoryConfiguration{}).Error

	if err != nil {
		return TemplateDBToApiError(err, nil)
	}
	return nil
}

func (t templateDaoImpl) Fetch(ctx context.Context, orgID string, uuid string, includeSoftDel bool) (api.TemplateResponse, error) {
	modelTemplate, err := t.fetch(ctx, orgID, uuid, includeSoftDel)
	if err != nil {
		return api.TemplateResponse{}, err
	}
	pulpContentPath := ""
	if config.Get().Features.Snapshots.Enabled {
		var err error
		pulpContentPath, err = t.pulpClient.GetContentPath(ctx)
		if err != nil {
			return api.TemplateResponse{}, err
		}
	}
	lastSnapshotUUIDs, err := t.fetchLatestSnapshotUUIDsForReposOfTemplates(ctx, []models.Template{modelTemplate})
	if err != nil {
		return api.TemplateResponse{}, TemplateDBToApiError(err, nil)
	}
	return templatesConvertToResponses([]models.Template{modelTemplate}, lastSnapshotUUIDs, pulpContentPath)[0], nil
}

func (t templateDaoImpl) fetch(ctx context.Context, orgID string, uuid string, includeSoftDel bool) (models.Template, error) {
	var modelTemplate models.Template
	query := t.db.WithContext(ctx)
	if includeSoftDel {
		query = query.Unscoped()
	}
	err := query.Where("uuid = ? AND org_id = ?", UuidifyString(uuid), orgID).
		Preload("TemplateRepositoryConfigurations.Snapshot.RepositoryConfiguration").
		Preload("LastUpdateTask").
		First(&modelTemplate).Error
	if err != nil {
		return modelTemplate, TemplateDBToApiError(err, &uuid)
	}
	return modelTemplate, nil
}

func (t templateDaoImpl) Update(ctx context.Context, orgID string, uuid string, templParams api.TemplateUpdateRequest) (api.TemplateResponse, error) {
	var resp api.TemplateResponse
	var err error

	err = t.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		return t.update(ctx, tx, orgID, uuid, templParams)
	})

	if err != nil {
		return resp, err
	}

	resp, err = t.Fetch(ctx, orgID, uuid, false)
	if err != nil {
		return resp, err
	}

	event.SendTemplateEvent(orgID, event.TemplateUpdated, []api.TemplateResponse{resp})

	return resp, err
}

func (t templateDaoImpl) update(ctx context.Context, tx *gorm.DB, orgID string, uuid string, templParams api.TemplateUpdateRequest) error {
	dbTempl, err := t.fetch(ctx, orgID, uuid, false)
	if err != nil {
		return err
	}

	templatesUpdateApiToModel(templParams, &dbTempl)

	// copy fields to validate before updating the template
	validateTemplate := models.Template{
		Name:      dbTempl.Name,
		OrgID:     dbTempl.OrgID,
		Arch:      dbTempl.Arch,
		UseLatest: dbTempl.UseLatest,
		Version:   dbTempl.Version,
		Date:      dbTempl.Date,
	}

	tx = tx.WithContext(ctx)
	if err := tx.Model(&validateTemplate).Where("uuid = ?", UuidifyString(uuid)).Updates(dbTempl.MapForUpdate()).Error; err != nil {
		return TemplateDBToApiError(err, &uuid)
	}

	var existingRepoConfigUUIDs []string
	if err := tx.Model(&models.TemplateRepositoryConfiguration{}).Select("repository_configuration_uuid").Where("template_uuid = ?", dbTempl.UUID).Find(&existingRepoConfigUUIDs).Error; err != nil {
		return RepositoryDBErrorToApi(err, nil)
	}

	if templParams.RepositoryUUIDS != nil {
		if err := t.validateRepositoryUUIDs(ctx, orgID, templParams.RepositoryUUIDS); err != nil {
			return err
		}

		err = t.softDeleteTemplateRepoConfigs(tx, uuid, templParams.RepositoryUUIDS)
		if err != nil {
			return fmt.Errorf("could not remove uneeded template repositories %w", err)
		}

		err = t.insertTemplateRepoConfigsAndSnapshots(tx, ctx, dbTempl.OrgID, dbTempl, templParams.RepositoryUUIDS)
		if err != nil {
			return fmt.Errorf("could not insert new template repositories %w", err)
		}
	}
	return nil
}

func (t templateDaoImpl) List(ctx context.Context, orgID string, includeSoftDel bool, paginationData api.PaginationData, filterData api.TemplateFilterData) (api.TemplateCollectionResponse, int64, error) {
	var totalTemplates int64
	templates := make([]models.Template, 0)

	filteredDB := t.filteredDbForList(orgID, t.db.WithContext(ctx), filterData)
	if includeSoftDel {
		filteredDB = filteredDB.Unscoped()
	}

	sortMap := map[string]string{
		"name":    "name",
		"url":     "url",
		"arch":    "arch",
		"version": "version",
	}

	order := convertSortByToSQL(paginationData.SortBy, sortMap, "name asc")

	// Get count
	if filteredDB.
		Model(&templates).
		Distinct("uuid").
		Count(&totalTemplates).Error != nil {
		return api.TemplateCollectionResponse{}, totalTemplates, TemplateDBToApiError(filteredDB.Error, nil)
	}

	if filteredDB.
		Distinct("templates.*").
		Preload("TemplateRepositoryConfigurations").
		Preload("LastUpdateTask").
		Order(order).
		Limit(paginationData.Limit).
		Offset(paginationData.Offset).
		Find(&templates).Error != nil {
		return api.TemplateCollectionResponse{}, totalTemplates, TemplateDBToApiError(filteredDB.Error, nil)
	}

	pulpContentPath := ""
	if config.Get().Features.Snapshots.Enabled {
		var err error
		pulpContentPath, err = t.pulpClient.GetContentPath(ctx)
		if err != nil {
			return api.TemplateCollectionResponse{}, 0, err
		}
	}
	lastSnapshotUUIDs, err := t.fetchLatestSnapshotUUIDsForReposOfTemplates(ctx, templates)
	if err != nil {
		return api.TemplateCollectionResponse{}, totalTemplates, TemplateDBToApiError(err, nil)
	}
	responses := templatesConvertToResponses(templates, lastSnapshotUUIDs, pulpContentPath)

	return api.TemplateCollectionResponse{Data: responses}, totalTemplates, nil
}

func (t templateDaoImpl) InternalOnlyFetchByName(ctx context.Context, name string) (models.Template, error) {
	var modelTemplate models.Template
	err := t.db.WithContext(ctx).
		Where("name = ? ", name).
		Preload("TemplateRepositoryConfigurations").First(&modelTemplate).Error
	if err != nil {
		return modelTemplate, TemplateDBToApiError(err, nil)
	}
	return modelTemplate, nil
}

func (t templateDaoImpl) filteredDbForList(orgID string, filteredDB *gorm.DB, filterData api.TemplateFilterData) *gorm.DB {
	filteredDB = filteredDB.Where("org_id = ? ", orgID).Preload("TemplateRepositoryConfigurations.Snapshot.RepositoryConfiguration")

	if filterData.Name != "" {
		filteredDB = filteredDB.Where("name = ?", filterData.Name)
	}
	if filterData.Arch != "" {
		filteredDB = filteredDB.Where("arch = ?", filterData.Arch)
	}
	if filterData.Version != "" {
		filteredDB = filteredDB.Where("version = ?", filterData.Version)
	}
	if filterData.Search != "" {
		containsSearch := "%" + filterData.Search + "%"
		filteredDB = filteredDB.
			Where("name LIKE ?", containsSearch)
	}

	if len(filterData.RepositoryUUIDs) > 0 || len(filterData.SnapshotUUIDs) > 0 {
		filteredDB = filteredDB.
			Joins("INNER JOIN templates_repository_configurations on templates_repository_configurations.template_uuid = templates.uuid")
	}
	if len(filterData.RepositoryUUIDs) > 0 && len(filterData.SnapshotUUIDs) > 0 {
		filteredDB = filteredDB.
			Where("templates_repository_configurations.repository_configuration_uuid in ?", UuidifyStrings(filterData.RepositoryUUIDs)).
			Or("templates_repository_configurations.snapshot_uuid in ?", UuidifyStrings(filterData.SnapshotUUIDs))
	} else if len(filterData.RepositoryUUIDs) > 0 {
		filteredDB = filteredDB.
			Where("templates_repository_configurations.repository_configuration_uuid in ?", UuidifyStrings(filterData.RepositoryUUIDs))
	} else if len(filterData.SnapshotUUIDs) > 0 {
		filteredDB = filteredDB.
			Where("templates_repository_configurations.snapshot_uuid in ?", UuidifyStrings(filterData.SnapshotUUIDs))
	}

	if filterData.UseLatest {
		filteredDB = filteredDB.Where("use_latest = ?", filterData.UseLatest)
	}
	return filteredDB
}

func (t templateDaoImpl) SoftDelete(ctx context.Context, orgID string, uuid string) error {
	var modelTemplate models.Template

	err := t.db.WithContext(ctx).Where("uuid = ? AND org_id = ?", UuidifyString(uuid), orgID).First(&modelTemplate).Error
	if err != nil {
		return TemplateDBToApiError(err, &uuid)
	}

	if err = t.db.WithContext(ctx).Delete(&modelTemplate).Error; err != nil {
		return err
	}

	var resp api.TemplateResponse
	templatesModelToApi(modelTemplate, &resp)
	event.SendTemplateEvent(orgID, event.TemplateDeleted, []api.TemplateResponse{resp})

	return nil
}

func (t templateDaoImpl) Delete(ctx context.Context, orgID string, uuid string) error {
	var modelTemplate models.Template

	err := t.db.WithContext(ctx).Unscoped().Where("uuid = ? AND org_id = ?", UuidifyString(uuid), orgID).First(&modelTemplate).Error
	if err != nil {
		return TemplateDBToApiError(err, &uuid)
	}

	if err = t.db.WithContext(ctx).Unscoped().Delete(&modelTemplate).Error; err != nil {
		return err
	}

	return nil
}

func (t templateDaoImpl) ClearDeletedAt(ctx context.Context, orgID string, uuid string) error {
	var modelTemplate models.Template

	err := t.db.WithContext(ctx).Unscoped().Where("uuid = ? AND org_id = ?", UuidifyString(uuid), orgID).First(&modelTemplate).Error
	if err != nil {
		return err
	}

	err = t.db.WithContext(ctx).Unscoped().Model(&modelTemplate).Update("deleted_at", nil).Error
	if err != nil {
		return err
	}

	return nil
}

// GetRepoChanges given a template UUID and a slice of repo config uuids, returns the added/removed/unchanged/all between the existing and given repos
func (t templateDaoImpl) GetRepoChanges(ctx context.Context, templateUUID string, newRepoConfigUUIDs []string) ([]string, []string, []string, []string, error) {
	var templateRepoConfigs []models.TemplateRepositoryConfiguration
	if err := t.db.WithContext(ctx).Model(&models.TemplateRepositoryConfiguration{}).Unscoped().Where("template_uuid = ?", templateUUID).Find(&templateRepoConfigs).Error; err != nil {
		return nil, nil, nil, nil, TemplateDBToApiError(err, nil)
	}

	// if the repo is being added, it's in the request and the distribution_href is nil
	// if the repo is already part of the template, it's in request and distribution_href is not nil
	// if the repo is being removed, it's not in request but is in the table
	var added, unchanged, removed, all []string
	for _, v := range templateRepoConfigs {
		if v.DistributionHref == "" && slices.Contains(newRepoConfigUUIDs, v.RepositoryConfigurationUUID) {
			added = append(added, v.RepositoryConfigurationUUID)
		} else if slices.Contains(newRepoConfigUUIDs, v.RepositoryConfigurationUUID) {
			unchanged = append(unchanged, v.RepositoryConfigurationUUID)
		} else {
			removed = append(removed, v.RepositoryConfigurationUUID)
		}
		all = append(all, v.RepositoryConfigurationUUID)
	}

	return added, removed, unchanged, all, nil
}

func (t templateDaoImpl) GetDistributionHref(ctx context.Context, templateUUID string, repoConfigUUID string) (string, error) {
	var distributionHref string
	err := t.db.WithContext(ctx).Model(&models.TemplateRepositoryConfiguration{}).Select("distribution_href").Unscoped().Where("template_uuid = ? AND repository_configuration_uuid =  ?", templateUUID, repoConfigUUID).Find(&distributionHref).Error
	if err != nil {
		return "", err
	}
	return distributionHref, nil
}

func (t templateDaoImpl) UpdateDistributionHrefs(ctx context.Context, templateUUID string, repoUUIDs []string, snapshots []models.Snapshot, repoDistributionMap map[string]string) error {
	templateRepoConfigs := make([]models.TemplateRepositoryConfiguration, len(repoUUIDs))
	for i, repo := range repoUUIDs {
		snapIndex := slices.IndexFunc(snapshots, func(s models.Snapshot) bool {
			return s.RepositoryConfigurationUUID == repo
		})

		templateRepoConfigs[i].TemplateUUID = templateUUID
		templateRepoConfigs[i].RepositoryConfigurationUUID = repo
		templateRepoConfigs[i].SnapshotUUID = snapshots[snapIndex].UUID
		if repoDistributionMap != nil {
			templateRepoConfigs[i].DistributionHref = repoDistributionMap[repo]
		}
	}

	err := t.db.WithContext(ctx).Clauses(clause.OnConflict{
		Columns:   []clause.Column{{Name: "template_uuid"}, {Name: "repository_configuration_uuid"}},
		DoUpdates: clause.AssignmentColumns([]string{"distribution_href"}),
	}).Create(&templateRepoConfigs).Error
	if err != nil {
		return TemplateDBToApiError(err, nil)
	}
	return nil
}

func (t templateDaoImpl) SetEnvironmentCreated(ctx context.Context, templateUUID string) error {
	result := t.db.WithContext(ctx).Exec(`
			UPDATE templates
			SET rhsm_environment_created = true 			
			WHERE uuid = ?`,
		templateUUID,
	)

	if result.Error != nil {
		return result.Error
	}
	return nil
}

func (t templateDaoImpl) UpdateLastUpdateTask(ctx context.Context, taskUUID string, orgID string, templateUUID string) error {
	result := t.db.WithContext(ctx).Exec(`
			UPDATE templates
			SET last_update_task_uuid = ? 
			WHERE org_id = ?
			AND uuid = ?`,
		taskUUID,
		orgID,
		templateUUID,
	)

	if result.Error != nil {
		return result.Error
	}
	return nil
}

func (t templateDaoImpl) UpdateLastError(ctx context.Context, orgID string, templateUUID string, lastUpdateSnapshotError string) error {
	result := t.db.WithContext(ctx).Exec(`
			UPDATE templates
			SET last_update_snapshot_error = ? 
			WHERE org_id = ?
			AND uuid = ?`,
		lastUpdateSnapshotError,
		orgID,
		templateUUID,
	)

	if result.Error != nil {
		return result.Error
	}
	return nil
}

func (t templateDaoImpl) UpdateSnapshots(ctx context.Context, templateUUID string, repoUUIDs []string, snapshots []models.Snapshot) error {
	var templateRepoConfigs []models.TemplateRepositoryConfiguration

	for _, repo := range repoUUIDs {
		snapIndex := slices.IndexFunc(snapshots, func(s models.Snapshot) bool {
			return s.RepositoryConfigurationUUID == repo
		})

		templateRepoConfigs = append(templateRepoConfigs, models.TemplateRepositoryConfiguration{
			TemplateUUID:                templateUUID,
			RepositoryConfigurationUUID: repo,
			SnapshotUUID:                snapshots[snapIndex].UUID,
		})
	}

	if len(templateRepoConfigs) > 0 {
		err := t.db.WithContext(ctx).Clauses(clause.OnConflict{
			Columns:   []clause.Column{{Name: "template_uuid"}, {Name: "repository_configuration_uuid"}},
			DoUpdates: clause.AssignmentColumns([]string{"snapshot_uuid"}),
		}).Create(&templateRepoConfigs).Error
		if err != nil {
			return TemplateDBToApiError(err, nil)
		}
	}
	return nil
}

func (t templateDaoImpl) DeleteTemplateSnapshot(ctx context.Context, snapshotUUID string) error {
	err := t.db.WithContext(ctx).Unscoped().Where("snapshot_uuid = ?", UuidifyString(snapshotUUID)).
		Delete(models.TemplateRepositoryConfiguration{}).Error

	if err != nil {
		return TemplateDBToApiError(err, nil)
	}
	return nil
}

func (t templateDaoImpl) GetRepositoryConfigurationFile(ctx context.Context, orgID, templateUUID string) (string, error) {
	sDao := snapshotDaoImpl(t)
	rcDao := repositoryConfigDaoImpl{db: t.db, fsClient: t.fsClient}

	template, err := t.Fetch(ctx, orgID, templateUUID, false)
	if err != nil {
		return "", err
	}

	pc := t.pulpClient
	contentPath, err := pc.GetContentPath(ctx)
	if err != nil {
		return "", err
	}

	var templateRepoConfigFile strings.Builder
	for _, snap := range template.Snapshots {
		repoConfig, err := rcDao.fetchRepoConfig(ctx, orgID, snap.RepositoryUUID, true)
		if err != nil {
			return "", err
		}

		repoConfigFile, err := sDao.GetRepositoryConfigurationFile(ctx, orgID, snap.UUID, false)
		if err != nil {
			return "", err
		}

		var contentURL string
		domain := strings.Split(snap.RepositoryPath, "/")[0]
		parsedRepoURL, err := url.Parse(repoConfig.Repository.URL)
		if err != nil {
			return "", err
		}
		path := parsedRepoURL.Path
		if repoConfig.IsRedHat() {
			contentURL = contentPath + domain + "/templates/" + templateUUID + path
		} else {
			contentURL = contentPath + domain + "/templates/" + templateUUID + "/" + snap.RepositoryUUID
		}

		// replace baseurl with the one specific to the template
		re, err := regexp.Compile(`(?m)^baseurl=.*`)
		if err != nil {
			return "", err
		}
		if !re.MatchString(repoConfigFile) {
			return "", fmt.Errorf("baseurl not found in config file")
		}
		repoConfigFile = re.ReplaceAllString(repoConfigFile, fmt.Sprintf("baseurl=%s", contentURL))

		templateRepoConfigFile.WriteString(repoConfigFile)
		templateRepoConfigFile.WriteString("\n")
	}
	return templateRepoConfigFile.String(), nil
}

func (t templateDaoImpl) InternalOnlyGetTemplatesForRepoConfig(ctx context.Context, repoUUID string, useLatestOnly bool) ([]api.TemplateResponse, error) {
	var templates []models.Template
	filtered := t.db.Model(&models.Template{}).WithContext(ctx).
		Joins("INNER JOIN templates_repository_configurations on templates_repository_configurations.template_uuid = templates.uuid").
		Where("templates_repository_configurations.repository_configuration_uuid", repoUUID)
	if useLatestOnly {
		filtered = filtered.Where("use_latest = true")
	}
	res := filtered.Find(&templates)
	if res.Error != nil {
		return nil, TemplateDBToApiError(res.Error, nil)
	}

	pulpContentPath := ""
	if config.Get().Features.Snapshots.Enabled {
		var err error
		pulpContentPath, err = t.pulpClient.GetContentPath(ctx)
		if err != nil {
			return nil, err
		}
	}
	lastSnapshotUUIDs, err := t.fetchLatestSnapshotUUIDsForReposOfTemplates(ctx, templates)
	if err != nil {
		return nil, TemplateDBToApiError(err, nil)
	}
	responses := templatesConvertToResponses(templates, lastSnapshotUUIDs, pulpContentPath)

	return responses, nil
}

func templatesCreateApiToModel(api api.TemplateRequest, model *models.Template) {
	if api.Name != nil {
		model.Name = *api.Name
	}
	if api.Description != nil {
		model.Description = *api.Description
	}
	if api.Version != nil {
		model.Version = *api.Version
	}
	if api.Arch != nil {
		model.Arch = *api.Arch
	}
	if api.Date != nil {
		model.Date = api.Date.AsTime().UTC()
	}
	if api.OrgID != nil {
		model.OrgID = *api.OrgID
	}
	if api.User != nil {
		model.CreatedBy = *api.User
		model.LastUpdatedBy = *api.User
	}
	if api.UseLatest != nil {
		model.UseLatest = *api.UseLatest
	}
}

func templatesUpdateApiToModel(api api.TemplateUpdateRequest, model *models.Template) {
	if api.Description != nil {
		model.Description = *api.Description
	}
	if api.Date != nil {
		model.Date = api.Date.AsTime().UTC()
	}
	if api.OrgID != nil {
		model.OrgID = *api.OrgID
	}
	if api.User != nil {
		model.LastUpdatedBy = *api.User
	}
	if api.Name != nil {
		model.Name = *api.Name
	}
	if api.UseLatest != nil {
		model.UseLatest = *api.UseLatest
	}
}

func templatesModelToApi(model models.Template, apiTemplate *api.TemplateResponse) {
	apiTemplate.UUID = model.UUID
	apiTemplate.RHSMEnvironmentID = candlepin_client.GetEnvironmentID(model.UUID)
	apiTemplate.RHSMEnvironmentCreated = model.RHSMEnvironmentCreated
	apiTemplate.OrgID = model.OrgID
	apiTemplate.Name = model.Name
	apiTemplate.Description = model.Description
	apiTemplate.Version = model.Version
	apiTemplate.Arch = model.Arch
	apiTemplate.Date = model.Date.UTC()
	apiTemplate.CreatedBy = model.CreatedBy
	apiTemplate.LastUpdatedBy = model.LastUpdatedBy
	apiTemplate.CreatedAt = model.CreatedAt.UTC()
	apiTemplate.UpdatedAt = model.UpdatedAt.UTC()
	apiTemplate.UseLatest = model.UseLatest
	apiTemplate.DeletedAt = model.DeletedAt
	if model.LastUpdateSnapshotError != nil {
		apiTemplate.LastUpdateSnapshotError = *model.LastUpdateSnapshotError
	}
	apiTemplate.LastUpdateTaskUUID = model.LastUpdateTaskUUID
	if model.LastUpdateTask != nil {
		apiTemplate.LastUpdateTask = &api.TaskInfoResponse{
			UUID:       model.LastUpdateTaskUUID,
			Status:     model.LastUpdateTask.Status,
			Typename:   model.LastUpdateTask.Typename,
			OrgId:      model.LastUpdateTask.OrgId,
			ObjectType: config.ObjectTypeTemplate,
			ObjectUUID: model.UUID,
			ObjectName: model.Name,
		}
		if model.LastUpdateTask.Started != nil {
			apiTemplate.LastUpdateTask.CreatedAt = model.LastUpdateTask.Started.Format(time.RFC3339)
		}
		if model.LastUpdateTask.Finished != nil {
			apiTemplate.LastUpdateTask.EndedAt = model.LastUpdateTask.Finished.Format(time.RFC3339)
		}
		if model.LastUpdateTask.Error != nil {
			apiTemplate.LastUpdateTask.Error = *model.LastUpdateTask.Error
		}
	}
}

func templatesConvertToResponses(templates []models.Template, lastSnapshotsUUIDs []string, pulpContentPath string) []api.TemplateResponse {
	responses := make([]api.TemplateResponse, len(templates))
	outdatedDate := time.Now().Add(-time.Duration((config.Get().Options.SnapshotRetainDaysLimit-14)*24) * time.Hour)
	for i := 0; i < len(templates); i++ {
		templatesModelToApi(templates[i], &responses[i])
		// Add in associations (Repository Config UUIDs and Snapshots)
		responses[i].RepositoryUUIDS = make([]string, 0) // prevent null responses
		responses[i].ToBeDeletedSnapshots = make([]api.SnapshotResponse, 0)
		for _, tRepoConfig := range templates[i].TemplateRepositoryConfigurations {
			responses[i].RepositoryUUIDS = append(responses[i].RepositoryUUIDS, tRepoConfig.RepositoryConfigurationUUID)
			snaps := snapshotConvertToResponses([]models.Snapshot{tRepoConfig.Snapshot}, pulpContentPath)
			responses[i].Snapshots = append(responses[i].Snapshots, snaps[0])
		}
		for _, snap := range responses[i].Snapshots {
			if snap.CreatedAt.Before(outdatedDate) && !slices.Contains(lastSnapshotsUUIDs, snap.UUID) {
				responses[i].ToBeDeletedSnapshots = append(responses[i].ToBeDeletedSnapshots, snap)
			}
		}
	}
	return responses
}

func (t templateDaoImpl) fetchLatestSnapshotUUIDsForReposOfTemplates(ctx context.Context, templates []models.Template) ([]string, error) {
	var repoUUIDs = make([]string, 0)
	var repos []models.RepositoryConfiguration
	var snapshotUUIDs = make([]string, 0)

	for _, template := range templates {
		for _, trc := range template.TemplateRepositoryConfigurations {
			repoUUIDs = append(repoUUIDs, trc.RepositoryConfigurationUUID)
		}
	}
	slices.Sort(repoUUIDs)
	repoUUIDs = slices.Compact(repoUUIDs)

	err := t.db.WithContext(ctx).
		Where("uuid IN ? ", repoUUIDs).
		Find(&repos).
		Error
	if err != nil {
		return snapshotUUIDs, err
	}
	for _, repo := range repos {
		snapshotUUIDs = append(snapshotUUIDs, repo.LastSnapshotUUID)
	}

	return snapshotUUIDs, nil
}