package kb

import (
	"context"
	"crypto/sha1"
	"encoding/hex"
	"fmt"
	"sort"
	"strings"
	"time"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

const (
	defaultKnowledgeBaseGroupName        = "Uncategorized"
	defaultKnowledgeBaseGroupDescription = "Knowledge bases that have not been assigned to another group."
	defaultKnowledgeBaseGroupColor       = "#737373"
)

func DefaultKnowledgeBaseGroupID(workspaceID string) string {
	sum := sha1.Sum([]byte("knowledge-base-default-group\x00" + strings.TrimSpace(workspaceID)))
	return "kbcg_default_" + hex.EncodeToString(sum[:])[:32]
}

func (s *Store) EnsureDefaultKnowledgeBaseGroup(ctx context.Context, workspaceID, actorID string, now time.Time) (KnowledgeBaseGroup, error) {
	if err := s.ensureDB(); err != nil {
		return KnowledgeBaseGroup{}, err
	}
	workspaceID = strings.TrimSpace(workspaceID)
	actorID = strings.TrimSpace(actorID)
	var existing KnowledgeBaseGroup
	if err := s.db.WithContext(ctx).
		Where("workspace_id = ? AND is_default = ?", workspaceID, true).
		Order("sort_order ASC, created_at ASC").
		First(&existing).Error; err == nil {
		return existing, nil
	} else if err != gorm.ErrRecordNotFound {
		return KnowledgeBaseGroup{}, fmt.Errorf("get default knowledge base group: %w", err)
	}
	if now.IsZero() {
		now = time.Now().UTC()
	}
	if actorID == "" {
		actorID = "system"
	}
	group := KnowledgeBaseGroup{
		ID:          DefaultKnowledgeBaseGroupID(workspaceID),
		WorkspaceID: workspaceID,
		Name:        defaultKnowledgeBaseGroupName,
		Description: defaultKnowledgeBaseGroupDescription,
		Color:       defaultKnowledgeBaseGroupColor,
		SortOrder:   10000,
		IsDefault:   true,
		CreatedBy:   actorID,
		UpdatedBy:   actorID,
		CreatedAt:   now,
		UpdatedAt:   now,
	}
	if err := s.db.WithContext(ctx).Clauses(clause.OnConflict{DoNothing: true}).Create(&group).Error; err != nil {
		return KnowledgeBaseGroup{}, fmt.Errorf("create default knowledge base group: %w", err)
	}
	if err := s.db.WithContext(ctx).Where("workspace_id = ? AND is_default = ?", workspaceID, true).Order("sort_order ASC, created_at ASC").First(&group).Error; err != nil {
		return KnowledgeBaseGroup{}, fmt.Errorf("get created default knowledge base group: %w", err)
	}
	return group, nil
}

func (s *Store) ListKnowledgeBaseGroups(ctx context.Context, workspaceID string) ([]KnowledgeBaseGroup, error) {
	if err := s.ensureDB(); err != nil {
		return nil, err
	}
	var groups []KnowledgeBaseGroup
	if err := s.db.WithContext(ctx).
		Where("workspace_id = ?", strings.TrimSpace(workspaceID)).
		Order("is_default ASC, sort_order ASC, name ASC, id ASC").
		Find(&groups).Error; err != nil {
		return nil, fmt.Errorf("list knowledge base groups: %w", err)
	}
	return groups, nil
}

func (s *Store) GetKnowledgeBaseGroup(ctx context.Context, workspaceID, groupID string) (KnowledgeBaseGroup, error) {
	if err := s.ensureDB(); err != nil {
		return KnowledgeBaseGroup{}, err
	}
	var group KnowledgeBaseGroup
	if err := s.db.WithContext(ctx).Where("workspace_id = ? AND id = ?", strings.TrimSpace(workspaceID), strings.TrimSpace(groupID)).First(&group).Error; err != nil {
		return KnowledgeBaseGroup{}, err
	}
	return group, nil
}

func (s *Store) CreateKnowledgeBaseGroup(ctx context.Context, group KnowledgeBaseGroup) (KnowledgeBaseGroup, error) {
	if err := s.ensureDB(); err != nil {
		return KnowledgeBaseGroup{}, err
	}
	if err := s.db.WithContext(ctx).Create(&group).Error; err != nil {
		return KnowledgeBaseGroup{}, fmt.Errorf("create knowledge base group: %w", err)
	}
	return group, nil
}

func (s *Store) UpdateKnowledgeBaseGroup(ctx context.Context, group KnowledgeBaseGroup) (KnowledgeBaseGroup, error) {
	if err := s.ensureDB(); err != nil {
		return KnowledgeBaseGroup{}, err
	}
	result := s.db.WithContext(ctx).Model(&KnowledgeBaseGroup{}).
		Where("workspace_id = ? AND id = ?", strings.TrimSpace(group.WorkspaceID), strings.TrimSpace(group.ID)).
		Updates(map[string]any{
			"name":        group.Name,
			"description": group.Description,
			"color":       group.Color,
			"sort_order":  group.SortOrder,
			"updated_by":  group.UpdatedBy,
			"updated_at":  group.UpdatedAt,
		})
	if result.Error != nil {
		return KnowledgeBaseGroup{}, fmt.Errorf("update knowledge base group: %w", result.Error)
	}
	if result.RowsAffected == 0 {
		return KnowledgeBaseGroup{}, gorm.ErrRecordNotFound
	}
	return s.GetKnowledgeBaseGroup(ctx, group.WorkspaceID, group.ID)
}

func (s *Store) DeleteKnowledgeBaseGroup(ctx context.Context, workspaceID, groupID, replacementGroupID string) error {
	if err := s.ensureDB(); err != nil {
		return err
	}
	if !s.inTransactionStore() {
		return s.Transaction(ctx, func(tx *Store) error {
			return tx.DeleteKnowledgeBaseGroup(ctx, workspaceID, groupID, replacementGroupID)
		})
	}
	workspaceID = strings.TrimSpace(workspaceID)
	groupID = strings.TrimSpace(groupID)
	replacementGroupID = strings.TrimSpace(replacementGroupID)
	if err := s.db.WithContext(ctx).Model(&KnowledgeBase{}).
		Where("workspace_id = ? AND group_id = ?", workspaceID, groupID).
		Update("group_id", replacementGroupID).Error; err != nil {
		return fmt.Errorf("reassign knowledge base group: %w", err)
	}
	result := s.db.WithContext(ctx).Where("workspace_id = ? AND id = ?", workspaceID, groupID).Delete(&KnowledgeBaseGroup{})
	if result.Error != nil {
		return fmt.Errorf("delete knowledge base group: %w", result.Error)
	}
	if result.RowsAffected == 0 {
		return gorm.ErrRecordNotFound
	}
	return nil
}

func (s *Store) ListKnowledgeBaseTags(ctx context.Context, workspaceID string) ([]KnowledgeBaseTag, error) {
	if err := s.ensureDB(); err != nil {
		return nil, err
	}
	var tags []KnowledgeBaseTag
	if err := s.db.WithContext(ctx).
		Where("workspace_id = ?", strings.TrimSpace(workspaceID)).
		Order("sort_order ASC, name ASC, id ASC").
		Find(&tags).Error; err != nil {
		return nil, fmt.Errorf("list knowledge base tags: %w", err)
	}
	return tags, nil
}

func (s *Store) GetKnowledgeBaseTag(ctx context.Context, workspaceID, tagID string) (KnowledgeBaseTag, error) {
	if err := s.ensureDB(); err != nil {
		return KnowledgeBaseTag{}, err
	}
	var tag KnowledgeBaseTag
	if err := s.db.WithContext(ctx).Where("workspace_id = ? AND id = ?", strings.TrimSpace(workspaceID), strings.TrimSpace(tagID)).First(&tag).Error; err != nil {
		return KnowledgeBaseTag{}, err
	}
	return tag, nil
}

func (s *Store) ListKnowledgeBaseTagsByIDs(ctx context.Context, workspaceID string, tagIDs []string) ([]KnowledgeBaseTag, error) {
	if err := s.ensureDB(); err != nil {
		return nil, err
	}
	tagIDs = uniqueTrimmedStrings(tagIDs)
	if len(tagIDs) == 0 {
		return []KnowledgeBaseTag{}, nil
	}
	var tags []KnowledgeBaseTag
	if err := s.db.WithContext(ctx).
		Where("workspace_id = ? AND id IN ?", strings.TrimSpace(workspaceID), tagIDs).
		Order("sort_order ASC, name ASC, id ASC").
		Find(&tags).Error; err != nil {
		return nil, fmt.Errorf("list knowledge base tags by IDs: %w", err)
	}
	return tags, nil
}

func (s *Store) CreateKnowledgeBaseTag(ctx context.Context, tag KnowledgeBaseTag) (KnowledgeBaseTag, error) {
	if err := s.ensureDB(); err != nil {
		return KnowledgeBaseTag{}, err
	}
	if err := s.db.WithContext(ctx).Create(&tag).Error; err != nil {
		return KnowledgeBaseTag{}, fmt.Errorf("create knowledge base tag: %w", err)
	}
	return tag, nil
}

func (s *Store) UpdateKnowledgeBaseTag(ctx context.Context, tag KnowledgeBaseTag) (KnowledgeBaseTag, error) {
	if err := s.ensureDB(); err != nil {
		return KnowledgeBaseTag{}, err
	}
	result := s.db.WithContext(ctx).Model(&KnowledgeBaseTag{}).
		Where("workspace_id = ? AND id = ?", strings.TrimSpace(tag.WorkspaceID), strings.TrimSpace(tag.ID)).
		Updates(map[string]any{
			"name":       tag.Name,
			"color":      tag.Color,
			"sort_order": tag.SortOrder,
			"updated_by": tag.UpdatedBy,
			"updated_at": tag.UpdatedAt,
		})
	if result.Error != nil {
		return KnowledgeBaseTag{}, fmt.Errorf("update knowledge base tag: %w", result.Error)
	}
	if result.RowsAffected == 0 {
		return KnowledgeBaseTag{}, gorm.ErrRecordNotFound
	}
	return s.GetKnowledgeBaseTag(ctx, tag.WorkspaceID, tag.ID)
}

func (s *Store) DeleteKnowledgeBaseTag(ctx context.Context, workspaceID, tagID string) error {
	if err := s.ensureDB(); err != nil {
		return err
	}
	if !s.inTransactionStore() {
		return s.Transaction(ctx, func(tx *Store) error {
			return tx.DeleteKnowledgeBaseTag(ctx, workspaceID, tagID)
		})
	}
	workspaceID = strings.TrimSpace(workspaceID)
	tagID = strings.TrimSpace(tagID)
	if err := s.db.WithContext(ctx).Where("workspace_id = ? AND tag_id = ?", workspaceID, tagID).Delete(&KnowledgeBaseTagAssignment{}).Error; err != nil {
		return fmt.Errorf("delete knowledge base tag assignments: %w", err)
	}
	result := s.db.WithContext(ctx).Where("workspace_id = ? AND id = ?", workspaceID, tagID).Delete(&KnowledgeBaseTag{})
	if result.Error != nil {
		return fmt.Errorf("delete knowledge base tag: %w", result.Error)
	}
	if result.RowsAffected == 0 {
		return gorm.ErrRecordNotFound
	}
	return nil
}

func (s *Store) ReplaceKnowledgeBaseTags(ctx context.Context, workspaceID, knowledgeBaseID, actorID string, tagIDs []string, now time.Time) error {
	if err := s.ensureDB(); err != nil {
		return err
	}
	if !s.inTransactionStore() {
		return s.Transaction(ctx, func(tx *Store) error {
			return tx.ReplaceKnowledgeBaseTags(ctx, workspaceID, knowledgeBaseID, actorID, tagIDs, now)
		})
	}
	workspaceID = strings.TrimSpace(workspaceID)
	knowledgeBaseID = strings.TrimSpace(knowledgeBaseID)
	if err := s.db.WithContext(ctx).Where("workspace_id = ? AND knowledge_base_id = ?", workspaceID, knowledgeBaseID).Delete(&KnowledgeBaseTagAssignment{}).Error; err != nil {
		return fmt.Errorf("replace knowledge base tags: %w", err)
	}
	tagIDs = uniqueTrimmedStrings(tagIDs)
	if len(tagIDs) == 0 {
		return nil
	}
	assignments := make([]KnowledgeBaseTagAssignment, 0, len(tagIDs))
	for _, tagID := range tagIDs {
		assignments = append(assignments, KnowledgeBaseTagAssignment{
			WorkspaceID:     workspaceID,
			KnowledgeBaseID: knowledgeBaseID,
			TagID:           tagID,
			CreatedBy:       strings.TrimSpace(actorID),
			CreatedAt:       now,
		})
	}
	if err := s.db.WithContext(ctx).Create(&assignments).Error; err != nil {
		return fmt.Errorf("create knowledge base tag assignments: %w", err)
	}
	return nil
}

func (s *Store) DecorateKnowledgeBaseTaxonomy(ctx context.Context, workspaceID string, bases []KnowledgeBase) error {
	if err := s.ensureDB(); err != nil {
		return err
	}
	if len(bases) == 0 {
		return nil
	}
	workspaceID = strings.TrimSpace(workspaceID)
	kbIDs := make([]string, 0, len(bases))
	for _, base := range bases {
		if id := strings.TrimSpace(base.ID); id != "" {
			kbIDs = append(kbIDs, id)
		}
	}
	if len(kbIDs) == 0 {
		return nil
	}
	groups, err := s.ListKnowledgeBaseGroups(ctx, workspaceID)
	if err != nil {
		return err
	}
	groupByID := make(map[string]KnowledgeBaseGroup, len(groups))
	for _, group := range groups {
		groupByID[group.ID] = group
	}
	tags, err := s.ListKnowledgeBaseTags(ctx, workspaceID)
	if err != nil {
		return err
	}
	tagByID := make(map[string]KnowledgeBaseTag, len(tags))
	tagRank := make(map[string]int, len(tags))
	for index, tag := range tags {
		tagByID[tag.ID] = tag
		tagRank[tag.ID] = index
	}
	var assignments []KnowledgeBaseTagAssignment
	if err := s.db.WithContext(ctx).
		Where("workspace_id = ? AND knowledge_base_id IN ?", workspaceID, kbIDs).
		Find(&assignments).Error; err != nil {
		return fmt.Errorf("load knowledge base tag assignments: %w", err)
	}
	tagsByKB := make(map[string][]KnowledgeBaseTag, len(bases))
	for _, assignment := range assignments {
		if tag, ok := tagByID[assignment.TagID]; ok {
			tagsByKB[assignment.KnowledgeBaseID] = append(tagsByKB[assignment.KnowledgeBaseID], tag)
		}
	}
	for kbID := range tagsByKB {
		sort.Slice(tagsByKB[kbID], func(i, j int) bool {
			return tagRank[tagsByKB[kbID][i].ID] < tagRank[tagsByKB[kbID][j].ID]
		})
	}
	var sourceCounts []struct {
		KnowledgeBaseID string
		Count           int64
	}
	if err := s.db.WithContext(ctx).Model(&Source{}).
		Select("knowledge_base_id, COUNT(*) AS count").
		Where("workspace_id = ? AND knowledge_base_id IN ? AND deleted_at IS NULL", workspaceID, kbIDs).
		Group("knowledge_base_id").
		Scan(&sourceCounts).Error; err != nil {
		return fmt.Errorf("count knowledge base sources: %w", err)
	}
	countByKB := make(map[string]int64, len(sourceCounts))
	for _, row := range sourceCounts {
		countByKB[row.KnowledgeBaseID] = row.Count
	}
	for index := range bases {
		if group, ok := groupByID[bases[index].GroupID]; ok {
			groupCopy := group
			bases[index].Group = &groupCopy
		}
		bases[index].Tags = tagsByKB[bases[index].ID]
		bases[index].SourceCount = countByKB[bases[index].ID]
	}
	return nil
}
