package server

import (
	"context"
	"errors"
	"sort"

	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"gorm.io/gorm"
)

func (s *serverImpl) GetComponentTemplate(
	ctx context.Context,
	componentType entity.ComponentType,
	version string,
) (*apiv1.GetComponentTemplateResponse, error) {
	if componentType == apiv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED {
		return nil, status.Error(codes.InvalidArgument, "component_type is required")
	}
	if version == "" {
		return nil, status.Error(codes.InvalidArgument, "version is required")
	}

	templateName, err := entity.GetComponentTemplateName(&entity.Component{ComponentType: componentType})
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "unsupported component_type %s", componentType.String())
	}

	resolvedName, resolvedVersion, content, err := s.templateVersionSvc.Resolve(ctx, templateName, version)
	if err != nil {
		return nil, err
	}

	return &apiv1.GetComponentTemplateResponse{
		TemplateName:    resolvedName,
		TemplateVersion: resolvedVersion,
		Content:         content,
	}, nil
}

func (s *serverImpl) ListComponentTemplateVersions(ctx context.Context, componentType entity.ComponentType) (*apiv1.ListComponentTemplateVersionsResponse, error) {
	if componentType == apiv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED {
		return nil, status.Error(codes.InvalidArgument, "component_type is required")
	}

	templateName, err := entity.GetComponentTemplateName(&entity.Component{ComponentType: componentType})
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "unsupported component_type %s", componentType.String())
	}

	versions, err := s.templateVersionSvc.ListVersions(ctx, templateName)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list template versions for %s: %v", templateName, err)
	}

	resp := &apiv1.ListComponentTemplateVersionsResponse{
		DefaultVersion: "",
		Versions:       versions,
	}

	for _, v := range versions {
		if v.GetStatus() == apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_AVAILABLE {
			resp.DefaultVersion = v.GetVersion()
			break
		}
	}

	return resp, nil
}

func (s *serverImpl) SetComponentTemplateVersionStatus(
	ctx context.Context,
	componentType entity.ComponentType, version string,
	targetStatus entity.TemplateVersionStatus,
) (*entity.TemplateVersionInfo, error) {
	if componentType == apiv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED {
		return nil, status.Error(codes.InvalidArgument, "component_type is required")
	}
	if version == "" {
		return nil, status.Error(codes.InvalidArgument, "version is required")
	}

	templateName, err := entity.GetComponentTemplateName(&entity.Component{ComponentType: componentType})
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "unsupported component_type %s", componentType.String())
	}

	pbStatus, err := validateStatus(targetStatus)
	if err != nil {
		return nil, err
	}

	var info *entity.TemplateVersionInfo
	err = s.transactioner.Transaction(ctx, func(ctx context.Context) error {
		var txErr error
		info, txErr = s.templateVersionSvc.SetStatus(ctx, templateName, version, pbStatus)
		return txErr
	})
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, status.Errorf(codes.NotFound, "template version not found: %s/%s", templateName, version)
		}
		if _, ok := status.FromError(err); ok {
			return nil, err
		}
		return nil, status.Errorf(codes.Internal, "failed to set template version status: %v", err)
	}

	return info, nil
}

// validateStatus validates the API status for SetComponentTemplateVersionStatus.
func validateStatus(targetStatus entity.TemplateVersionStatus) (entity.TemplateVersionStatus, error) {
	switch targetStatus {
	case apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_AVAILABLE,
		apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_DISABLED:
		return targetStatus, nil
	case apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_UNSPECIFIED:
		return 0, status.Error(codes.InvalidArgument, "status is required")
	case apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_EXPIRED:
		return 0, status.Error(codes.InvalidArgument, "EXPIRED is system-managed")
	default:
		return 0, status.Errorf(codes.InvalidArgument, "unsupported status: %s", targetStatus.String())
	}
}

// --- by template_name APIs (for templates without ComponentType mapping) ---

func (s *serverImpl) GetTemplate(
	ctx context.Context,
	templateName, version string,
) (*apiv1.GetTemplateResponse, error) {
	if templateName == "" {
		return nil, status.Error(codes.InvalidArgument, "template_name is required")
	}
	if version == "" {
		return nil, status.Error(codes.InvalidArgument, "version is required")
	}

	resolvedName, resolvedVersion, content, err := s.templateVersionSvc.Resolve(ctx, templateName, version)
	if err != nil {
		return nil, err
	}

	return &apiv1.GetTemplateResponse{
		TemplateName: resolvedName,
		Version:      resolvedVersion,
		Content:      content,
	}, nil
}

func (s *serverImpl) ListTemplateVersions(
	ctx context.Context,
	templateName string,
) (*apiv1.ListTemplateVersionsResponse, error) {
	if templateName == "" {
		return nil, status.Error(codes.InvalidArgument, "template_name is required")
	}

	versions, err := s.templateVersionSvc.ListVersions(ctx, templateName)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list template versions for %s: %v", templateName, err)
	}

	resp := &apiv1.ListTemplateVersionsResponse{
		DefaultVersion: "",
		Versions:       versions,
	}

	for _, v := range versions {
		if v.GetStatus() == apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_AVAILABLE {
			resp.DefaultVersion = v.GetVersion()
			break
		}
	}

	return resp, nil
}

func (s *serverImpl) SetTemplateVersionStatus(
	ctx context.Context,
	templateName, version string,
	targetStatus entity.TemplateVersionStatus,
) (*entity.TemplateVersionInfo, error) {
	if templateName == "" {
		return nil, status.Error(codes.InvalidArgument, "template_name is required")
	}
	if version == "" {
		return nil, status.Error(codes.InvalidArgument, "version is required")
	}

	pbStatus, err := validateStatus(targetStatus)
	if err != nil {
		return nil, err
	}

	var info *entity.TemplateVersionInfo
	err = s.transactioner.Transaction(ctx, func(ctx context.Context) error {
		var txErr error
		info, txErr = s.templateVersionSvc.SetStatus(ctx, templateName, version, pbStatus)
		return txErr
	})
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, status.Errorf(codes.NotFound, "template version not found: %s/%s", templateName, version)
		}
		if _, ok := status.FromError(err); ok {
			return nil, err
		}
		return nil, status.Errorf(codes.Internal, "failed to set template version status: %v", err)
	}

	return info, nil
}

func (s *serverImpl) ListTemplates(ctx context.Context) (*apiv1.ListTemplatesResponse, error) {
	summaries, err := s.templateVersionSvc.ListAllTemplates(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list templates: %v", err)
	}

	result := make([]*apiv1.TemplateSummary, 0, len(summaries))
	for _, sm := range summaries {
		result = append(result, &apiv1.TemplateSummary{
			TemplateName:   sm.TemplateName,
			DefaultVersion: sm.DefaultVersion,
		})
	}

	return &apiv1.ListTemplatesResponse{Templates: result}, nil
}

// componentTypesWithTemplate is derived at init time from the ComponentType enum
// by probing entity.GetComponentTemplateName. This avoids a hard-coded list that
// can drift when new ComponentType→template mappings are added.
var componentTypesWithTemplate []apiv1.ComponentType

func init() {
	// Collect all ComponentType values that have a template mapping, sorted by
	// enum number for deterministic output.
	keys := make([]int, 0, len(apiv1.ComponentType_name))
	for k := range apiv1.ComponentType_name {
		keys = append(keys, int(k))
	}
	sort.Ints(keys)
	for _, k := range keys {
		ct := apiv1.ComponentType(k)
		if ct == apiv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED {
			continue
		}
		if _, err := entity.GetComponentTemplateName(&entity.Component{ComponentType: ct}); err == nil {
			componentTypesWithTemplate = append(componentTypesWithTemplate, ct)
		}
	}
}

func (s *serverImpl) ListComponentTemplates(ctx context.Context) (*apiv1.ListComponentTemplatesResponse, error) {
	// Pre-fetch all template summaries in a single query to avoid N+1.
	summaries, err := s.templateVersionSvc.ListAllTemplates(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list template summaries: %v", err)
	}
	defaultVersionByName := make(map[string]string, len(summaries))
	for _, sm := range summaries {
		defaultVersionByName[sm.TemplateName] = sm.DefaultVersion
	}

	items := make([]*apiv1.ComponentTemplateSummary, 0, len(componentTypesWithTemplate))
	for _, ct := range componentTypesWithTemplate {
		tmplName, err := entity.GetComponentTemplateName(&entity.Component{ComponentType: ct})
		if err != nil {
			// This should never happen since componentTypesWithTemplate is derived
			// from the same mapping at init time. Surface as Internal error.
			return nil, status.Errorf(codes.Internal, "unexpected mapping error for %s: %v", ct.String(), err)
		}

		items = append(items, &apiv1.ComponentTemplateSummary{
			ComponentType:  ct,
			TemplateName:   tmplName,
			DefaultVersion: defaultVersionByName[tmplName],
		})
	}

	return &apiv1.ListComponentTemplatesResponse{ComponentTemplates: items}, nil
}
