package workspace

import (
	"context"
	"fmt"
	"strings"
	"time"
	"unicode/utf8"

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

const (
	channelSidebarPinnedSectionKey     = "__pinned__"
	channelSidebarDefaultSectionKey    = "__default__"
	maxChannelSidebarSections          = 50
	maxChannelSidebarSectionIDRunes    = 64
	maxChannelSidebarSectionNameRunes  = 24
	maxChannelSidebarSectionEmojiRunes = 16
	maxChannelSidebarChannelIDs        = 1000
)

// ChannelSidebarSection is one user-private channel grouping in a workspace.
type ChannelSidebarSection struct {
	ID         string
	Name       string
	Emoji      string
	ChannelIDs []string
}

// ChannelSidebarPreferenceState is the atomically persisted sidebar grouping state.
type ChannelSidebarPreferenceState struct {
	Sections  []ChannelSidebarSection
	Collapsed map[string]bool
}

// ChannelSidebarPreference is one member's persisted workspace sidebar state.
type ChannelSidebarPreference struct {
	State     ChannelSidebarPreferenceState
	Revision  int64
	UpdatedAt time.Time
}

// GetChannelSidebarPreferenceForUser loads the current human member's private
// channel grouping. The boolean is false until that user has persisted a state.
func (store *Store) GetChannelSidebarPreferenceForUser(
	ctx context.Context,
	userID string,
	workspaceID string,
) (ChannelSidebarPreference, bool, error) {
	membership, err := store.requireWorkspaceAccess(ctx, userID, workspaceID)
	if err != nil {
		return ChannelSidebarPreference{}, false, err
	}

	var stateJSON string
	var revision int64
	var updatedAt time.Time
	err = store.db.QueryRow(ctx, `
SELECT state_json, revision, updated_at
FROM channel_sidebar_preferences
WHERE workspace_id = $1 AND member_id = $2
`, strings.TrimSpace(workspaceID), membership.Member.ID).Scan(&stateJSON, &revision, &updatedAt)
	if coredb.IsNotFound(err) {
		return ChannelSidebarPreference{}, false, nil
	}
	if err != nil {
		return ChannelSidebarPreference{}, false, fmt.Errorf("load channel sidebar preference: %w", err)
	}

	var state ChannelSidebarPreferenceState
	if err := model.UnmarshalJSONString("channel sidebar preference", stateJSON, &state); err != nil {
		return ChannelSidebarPreference{}, false, err
	}
	normalized, err := normalizeChannelSidebarPreferenceState(state)
	if err != nil {
		return ChannelSidebarPreference{}, false, fmt.Errorf("validate stored channel sidebar preference: %w", err)
	}
	return ChannelSidebarPreference{State: normalized, Revision: revision, UpdatedAt: updatedAt}, true, nil
}

// PutChannelSidebarPreferenceForUser atomically replaces the current human
// member's private channel grouping for this workspace.
func (store *Store) PutChannelSidebarPreferenceForUser(
	ctx context.Context,
	userID string,
	workspaceID string,
	state ChannelSidebarPreferenceState,
	expectedRevision *int64,
) (ChannelSidebarPreference, error) {
	membership, err := store.requireWorkspaceAccess(ctx, userID, workspaceID)
	if err != nil {
		return ChannelSidebarPreference{}, err
	}
	normalized, err := normalizeChannelSidebarPreferenceState(state)
	if err != nil {
		return ChannelSidebarPreference{}, err
	}
	stateJSON, err := model.MarshalJSONString("channel sidebar preference", normalized)
	if err != nil {
		return ChannelSidebarPreference{}, err
	}

	trimmedWorkspaceID := strings.TrimSpace(workspaceID)
	if expectedRevision == nil {
		_, createErr := store.db.Exec(ctx, `
INSERT INTO channel_sidebar_preferences (
  workspace_id, member_id, state_json, revision, created_at, updated_at
) VALUES ($1, $2, $3, 1, NOW(), NOW())
`, trimmedWorkspaceID, membership.Member.ID, stateJSON)
		if createErr != nil {
			if coredb.IsUniqueViolation(createErr) {
				return ChannelSidebarPreference{}, conflict("channel sidebar preference changed; refresh and retry")
			}
			return ChannelSidebarPreference{}, fmt.Errorf("create channel sidebar preference: %w", createErr)
		}
	} else {
		result, updateErr := store.db.Exec(ctx, `
UPDATE channel_sidebar_preferences
SET state_json = $3,
    revision = revision + 1,
    updated_at = NOW()
WHERE workspace_id = $1
  AND member_id = $2
  AND revision = $4
`, trimmedWorkspaceID, membership.Member.ID, stateJSON, *expectedRevision)
		if updateErr != nil {
			return ChannelSidebarPreference{}, fmt.Errorf("update channel sidebar preference: %w", updateErr)
		}
		if result.RowsAffected() == 0 {
			return ChannelSidebarPreference{}, conflict("channel sidebar preference changed; refresh and retry")
		}
	}

	preference, exists, err := store.GetChannelSidebarPreferenceForUser(ctx, userID, trimmedWorkspaceID)
	if err != nil {
		return ChannelSidebarPreference{}, err
	}
	if !exists {
		return ChannelSidebarPreference{}, fmt.Errorf("updated channel sidebar preference was not found")
	}
	return preference, nil
}

func normalizeChannelSidebarPreferenceState(
	state ChannelSidebarPreferenceState,
) (ChannelSidebarPreferenceState, error) {
	if len(state.Sections) > maxChannelSidebarSections {
		return ChannelSidebarPreferenceState{}, badRequest(fmt.Sprintf(
			"channel sidebar may contain at most %d sections",
			maxChannelSidebarSections,
		))
	}
	if len(state.Collapsed) > maxChannelSidebarSections+2 {
		return ChannelSidebarPreferenceState{}, badRequest(fmt.Sprintf(
			"channel sidebar may contain at most %d collapse entries",
			maxChannelSidebarSections+2,
		))
	}

	sections := make([]ChannelSidebarSection, 0, len(state.Sections))
	sectionIDs := make(map[string]struct{}, len(state.Sections))
	channelIDs := make(map[string]struct{})
	for _, section := range state.Sections {
		sectionID := strings.TrimSpace(section.ID)
		if sectionID == "" {
			return ChannelSidebarPreferenceState{}, badRequest("channel sidebar section id is required")
		}
		if utf8.RuneCountInString(sectionID) > maxChannelSidebarSectionIDRunes {
			return ChannelSidebarPreferenceState{}, badRequest("channel sidebar section id is too long")
		}
		if _, duplicate := sectionIDs[sectionID]; duplicate {
			return ChannelSidebarPreferenceState{}, badRequest("channel sidebar section ids must be unique")
		}
		if sectionID == channelSidebarPinnedSectionKey || sectionID == channelSidebarDefaultSectionKey {
			return ChannelSidebarPreferenceState{}, badRequest("channel sidebar section id is reserved")
		}
		sectionIDs[sectionID] = struct{}{}

		name := strings.TrimSpace(section.Name)
		if name == "" {
			return ChannelSidebarPreferenceState{}, badRequest("channel sidebar section name is required")
		}
		if utf8.RuneCountInString(name) > maxChannelSidebarSectionNameRunes {
			return ChannelSidebarPreferenceState{}, badRequest("channel sidebar section name is too long")
		}
		emoji := strings.TrimSpace(section.Emoji)
		if emoji == "" {
			return ChannelSidebarPreferenceState{}, badRequest("channel sidebar section icon is required")
		}
		if utf8.RuneCountInString(emoji) > maxChannelSidebarSectionEmojiRunes {
			return ChannelSidebarPreferenceState{}, badRequest("channel sidebar section icon is too long")
		}

		if len(section.ChannelIDs) > maxChannelSidebarChannelIDs {
			return ChannelSidebarPreferenceState{}, badRequest(fmt.Sprintf(
				"channel sidebar may contain at most %d channel assignments",
				maxChannelSidebarChannelIDs,
			))
		}
		normalizedChannelIDs := make([]string, 0, len(section.ChannelIDs))
		for _, channelIDValue := range section.ChannelIDs {
			channelID := strings.TrimSpace(channelIDValue)
			if channelID == "" {
				return ChannelSidebarPreferenceState{}, badRequest("channel sidebar channel id is required")
			}
			if utf8.RuneCountInString(channelID) > 255 {
				return ChannelSidebarPreferenceState{}, badRequest("channel sidebar channel id is too long")
			}
			if _, duplicate := channelIDs[channelID]; duplicate {
				return ChannelSidebarPreferenceState{}, badRequest("a channel may belong to only one sidebar section")
			}
			channelIDs[channelID] = struct{}{}
			if len(channelIDs) > maxChannelSidebarChannelIDs {
				return ChannelSidebarPreferenceState{}, badRequest(fmt.Sprintf(
					"channel sidebar may contain at most %d channel assignments",
					maxChannelSidebarChannelIDs,
				))
			}
			normalizedChannelIDs = append(normalizedChannelIDs, channelID)
		}
		sections = append(sections, ChannelSidebarSection{
			ID:         sectionID,
			Name:       name,
			Emoji:      emoji,
			ChannelIDs: normalizedChannelIDs,
		})
	}
	collapsed := make(map[string]bool)
	for key, value := range state.Collapsed {
		trimmedKey := strings.TrimSpace(key)
		if !value || trimmedKey == "" {
			continue
		}
		if trimmedKey != channelSidebarPinnedSectionKey && trimmedKey != channelSidebarDefaultSectionKey {
			if _, exists := sectionIDs[trimmedKey]; !exists {
				continue
			}
		}
		collapsed[trimmedKey] = true
	}
	return ChannelSidebarPreferenceState{Sections: sections, Collapsed: collapsed}, nil
}
