package app

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
)

const (
	contentModerationBlockedMessage = "content contains disallowed information"
	moderationKeywordCacheTTL       = 10 * time.Minute
)

type moderationMatch struct {
	Keyword string
}

func (store *Store) buildModerationBlockForHumanMessage(ctx context.Context, prepared preparedUserAppMessage, userID string, workspaceID string, skip bool) (moderationBlockRecord, bool, error) {
	if skip {
		return moderationBlockRecord{}, false, nil
	}
	match, matched, err := store.matchEnabledModerationKeyword(ctx, prepared.trimmedContent)
	if err != nil || !matched {
		return moderationBlockRecord{}, false, err
	}
	return moderationBlockRecord{
		WorkspaceID:    strings.TrimSpace(workspaceID),
		ScopeID:        message.ScopeIDForThreadAccess(prepared.target),
		ScopeType:      moderationScopeType(prepared.target),
		UserID:         strings.TrimSpace(userID),
		MemberID:       strings.TrimSpace(prepared.membership.Member.ID),
		Content:        prepared.trimmedContent,
		MatchedKeyword: match.Keyword,
		CreatedAt:      time.Now().UTC(),
	}, true, nil
}

func (store *Store) matchEnabledModerationKeyword(ctx context.Context, content string) (moderationMatch, bool, error) {
	trimmedContent := strings.TrimSpace(content)
	if trimmedContent == "" {
		return moderationMatch{}, false, nil
	}
	keywords, err := store.enabledModerationKeywords(ctx)
	if err != nil {
		return moderationMatch{}, false, err
	}
	normalizedContent := strings.ToLower(trimmedContent)
	for _, keyword := range keywords {
		if normalizedModerationKeywordMatches(normalizedContent, keyword) {
			return moderationMatch{Keyword: keyword}, true, nil
		}
	}
	return moderationMatch{}, false, nil
}

func moderationKeywordMatches(content string, keyword string) bool {
	return normalizedModerationKeywordMatches(strings.ToLower(strings.TrimSpace(content)), keyword)
}

func normalizedModerationKeywordMatches(normalizedContent string, keyword string) bool {
	normalizedKeyword := strings.ToLower(strings.TrimSpace(keyword))
	if normalizedKeyword == "" {
		return false
	}
	if isASCIIKeyword(normalizedKeyword) {
		return containsASCIIKeywordToken(normalizedContent, normalizedKeyword)
	}
	return strings.Contains(normalizedContent, normalizedKeyword)
}

func isASCIIKeyword(keyword string) bool {
	hasWordByte := false
	for i := 0; i < len(keyword); i++ {
		if keyword[i] >= 0x80 {
			return false
		}
		if isASCIIWordByte(keyword[i]) {
			hasWordByte = true
		}
	}
	return hasWordByte
}

func containsASCIIKeywordToken(content string, keyword string) bool {
	start := 0
	for {
		index := strings.Index(content[start:], keyword)
		if index < 0 {
			return false
		}
		index += start
		end := index + len(keyword)
		if isASCIIKeywordBoundary(content, index-1) && isASCIIKeywordBoundary(content, end) {
			return true
		}
		start = index + 1
	}
}

func isASCIIKeywordBoundary(content string, index int) bool {
	if index < 0 || index >= len(content) {
		return true
	}
	b := content[index]
	return !isASCIIWordByte(b)
}

func isASCIIWordByte(b byte) bool {
	return (b >= 'a' && b <= 'z') || (b >= '0' && b <= '9') || b == '_'
}

func moderationScopeType(target message.ThreadAccess) string {
	if target.ReplyThread != nil {
		return "thread"
	}
	return strings.TrimSpace(string(target.ThreadType))
}

func (store *Store) enabledModerationKeywords(ctx context.Context) ([]string, error) {
	now := time.Now().UTC()
	store.moderationKeywords.mu.Lock()
	if now.Before(store.moderationKeywords.expiresAt) {
		keywords := append([]string(nil), store.moderationKeywords.keywords...)
		store.moderationKeywords.mu.Unlock()
		return keywords, nil
	}
	store.moderationKeywords.mu.Unlock()

	keywords, err := store.loadEnabledModerationKeywords(ctx, store.db)
	if err != nil {
		return nil, err
	}

	store.moderationKeywords.mu.Lock()
	store.moderationKeywords.keywords = append([]string(nil), keywords...)
	store.moderationKeywords.expiresAt = now.Add(moderationKeywordCacheTTL)
	store.moderationKeywords.mu.Unlock()
	return keywords, nil
}

func (store *Store) loadEnabledModerationKeywords(ctx context.Context, queryer coredb.Queryer) ([]string, error) {
	rows, err := queryer.Query(ctx, `
SELECT keyword
FROM moderation_keywords
WHERE enabled = 1
ORDER BY created_at ASC, id ASC
`)
	if err != nil {
		return nil, fmt.Errorf("load moderation keywords: %w", err)
	}
	defer rows.Close()

	var keywords []string
	for rows.Next() {
		var keyword string
		if err := rows.Scan(&keyword); err != nil {
			return nil, fmt.Errorf("scan moderation keyword: %w", err)
		}
		trimmedKeyword := strings.TrimSpace(keyword)
		if trimmedKeyword == "" {
			continue
		}
		keywords = append(keywords, trimmedKeyword)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate moderation keywords: %w", err)
	}
	return keywords, nil
}

type moderationBlockRecord struct {
	WorkspaceID    string
	ScopeID        string
	ScopeType      string
	UserID         string
	MemberID       string
	Content        string
	MatchedKeyword string
	CreatedAt      time.Time
}

func (store *Store) insertModerationBlock(ctx context.Context, execer coredb.QueryExecer, record moderationBlockRecord) error {
	blockID, err := identifiers.NewEntityID("modblk")
	if err != nil {
		return fmt.Errorf("generate moderation block id: %w", err)
	}
	if _, err := execer.Exec(ctx, `
INSERT INTO moderation_blocks (
  id, workspace_id, scope_id, scope_type, user_id, member_id, content, matched_keyword, created_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
`, blockID, record.WorkspaceID, record.ScopeID, record.ScopeType, record.UserID, record.MemberID, record.Content, record.MatchedKeyword, record.CreatedAt); err != nil {
		return fmt.Errorf("insert moderation block: %w", err)
	}
	return nil
}
