package template_registry

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"strings"

	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/mysql/schema"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	templateversion "github.com/tidbcloud/cluster-service-ng/pkg/template/version"
	"github.com/tidbcloud/pkgv2/db"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

type templateRegistryRepo struct {
	dbProvider db.Provider
}

func NewTemplateRegistryRepo(provider db.Provider) repo.TemplateRegistryRepo {
	return &templateRegistryRepo{
		dbProvider: provider,
	}
}

func (r *templateRegistryRepo) Upsert(ctx context.Context, item *entity.TemplateRegistry) error {
	if item == nil {
		return fmt.Errorf("template item is nil")
	}
	if err := validateTemplateHash(item.Content, item.Version); err != nil {
		return err
	}

	row := cvtEntityToSchema(item)
	wdb := r.dbProvider.WriteDB(ctx)
	if err := wdb.Clauses(clause.OnConflict{
		Columns: []clause.Column{
			{Name: "template_name"},
			{Name: "version"},
		},
		DoUpdates: clause.AssignmentColumns([]string{"content", "updated_at"}),
	}).Create(row).Error; err != nil {
		return fmt.Errorf("failed to upsert template registry %s/%s: %w", item.TemplateName, item.Version, err)
	}
	return nil
}

func (r *templateRegistryRepo) Load(ctx context.Context, templateName, version string) (*entity.TemplateRegistry, error) {
	var item schema.TemplateRegistry
	rdb := r.dbProvider.ReadDB(ctx)
	if err := rdb.WithContext(ctx).
		Where("template_name = ? AND version = ?", templateName, version).
		First(&item).Error; err != nil {
		return nil, err
	}
	return cvtSchemaToEntity(&item), nil
}

func (r *templateRegistryRepo) ListByTemplateName(ctx context.Context, templateName string) ([]*entity.TemplateRegistry, error) {
	rdb := r.dbProvider.ReadDB(ctx)
	var items []*schema.TemplateRegistry
	if err := rdb.WithContext(ctx).
		Where("template_name = ?", templateName).
		Order("created_at DESC, id DESC").
		Find(&items).Error; err != nil {
		return nil, fmt.Errorf("failed to list template registry by template name %s: %w", templateName, err)
	}
	result := make([]*entity.TemplateRegistry, 0, len(items))
	for _, item := range items {
		result = append(result, cvtSchemaToEntity(item))
	}
	return result, nil
}

func (r *templateRegistryRepo) ListByTemplateNameForUpdate(ctx context.Context, templateName string) ([]*entity.TemplateRegistry, error) {
	wdb := r.dbProvider.WriteDB(ctx)
	var items []*schema.TemplateRegistry
	if err := wdb.WithContext(ctx).
		Clauses(clause.Locking{Strength: "UPDATE"}).
		Where("template_name = ?", templateName).
		Order("created_at DESC, id DESC").
		Find(&items).Error; err != nil {
		return nil, fmt.Errorf("failed to list template registry by template name %s for update: %w", templateName, err)
	}
	result := make([]*entity.TemplateRegistry, 0, len(items))
	for _, item := range items {
		result = append(result, cvtSchemaToEntity(item))
	}
	return result, nil
}

func (r *templateRegistryRepo) UpdateStatus(ctx context.Context, templateName, version string, status entity.TemplateVersionStatus) error {
	storeStatus := statusToStore(status)
	if storeStatus == "" {
		return fmt.Errorf("unsupported template version status: %s", status.String())
	}
	wdb := r.dbProvider.WriteDB(ctx)
	result := wdb.WithContext(ctx).
		Model(&schema.TemplateRegistry{}).
		Where("template_name = ? AND version = ?", templateName, version).
		Update("status", storeStatus)
	if result.Error != nil {
		return fmt.Errorf("failed to update template registry status %s/%s to %s: %w", templateName, version, storeStatus, result.Error)
	}
	if result.RowsAffected == 0 {
		return fmt.Errorf("template version %s/%s not found: %w", templateName, version, gorm.ErrRecordNotFound)
	}
	return nil
}

func (r *templateRegistryRepo) ListTemplateSummaries(ctx context.Context) ([]*repo.TemplateSummary, error) {
	rdb := r.dbProvider.ReadDB(ctx)

	// Single query: for each template_name, pick the newest AVAILABLE version
	// (by created_at DESC, id DESC). Uses a correlated subquery to find the row
	// with the latest created_at among AVAILABLE rows per template_name, then
	// LEFT JOIN to get the version.
	type row struct {
		TemplateName   string `gorm:"column:template_name"`
		DefaultVersion string `gorm:"column:default_version"`
	}
	var rows []row
	if err := rdb.WithContext(ctx).Raw(`
		SELECT
			n.template_name,
			COALESCE(a.version, '') AS default_version
		FROM (
			SELECT DISTINCT template_name
			FROM template_registries
			WHERE deleted_at IS NULL
		) n
		LEFT JOIN template_registries a ON a.template_name = n.template_name
			AND a.status = ?
			AND a.deleted_at IS NULL
			AND a.id = (
				SELECT id FROM template_registries sub
				WHERE sub.template_name = n.template_name
					AND sub.status = ?
					AND sub.deleted_at IS NULL
				ORDER BY sub.created_at DESC, sub.id DESC
				LIMIT 1
			)
		ORDER BY n.template_name ASC
	`, schema.TemplateVersionStatusAvailable, schema.TemplateVersionStatusAvailable).Scan(&rows).Error; err != nil {
		return nil, fmt.Errorf("failed to list template summaries: %w", err)
	}

	result := make([]*repo.TemplateSummary, 0, len(rows))
	for _, r := range rows {
		result = append(result, &repo.TemplateSummary{
			TemplateName:   r.TemplateName,
			DefaultVersion: r.DefaultVersion,
		})
	}
	return result, nil
}

func validateTemplateHash(content, version string) error {
	normalized := templateversion.NormalizeTemplateContent(content)
	sum := sha256.Sum256([]byte(normalized))
	got := hex.EncodeToString(sum[:])
	if !strings.EqualFold(got, version) {
		return fmt.Errorf("template version mismatch: expected=%s, got sha256(normalized(content))=%s", version, got)
	}
	return nil
}
