package kb

import (
	"context"
	"regexp"
	"strings"

	storekb "github.com/tidbcloud/AgentTeam/internal/store/kb"
	"gorm.io/gorm"
)

const (
	maxKnowledgeBaseGroups       = 100
	maxKnowledgeBaseTags         = 200
	maxKnowledgeBaseTagsPerBase  = 20
	maxKnowledgeBaseGroupNameLen = 100
	maxKnowledgeBaseGroupDescLen = 500
	maxKnowledgeBaseTagNameLen   = 64

	kbTaxonomyGroupLimitCode         = "kb_taxonomy_group_limit"
	kbTaxonomyGroupNameRequiredCode  = "kb_taxonomy_group_name_required"
	kbTaxonomyGroupNameTooLongCode   = "kb_taxonomy_group_name_too_long"
	kbTaxonomyGroupNameExistsCode    = "kb_taxonomy_group_name_exists"
	kbTaxonomyGroupDescriptionCode   = "kb_taxonomy_group_description_too_long"
	kbTaxonomyTagLimitCode           = "kb_taxonomy_tag_limit"
	kbTaxonomyTagNameRequiredCode    = "kb_taxonomy_tag_name_required"
	kbTaxonomyTagNameTooLongCode     = "kb_taxonomy_tag_name_too_long"
	kbTaxonomyTagNameExistsCode      = "kb_taxonomy_tag_name_exists"
	kbTaxonomyColorInvalidCode       = "kb_taxonomy_color_invalid"
	kbTaxonomyDefaultGroupDeleteCode = "kb_taxonomy_default_group_delete"
)

var taxonomyColorPattern = regexp.MustCompile(`^#[0-9a-fA-F]{6}$`)

var taxonomyColors = []string{
	"#5B6FB5", "#3F826D", "#AD674D", "#8064A2", "#A97932", "#477C9E", "#9B5C75",
}

type KnowledgeBaseTaxonomy struct {
	Groups []storekb.KnowledgeBaseGroup
	Tags   []storekb.KnowledgeBaseTag
}

type KnowledgeBaseTaxonomyMutation struct {
	Name        string
	Description string
	Color       string
	SortOrder   *int
}

func (s *KBService) ListKnowledgeBaseTaxonomy(ctx context.Context, workspaceID, subject string) (KnowledgeBaseTaxonomy, error) {
	if err := s.validateScope(workspaceID, subject); err != nil {
		return KnowledgeBaseTaxonomy{}, err
	}
	workspaceID = strings.TrimSpace(workspaceID)
	if _, err := s.store.EnsureDefaultKnowledgeBaseGroup(ctx, workspaceID, subject, s.now()); err != nil {
		return KnowledgeBaseTaxonomy{}, err
	}
	groups, err := s.store.ListKnowledgeBaseGroups(ctx, workspaceID)
	if err != nil {
		return KnowledgeBaseTaxonomy{}, err
	}
	tags, err := s.store.ListKnowledgeBaseTags(ctx, workspaceID)
	if err != nil {
		return KnowledgeBaseTaxonomy{}, err
	}
	return KnowledgeBaseTaxonomy{Groups: groups, Tags: tags}, nil
}

func (s *KBService) CreateKnowledgeBaseGroup(ctx context.Context, workspaceID, subject string, input KnowledgeBaseTaxonomyMutation) (storekb.KnowledgeBaseGroup, error) {
	taxonomy, err := s.ListKnowledgeBaseTaxonomy(ctx, workspaceID, subject)
	if err != nil {
		return storekb.KnowledgeBaseGroup{}, err
	}
	if len(taxonomy.Groups) >= maxKnowledgeBaseGroups {
		return storekb.KnowledgeBaseGroup{}, codedInvalidArgument(kbTaxonomyGroupLimitCode, "knowledge base group limit reached")
	}
	name, err := validateTaxonomyName(input.Name, maxKnowledgeBaseGroupNameLen, "knowledge base group", kbTaxonomyGroupNameRequiredCode, kbTaxonomyGroupNameTooLongCode)
	if err != nil {
		return storekb.KnowledgeBaseGroup{}, err
	}
	if taxonomyNameExistsInGroups(taxonomy.Groups, "", name) {
		return storekb.KnowledgeBaseGroup{}, codedInvalidArgument(kbTaxonomyGroupNameExistsCode, "knowledge base group name already exists")
	}
	description, err := validateTaxonomyDescription(input.Description)
	if err != nil {
		return storekb.KnowledgeBaseGroup{}, err
	}
	color, err := normalizeTaxonomyColor(input.Color, taxonomyColors[len(taxonomy.Groups)%len(taxonomyColors)])
	if err != nil {
		return storekb.KnowledgeBaseGroup{}, err
	}
	sortOrder := len(taxonomy.Groups) * 100
	if input.SortOrder != nil {
		sortOrder = *input.SortOrder
	}
	now := s.now()
	return s.store.CreateKnowledgeBaseGroup(ctx, storekb.KnowledgeBaseGroup{
		ID:          newKBID("kbcg"),
		WorkspaceID: strings.TrimSpace(workspaceID),
		Name:        name,
		Description: description,
		Color:       color,
		SortOrder:   sortOrder,
		CreatedBy:   strings.TrimSpace(subject),
		UpdatedBy:   strings.TrimSpace(subject),
		CreatedAt:   now,
		UpdatedAt:   now,
	})
}

func (s *KBService) UpdateKnowledgeBaseGroup(ctx context.Context, workspaceID, subject, groupID string, input KnowledgeBaseTaxonomyMutation) (storekb.KnowledgeBaseGroup, error) {
	taxonomy, err := s.ListKnowledgeBaseTaxonomy(ctx, workspaceID, subject)
	if err != nil {
		return storekb.KnowledgeBaseGroup{}, err
	}
	current, err := s.store.GetKnowledgeBaseGroup(ctx, workspaceID, groupID)
	if err != nil {
		return storekb.KnowledgeBaseGroup{}, mapStoreNotFound(err)
	}
	name, err := validateTaxonomyName(input.Name, maxKnowledgeBaseGroupNameLen, "knowledge base group", kbTaxonomyGroupNameRequiredCode, kbTaxonomyGroupNameTooLongCode)
	if err != nil {
		return storekb.KnowledgeBaseGroup{}, err
	}
	if taxonomyNameExistsInGroups(taxonomy.Groups, current.ID, name) {
		return storekb.KnowledgeBaseGroup{}, codedInvalidArgument(kbTaxonomyGroupNameExistsCode, "knowledge base group name already exists")
	}
	description, err := validateTaxonomyDescription(input.Description)
	if err != nil {
		return storekb.KnowledgeBaseGroup{}, err
	}
	color, err := normalizeTaxonomyColor(input.Color, current.Color)
	if err != nil {
		return storekb.KnowledgeBaseGroup{}, err
	}
	current.Name = name
	current.Description = description
	current.Color = color
	if input.SortOrder != nil {
		current.SortOrder = *input.SortOrder
	}
	current.UpdatedBy = strings.TrimSpace(subject)
	current.UpdatedAt = s.now()
	updated, err := s.store.UpdateKnowledgeBaseGroup(ctx, current)
	if err != nil {
		return storekb.KnowledgeBaseGroup{}, mapStoreNotFound(err)
	}
	return updated, nil
}

func (s *KBService) DeleteKnowledgeBaseGroup(ctx context.Context, workspaceID, subject, groupID string) error {
	if err := s.validateScope(workspaceID, subject); err != nil {
		return err
	}
	group, err := s.store.GetKnowledgeBaseGroup(ctx, workspaceID, groupID)
	if err != nil {
		return mapStoreNotFound(err)
	}
	if group.IsDefault {
		return codedInvalidArgument(kbTaxonomyDefaultGroupDeleteCode, "default knowledge base group cannot be deleted")
	}
	defaultGroup, err := s.store.EnsureDefaultKnowledgeBaseGroup(ctx, workspaceID, subject, s.now())
	if err != nil {
		return err
	}
	if err := s.store.DeleteKnowledgeBaseGroup(ctx, workspaceID, group.ID, defaultGroup.ID); err != nil {
		return mapStoreNotFound(err)
	}
	return nil
}

func (s *KBService) CreateKnowledgeBaseTag(ctx context.Context, workspaceID, subject string, input KnowledgeBaseTaxonomyMutation) (storekb.KnowledgeBaseTag, error) {
	taxonomy, err := s.ListKnowledgeBaseTaxonomy(ctx, workspaceID, subject)
	if err != nil {
		return storekb.KnowledgeBaseTag{}, err
	}
	if len(taxonomy.Tags) >= maxKnowledgeBaseTags {
		return storekb.KnowledgeBaseTag{}, codedInvalidArgument(kbTaxonomyTagLimitCode, "knowledge base tag limit reached")
	}
	name, err := validateTaxonomyName(input.Name, maxKnowledgeBaseTagNameLen, "knowledge base tag", kbTaxonomyTagNameRequiredCode, kbTaxonomyTagNameTooLongCode)
	if err != nil {
		return storekb.KnowledgeBaseTag{}, err
	}
	if taxonomyNameExistsInTags(taxonomy.Tags, "", name) {
		return storekb.KnowledgeBaseTag{}, codedInvalidArgument(kbTaxonomyTagNameExistsCode, "knowledge base tag name already exists")
	}
	color, err := normalizeTaxonomyColor(input.Color, taxonomyColors[len(taxonomy.Tags)%len(taxonomyColors)])
	if err != nil {
		return storekb.KnowledgeBaseTag{}, err
	}
	sortOrder := len(taxonomy.Tags) * 100
	if input.SortOrder != nil {
		sortOrder = *input.SortOrder
	}
	now := s.now()
	return s.store.CreateKnowledgeBaseTag(ctx, storekb.KnowledgeBaseTag{
		ID:          newKBID("kbct"),
		WorkspaceID: strings.TrimSpace(workspaceID),
		Name:        name,
		Color:       color,
		SortOrder:   sortOrder,
		CreatedBy:   strings.TrimSpace(subject),
		UpdatedBy:   strings.TrimSpace(subject),
		CreatedAt:   now,
		UpdatedAt:   now,
	})
}

func (s *KBService) UpdateKnowledgeBaseTag(ctx context.Context, workspaceID, subject, tagID string, input KnowledgeBaseTaxonomyMutation) (storekb.KnowledgeBaseTag, error) {
	taxonomy, err := s.ListKnowledgeBaseTaxonomy(ctx, workspaceID, subject)
	if err != nil {
		return storekb.KnowledgeBaseTag{}, err
	}
	current, err := s.store.GetKnowledgeBaseTag(ctx, workspaceID, tagID)
	if err != nil {
		return storekb.KnowledgeBaseTag{}, mapStoreNotFound(err)
	}
	name, err := validateTaxonomyName(input.Name, maxKnowledgeBaseTagNameLen, "knowledge base tag", kbTaxonomyTagNameRequiredCode, kbTaxonomyTagNameTooLongCode)
	if err != nil {
		return storekb.KnowledgeBaseTag{}, err
	}
	if taxonomyNameExistsInTags(taxonomy.Tags, current.ID, name) {
		return storekb.KnowledgeBaseTag{}, codedInvalidArgument(kbTaxonomyTagNameExistsCode, "knowledge base tag name already exists")
	}
	color, err := normalizeTaxonomyColor(input.Color, current.Color)
	if err != nil {
		return storekb.KnowledgeBaseTag{}, err
	}
	current.Name = name
	current.Color = color
	if input.SortOrder != nil {
		current.SortOrder = *input.SortOrder
	}
	current.UpdatedBy = strings.TrimSpace(subject)
	current.UpdatedAt = s.now()
	updated, err := s.store.UpdateKnowledgeBaseTag(ctx, current)
	if err != nil {
		return storekb.KnowledgeBaseTag{}, mapStoreNotFound(err)
	}
	return updated, nil
}

func (s *KBService) DeleteKnowledgeBaseTag(ctx context.Context, workspaceID, subject, tagID string) error {
	if err := s.validateScope(workspaceID, subject); err != nil {
		return err
	}
	if err := s.store.DeleteKnowledgeBaseTag(ctx, workspaceID, tagID); err != nil {
		return mapStoreNotFound(err)
	}
	return nil
}

func (s *KBService) resolveKnowledgeBaseClassification(ctx context.Context, workspaceID, subject string, requestedGroupID *string, requestedTagIDs []string) (string, []string, error) {
	defaultGroup, err := s.store.EnsureDefaultKnowledgeBaseGroup(ctx, workspaceID, subject, s.now())
	if err != nil {
		return "", nil, err
	}
	groupID := defaultGroup.ID
	if requestedGroupID != nil && strings.TrimSpace(*requestedGroupID) != "" {
		groupID = strings.TrimSpace(*requestedGroupID)
		if _, err := s.store.GetKnowledgeBaseGroup(ctx, workspaceID, groupID); err != nil {
			if err == gorm.ErrRecordNotFound {
				return "", nil, invalidArgument("knowledge base group does not belong to this workspace")
			}
			return "", nil, err
		}
	}
	tagIDs := uniqueTaxonomyIDs(requestedTagIDs)
	if len(tagIDs) > maxKnowledgeBaseTagsPerBase {
		return "", nil, invalidArgument("too many knowledge base tags")
	}
	if len(tagIDs) > 0 {
		tags, err := s.store.ListKnowledgeBaseTagsByIDs(ctx, workspaceID, tagIDs)
		if err != nil {
			return "", nil, err
		}
		if len(tags) != len(tagIDs) {
			return "", nil, invalidArgument("knowledge base tag does not belong to this workspace")
		}
	}
	return groupID, tagIDs, nil
}

func validateTaxonomyName(raw string, maximum int, resource, requiredCode, tooLongCode string) (string, error) {
	name := strings.TrimSpace(raw)
	if name == "" {
		return "", codedInvalidArgument(requiredCode, resource+" name is required")
	}
	if len([]rune(name)) > maximum {
		return "", codedInvalidArgument(tooLongCode, resource+" name is too long")
	}
	return name, nil
}

func validateTaxonomyDescription(raw string) (string, error) {
	description := strings.TrimSpace(raw)
	if len([]rune(description)) > maxKnowledgeBaseGroupDescLen {
		return "", codedInvalidArgument(kbTaxonomyGroupDescriptionCode, "knowledge base group description is too long")
	}
	return description, nil
}

func normalizeTaxonomyColor(raw, fallback string) (string, error) {
	color := strings.TrimSpace(raw)
	if color == "" {
		color = strings.TrimSpace(fallback)
	}
	if !taxonomyColorPattern.MatchString(color) {
		return "", codedInvalidArgument(kbTaxonomyColorInvalidCode, "knowledge base taxonomy color must use #RRGGBB")
	}
	return strings.ToUpper(color), nil
}

func taxonomyNameExistsInGroups(groups []storekb.KnowledgeBaseGroup, excludedID, name string) bool {
	for _, group := range groups {
		if group.ID != excludedID && strings.EqualFold(strings.TrimSpace(group.Name), name) {
			return true
		}
	}
	return false
}

func taxonomyNameExistsInTags(tags []storekb.KnowledgeBaseTag, excludedID, name string) bool {
	for _, tag := range tags {
		if tag.ID != excludedID && strings.EqualFold(strings.TrimSpace(tag.Name), name) {
			return true
		}
	}
	return false
}

func uniqueTaxonomyIDs(values []string) []string {
	result := make([]string, 0, len(values))
	seen := make(map[string]struct{}, len(values))
	for _, value := range values {
		value = strings.TrimSpace(value)
		if value == "" {
			continue
		}
		if _, ok := seen[value]; ok {
			continue
		}
		seen[value] = struct{}{}
		result = append(result, value)
	}
	return result
}
