package identity

import (
	"context"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
)

// MemberIdentityQueryer is the minimal query surface needed to resolve public
// member labels without depending on the root store facade.
type MemberIdentityQueryer interface {
	Query(context.Context, string, ...any) (coredb.Rows, error)
}

// LoadPublicMemberNamesByMemberID resolves stable public display names for
// human, agent, and service members in one workspace.
func LoadPublicMemberNamesByMemberID(ctx context.Context, queryer MemberIdentityQueryer, workspaceID string, memberIDs []string) (map[string]string, error) {
	trimmedWorkspaceID := strings.TrimSpace(workspaceID)
	if trimmedWorkspaceID == "" {
		return nil, fmt.Errorf("workspace id is required")
	}

	normalizedMemberIDs := NormalizeNonEmptyStrings(memberIDs)
	if len(normalizedMemberIDs) == 0 {
		return map[string]string{}, nil
	}

	requestedMemberIDsCTE, requestedMemberIDArgs, _, err := coredb.BuildStringValuesCTE("requested_member_ids", "member_id", normalizedMemberIDs, 2)
	if err != nil {
		return nil, err
	}

	query := fmt.Sprintf(`
WITH %s
SELECT
    m.id,
    m.type,
    m.display_name,
	COALESCE(u.display_name, '') AS user_display_name,
	COALESCE(u.name, '') AS user_name,
    COALESCE(a.name, '') AS agent_name
FROM requested_member_ids
JOIN members m ON m.id = requested_member_ids.member_id
LEFT JOIN users u ON m.type = 'human' AND u.id = m.user_id
LEFT JOIN agents a ON m.type = 'agent' AND a.workspace_id = m.workspace_id AND a.member_id = m.id
WHERE m.workspace_id = $1
`, requestedMemberIDsCTE)
	rows, err := queryer.Query(ctx, query, append([]any{trimmedWorkspaceID}, requestedMemberIDArgs...)...)
	if err != nil {
		return nil, fmt.Errorf("load public member names by member id: %w", err)
	}
	defer rows.Close()

	records := make([]model.PublicMemberIdentityRecord, 0, len(normalizedMemberIDs))
	serviceMemberIDs := make([]string, 0)
	for rows.Next() {
		var record model.PublicMemberIdentityRecord
		if err := rows.Scan(
			&record.MemberID,
			&record.MemberType,
			&record.MemberDisplayName,
			&record.UserDisplayName,
			&record.UserName,
			&record.AgentName,
		); err != nil {
			return nil, fmt.Errorf("scan public member name by member id: %w", err)
		}
		record.MemberID = strings.TrimSpace(record.MemberID)
		record.MemberType = strings.TrimSpace(record.MemberType)
		record.MemberDisplayName = strings.TrimSpace(record.MemberDisplayName)
		record.UserDisplayName = strings.TrimSpace(record.UserDisplayName)
		record.UserName = strings.TrimSpace(record.UserName)
		record.AgentName = strings.TrimSpace(record.AgentName)
		if record.MemberID == "" {
			continue
		}
		if record.MemberType == "service" {
			serviceMemberIDs = append(serviceMemberIDs, record.MemberID)
		}
		records = append(records, record)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate public member names by member id: %w", err)
	}

	serviceHandles, err := loadPublicServiceHandlesByMemberID(ctx, queryer, trimmedWorkspaceID, serviceMemberIDs)
	if err != nil {
		return nil, err
	}

	publicNames := make(map[string]string, len(normalizedMemberIDs))
	for _, record := range records {
		publicName := publicMemberNameFromRecord(record, serviceHandles)
		if strings.TrimSpace(publicName) == "" {
			continue
		}
		publicNames[record.MemberID] = publicName
	}
	return publicNames, nil
}

func loadPublicServiceHandlesByMemberID(ctx context.Context, queryer MemberIdentityQueryer, workspaceID string, memberIDs []string) (map[string]string, error) {
	normalizedMemberIDs := NormalizeNonEmptyStrings(memberIDs)
	if len(normalizedMemberIDs) == 0 {
		return map[string]string{}, nil
	}

	requestedMemberIDsCTE, requestedMemberIDArgs, _, err := coredb.BuildStringValuesCTE("requested_member_ids", "member_id", normalizedMemberIDs, 2)
	if err != nil {
		return nil, err
	}

	query := fmt.Sprintf(`
WITH %s
SELECT sm.member_id, COALESCE(sm.handle, '')
FROM requested_member_ids
JOIN service_members sm ON sm.member_id = requested_member_ids.member_id
WHERE sm.workspace_id = $1
`, requestedMemberIDsCTE)
	rows, err := queryer.Query(ctx, query, append([]any{workspaceID}, requestedMemberIDArgs...)...)
	if err != nil {
		return nil, fmt.Errorf("load public service handles by member id: %w", err)
	}
	defer rows.Close()

	handles := make(map[string]string, len(normalizedMemberIDs))
	for rows.Next() {
		var memberID string
		var handle string
		if err := rows.Scan(&memberID, &handle); err != nil {
			return nil, fmt.Errorf("scan public service handle by member id: %w", err)
		}
		trimmedMemberID := strings.TrimSpace(memberID)
		trimmedHandle := strings.TrimSpace(handle)
		if trimmedMemberID == "" || trimmedHandle == "" {
			continue
		}
		handles[trimmedMemberID] = trimmedHandle
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate public service handles by member id: %w", err)
	}
	return handles, nil
}

func publicMemberNameFromRecord(record model.PublicMemberIdentityRecord, serviceHandles map[string]string) string {
	switch record.MemberType {
	case "agent":
		return strings.TrimSpace(firstNonEmpty(record.AgentName, record.MemberDisplayName))
	case "human":
		return strings.TrimSpace(firstNonEmpty(
			record.UserDisplayName,
			record.UserName,
			record.MemberDisplayName,
		))
	case "service":
		return strings.TrimSpace(firstNonEmpty(serviceHandles[record.MemberID], record.MemberID))
	default:
		return ""
	}
}

// NormalizeNonEmptyStrings trims, de-duplicates, and removes empty strings
// while preserving first-seen order.
func NormalizeNonEmptyStrings(values []string) []string {
	if len(values) == 0 {
		return nil
	}

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

// PublicMemberNameForMemberID returns a resolved public name when available,
// falling back to the member ID for stable legacy behavior.
func PublicMemberNameForMemberID(memberID string, publicNames map[string]string) string {
	trimmedMemberID := strings.TrimSpace(memberID)
	if trimmedMemberID == "" {
		return ""
	}
	if publicName := strings.TrimSpace(publicNames[trimmedMemberID]); publicName != "" {
		return publicName
	}
	return trimmedMemberID
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if trimmed := strings.TrimSpace(value); trimmed != "" {
			return trimmed
		}
	}
	return ""
}
