package workspace

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

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/pinyinsearch"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
)

// ErrConflict indicates workspace creation hit a slug collision that could not be resolved.
var ErrConflict = errors.New("workspace conflict")

// Summary is the shared workspace list item.
type Summary struct {
	WorkspaceID       string
	OrganizationID    string
	Slug              string
	Name              string
	MemberID          string
	MemberUserID      string
	MemberDisplayName string
	WorkspaceRole     string
	PlanKey           string
	AllChannelID      string
}

type invalidArgumentError struct {
	message string
}

func (err invalidArgumentError) Error() string {
	return err.message
}

func (err invalidArgumentError) Unwrap() error {
	return domain.ErrInvalidArgument
}

type createOptions struct {
	kind                string
	organizationID      string
	name                string
	slug                string
	workspaceGroupID    string
	defaultAllMessage   string
	allowSlugAutoRepair bool
	validateSlug        func(string) error
}

// CreatePersonalWorkspace creates the default workspace bootstrapped during auth flows.
func CreatePersonalWorkspace(ctx context.Context, tx coredb.Tx, owner model.User, name string, workspaceGroupID string) (Summary, error) {
	return CreatePersonalWorkspaceWithNewOrganization(ctx, tx, owner, name, workspaceGroupID)
}

// CreatePersonalWorkspaceWithNewOrganization creates a minimal personal organization
// and its default workspace in the same transaction.
func CreatePersonalWorkspaceWithNewOrganization(ctx context.Context, tx coredb.Tx, owner model.User, name string, workspaceGroupID string) (Summary, error) {
	return createWorkspaceWithNewPersonalOrganization(ctx, tx, owner, name, "", workspaceGroupID, true)
}

// CreatePersonalWorkspaceForOrganization creates a default workspace owned by organizationID.
func CreatePersonalWorkspaceForOrganization(ctx context.Context, tx coredb.Tx, organizationID string, owner model.User, name string, workspaceGroupID string) (Summary, error) {
	return create(ctx, tx, owner, createOptions{
		kind:                "workspace",
		organizationID:      organizationID,
		name:                name,
		workspaceGroupID:    workspaceGroupID,
		defaultAllMessage:   "Everyone in the workspace.",
		allowSlugAutoRepair: true,
		validateSlug:        domain.ValidateWorkspaceSlug,
	})
}

// CreateWorkspace creates a user-visible workspace.
func CreateWorkspace(ctx context.Context, tx coredb.Tx, owner model.User, name string, slug string, workspaceGroupID string) (Summary, error) {
	return createWorkspaceWithNewPersonalOrganization(ctx, tx, owner, name, slug, workspaceGroupID, false)
}

// CreateWorkspaceForOrganization creates a user-visible workspace owned by organizationID.
func CreateWorkspaceForOrganization(ctx context.Context, tx coredb.Tx, organizationID string, owner model.User, name string, slug string, workspaceGroupID string) (Summary, error) {
	return create(ctx, tx, owner, createOptions{
		kind:              "workspace",
		organizationID:    organizationID,
		name:              name,
		slug:              slug,
		workspaceGroupID:  workspaceGroupID,
		defaultAllMessage: "Everyone in the workspace.",
		validateSlug:      domain.ValidateWorkspaceSlug,
	})
}

func create(ctx context.Context, tx coredb.Tx, owner model.User, options createOptions) (Summary, error) {
	organizationID := strings.TrimSpace(options.organizationID)
	if organizationID == "" {
		return Summary{}, invalidArgumentError{message: "organization id is required"}
	}

	trimmedName := strings.TrimSpace(options.name)
	if trimmedName == "" {
		return Summary{}, invalidArgumentError{message: fmt.Sprintf("%s name is required", options.kind)}
	}

	resolvedSlug := strings.TrimSpace(options.slug)
	if resolvedSlug == "" {
		resolvedSlug = slugifyName(trimmedName)
	}
	if resolvedSlug == "" {
		return Summary{}, invalidArgumentError{message: fmt.Sprintf("%s slug is required", options.kind)}
	}
	if err := options.validateSlug(resolvedSlug); err != nil {
		return Summary{}, err
	}

	workspaceGroupID := strings.TrimSpace(options.workspaceGroupID)
	if workspaceGroupID == "" {
		return Summary{}, invalidArgumentError{message: fmt.Sprintf("%s placement group id is required", options.kind)}
	}

	workspaceID, err := identifiers.NewEntityID("ws")
	if err != nil {
		return Summary{}, fmt.Errorf("generate %s id: %w", options.kind, err)
	}
	memberID, err := identifiers.NewMemberID()
	if err != nil {
		return Summary{}, fmt.Errorf("generate owner member id: %w", err)
	}
	channelID, err := identifiers.NewEntityID("chn")
	if err != nil {
		return Summary{}, fmt.Errorf("generate all channel id: %w", err)
	}

	now := time.Now().UTC()
	if options.allowSlugAutoRepair {
		resolvedSlug, err = insertWorkspaceWithSlugRepair(ctx, tx, workspaceID, organizationID, resolvedSlug, trimmedName, owner.ID, now)
	} else {
		err = insertWorkspace(ctx, tx, workspaceID, organizationID, resolvedSlug, trimmedName, now)
		if errors.Is(err, ErrConflict) {
			return Summary{}, err
		}
	}
	if err != nil {
		return Summary{}, err
	}
	if err := EnsureWorkspacePlacementInTx(ctx, tx, workspaceID, workspaceGroupID, now); err != nil {
		return Summary{}, err
	}
	if err := initializeScopeActivityWorkspaceCounterInTx(ctx, tx, workspaceID, now); err != nil {
		return Summary{}, err
	}

	role := string(domain.WorkspaceRoleOwner)
	ownerDisplayName := displayNameFromEmail(owner.Email)
	ownerPinyinFull, ownerPinyinInitials := pinyinsearch.Variants(ownerDisplayName)
	if _, err := tx.Exec(ctx, `
INSERT INTO members (id, workspace_id, type, display_name, display_name_pinyin, display_name_pinyin_initials, user_id, workspace_role, created_at, updated_at)
VALUES ($1, $2, 'human', $3, $4, $5, $6, $7, $8, $8)
`, memberID, workspaceID, ownerDisplayName, ownerPinyinFull, ownerPinyinInitials, owner.ID, role, now); err != nil {
		return Summary{}, fmt.Errorf("insert owner member: %w", err)
	}
	if _, err := tx.Exec(ctx, `
INSERT INTO scopes (
  id, workspace_id, type, parent_scope_id, root_message_id, root_message_short_id,
  identity_key, latest_seq, latest_global_seq, latest_message_at, archived_until_seq,
  status, created_by_member_id, created_at, updated_at, archived_at
) VALUES ($1, $2, 'channel', NULL, NULL, NULL, $3, 0, 0, NULL, 0, 'active', $4, $5, $5, NULL)
`, channelID, workspaceID, "channel:"+channelID, memberID, now); err != nil {
		return Summary{}, fmt.Errorf("insert #all scope: %w", err)
	}
	if _, err := tx.Exec(ctx, `
INSERT INTO scope_channel_profiles (
  scope_id, workspace_id, slug, display_name, description, visibility,
  owner_member_id, is_all_channel, created_at, updated_at
) VALUES ($1, $2, 'all', 'all', $3, 'public', $4, TRUE, $5, $5)
`, channelID, workspaceID, options.defaultAllMessage, memberID, now); err != nil {
		return Summary{}, fmt.Errorf("insert #all scope channel profile: %w", err)
	}
	if _, err := tx.Exec(ctx, `
INSERT INTO scope_memberships (
  scope_id, member_id, visibility, owner_member_id, last_read_seq,
  last_read_global_seq, last_activity_global_seq, owner_context_dismissed_through_global_seq,
  effective_from_seq, effective_to_seq, authority_from_at, authority_to_at,
  created_at, updated_at, removed_at, archived_at
) VALUES ($1, $2, 'public', NULL, 0, 0, 0, 0, 1, NULL, LEAST($3, CURRENT_TIMESTAMP(6)), NULL, $3, $3, NULL, NULL)
`, channelID, memberID, now); err != nil {
		return Summary{}, fmt.Errorf("insert #all scope membership: %w", err)
	}
	if _, err := tx.Exec(ctx, `
INSERT INTO scope_notification_settings (
  scope_id, member_id, state, created_at, updated_at
) VALUES ($1, $2, 'active', $3, $3)
`, channelID, memberID, now); err != nil {
		return Summary{}, fmt.Errorf("insert #all scope notification setting: %w", err)
	}

	return Summary{
		WorkspaceID:       workspaceID,
		OrganizationID:    organizationID,
		Slug:              resolvedSlug,
		Name:              trimmedName,
		MemberID:          memberID,
		MemberUserID:      owner.ID,
		MemberDisplayName: workspaceSummaryMemberDisplayName(owner.DisplayName, displayNameFromEmail(owner.Email), memberID),
		WorkspaceRole:     role,
	}, nil
}

func initializeScopeActivityWorkspaceCounterInTx(ctx context.Context, tx coredb.Tx, workspaceID string, now time.Time) error {
	if _, err := tx.Exec(ctx, `
INSERT INTO scope_activity_workspace_counters (
  workspace_id, last_sequence, updated_at
) VALUES ($1, 0, $2)
ON DUPLICATE KEY UPDATE updated_at = scope_activity_workspace_counters.updated_at
`, workspaceID, now.UTC()); err != nil {
		return fmt.Errorf("initialize activity counter for workspace %s: %w", workspaceID, err)
	}
	return nil
}

func createWorkspaceWithNewPersonalOrganization(ctx context.Context, tx coredb.Tx, owner model.User, name string, slug string, workspaceGroupID string, allowSlugAutoRepair bool) (Summary, error) {
	trimmedName := strings.TrimSpace(name)
	if trimmedName == "" {
		return Summary{}, invalidArgumentError{message: "workspace name is required"}
	}

	organizationID, err := identifiers.NewEntityID("org")
	if err != nil {
		return Summary{}, fmt.Errorf("generate personal organization id: %w", err)
	}
	defaultTeamID, err := identifiers.NewEntityID("orgt")
	if err != nil {
		return Summary{}, fmt.Errorf("generate personal organization team id: %w", err)
	}
	defaultUnitID, err := identifiers.NewEntityID("orgu")
	if err != nil {
		return Summary{}, fmt.Errorf("generate personal organization unit id: %w", err)
	}
	memberID, err := identifiers.NewEntityID("orgm")
	if err != nil {
		return Summary{}, fmt.Errorf("generate personal organization member id: %w", err)
	}
	teamMembershipID, err := identifiers.NewEntityID("oatm")
	if err != nil {
		return Summary{}, fmt.Errorf("generate personal organization team membership id: %w", err)
	}
	unitMembershipID, err := identifiers.NewEntityID("oaum")
	if err != nil {
		return Summary{}, fmt.Errorf("generate personal organization unit membership id: %w", err)
	}

	now := time.Now().UTC()
	organizationSlug := personalOrganizationSlug(trimmedName, organizationID)
	if _, err := tx.Exec(ctx, `
INSERT INTO organizations (id, slug, name, created_by_user_id, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $5)
`, organizationID, organizationSlug, personalOrganizationName(trimmedName), owner.ID, now); err != nil {
		return Summary{}, fmt.Errorf("insert personal organization: %w", err)
	}
	if _, err := tx.Exec(ctx, `
INSERT INTO organization_settings (
    organization_id,
    status,
    timezone,
    join_policy,
    directory_backend,
    workspace_bootstrap_policy,
    service_install_policy,
    created_at,
    updated_at
) VALUES ($1, 'active', 'UTC', 'admin_invite', 'embedded', 'manual', 'workspace_admin', $2, $2)
`, organizationID, now); err != nil {
		return Summary{}, fmt.Errorf("insert personal organization settings: %w", err)
	}
	if _, err := tx.Exec(ctx, `
INSERT INTO organization_teams (
    id,
    organization_id,
    name,
    slug,
    description,
    status,
    source,
    is_default,
    created_at,
    updated_at
) VALUES ($1, $2, 'General', 'general', '', 'active', 'system', TRUE, $3, $3)
`, defaultTeamID, organizationID, now); err != nil {
		return Summary{}, fmt.Errorf("insert personal organization default team: %w", err)
	}
	if _, err := tx.Exec(ctx, `
INSERT INTO organization_units (
    id,
    organization_id,
    parent_unit_id,
    manager_member_id,
    name,
    slug,
    status,
    external_provider,
    external_id,
    synced_at,
    is_default,
    created_by_user_id,
    created_at,
    updated_at
) VALUES ($1, $2, NULL, NULL, 'General', 'general', 'active', '', '', NULL, TRUE, $3, $4, $4)
`, defaultUnitID, organizationID, owner.ID, now); err != nil {
		return Summary{}, fmt.Errorf("insert personal organization default unit: %w", err)
	}
	if _, err := tx.Exec(ctx, `
INSERT INTO organization_members (
    id,
    organization_id,
    user_id,
    primary_team_id,
    primary_unit_id,
    org_role,
    status,
    source,
    default_workspace_role,
    joined_at,
    invited_by_user_id,
    created_by_user_id,
    created_at,
    updated_at
) VALUES ($1, $2, $3, $4, $5, 'org_owner', 'active', 'local', 'member', $6, $3, $3, $6, $6)
`, memberID, organizationID, owner.ID, defaultTeamID, defaultUnitID, now); err != nil {
		return Summary{}, fmt.Errorf("insert personal organization member: %w", err)
	}
	if _, err := tx.Exec(ctx, `
INSERT INTO organization_member_team_memberships (
    id,
    organization_id,
    organization_team_id,
    organization_member_id,
    membership_role,
    source,
    status,
    is_primary,
    created_by_user_id,
    created_at,
    updated_at
) VALUES ($1, $2, $3, $4, 'team_member', 'direct', 'active', TRUE, $5, $6, $6)
`, teamMembershipID, organizationID, defaultTeamID, memberID, owner.ID, now); err != nil {
		return Summary{}, fmt.Errorf("insert personal organization team membership: %w", err)
	}
	if _, err := tx.Exec(ctx, `
INSERT INTO organization_member_unit_memberships (
    id,
    organization_id,
    organization_unit_id,
    organization_member_id,
    source,
    status,
    is_primary,
    created_by_user_id,
    created_at,
    updated_at
) VALUES ($1, $2, $3, $4, 'direct', 'active', TRUE, $5, $6, $6)
`, unitMembershipID, organizationID, defaultUnitID, memberID, owner.ID, now); err != nil {
		return Summary{}, fmt.Errorf("insert personal organization unit membership: %w", err)
	}

	created, err := create(ctx, tx, owner, createOptions{
		kind:                "workspace",
		organizationID:      organizationID,
		name:                trimmedName,
		slug:                slug,
		workspaceGroupID:    workspaceGroupID,
		defaultAllMessage:   "Everyone in the workspace.",
		allowSlugAutoRepair: allowSlugAutoRepair,
		validateSlug:        domain.ValidateWorkspaceSlug,
	})
	if err != nil {
		return Summary{}, err
	}
	if err := attachDefaultOrganizationWorkspace(ctx, tx, organizationID, defaultTeamID, owner.ID, created.WorkspaceID, now); err != nil {
		return Summary{}, err
	}
	return created, nil
}

func attachDefaultOrganizationWorkspace(ctx context.Context, tx coredb.Tx, organizationID string, defaultTeamID string, ownerID string, workspaceID string, now time.Time) error {
	if _, err := tx.Exec(ctx, `
UPDATE organization_settings
SET default_workspace_id = $2,
    updated_at = $3
WHERE organization_id = $1
`, organizationID, workspaceID, now); err != nil {
		return fmt.Errorf("update personal organization default workspace: %w", err)
	}

	grantID, err := identifiers.NewEntityID("ogtg")
	if err != nil {
		return fmt.Errorf("generate personal organization team grant id: %w", err)
	}
	if _, err := tx.Exec(ctx, `
INSERT INTO organization_workspace_team_grants (
    id,
    organization_id,
    workspace_id,
    organization_team_id,
    workspace_role,
    status,
    source,
    granted_by_user_id,
    created_at,
    updated_at
) VALUES ($1, $2, $3, $4, 'member', 'active', 'manual', $5, $6, $6)
`, grantID, organizationID, workspaceID, defaultTeamID, ownerID, now); err != nil {
		return fmt.Errorf("insert personal organization workspace team grant: %w", err)
	}
	return nil
}

func insertWorkspaceWithSlugRepair(ctx context.Context, tx coredb.Tx, workspaceID string, organizationID string, slug string, name string, userID string, now time.Time) (string, error) {
	const workspaceInsertSavepoint = "workspace_insert"
	if _, err := tx.Exec(ctx, "SAVEPOINT "+workspaceInsertSavepoint); err != nil {
		return "", fmt.Errorf("create workspace savepoint: %w", err)
	}

	if err := insertWorkspace(ctx, tx, workspaceID, organizationID, slug, name, now); err != nil {
		if !errors.Is(err, ErrConflict) {
			return "", err
		}

		if _, rollbackErr := tx.Exec(ctx, "ROLLBACK TO SAVEPOINT "+workspaceInsertSavepoint); rollbackErr != nil {
			return "", fmt.Errorf("rollback workspace savepoint: %w", rollbackErr)
		}

		fallbackSlug, slugErr := slugWithUserSuffix(slug, userID)
		if slugErr != nil {
			return "", fmt.Errorf("generate unique workspace slug: %w", slugErr)
		}
		if err := insertWorkspace(ctx, tx, workspaceID, organizationID, fallbackSlug, name, now); err != nil {
			if errors.Is(err, ErrConflict) {
				return "", ErrConflict
			}
			return "", err
		}
		slug = fallbackSlug
	}

	if _, err := tx.Exec(ctx, "RELEASE SAVEPOINT "+workspaceInsertSavepoint); err != nil {
		return "", fmt.Errorf("release workspace savepoint: %w", err)
	}
	return slug, nil
}

func insertWorkspace(ctx context.Context, tx coredb.Tx, workspaceID string, organizationID string, slug string, name string, now time.Time) error {
	if _, err := tx.Exec(ctx, `
INSERT INTO workspaces (id, organization_id, slug, name, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $5)
`, workspaceID, organizationID, slug, name, now); err != nil {
		if isWorkspaceSlugConflict(err) {
			return ErrConflict
		}
		return fmt.Errorf("insert workspace: %w", err)
	}
	return nil
}

func isWorkspaceSlugConflict(err error) bool {
	constraint, ok := coredb.UniqueViolationConstraint(err)
	return ok && (constraint == "slug" || constraint == "workspaces_slug_key")
}

func displayNameFromEmail(email string) string {
	localPart := strings.TrimSpace(strings.SplitN(email, "@", 2)[0])
	if localPart == "" {
		return "user"
	}
	return localPart
}

func personalOrganizationName(workspaceName string) string {
	name := strings.TrimSpace(workspaceName)
	if name == "" {
		return "Personal Organization"
	}

	if ownerName, ok := strings.CutSuffix(name, "'s Workspace"); ok {
		ownerName = strings.TrimSpace(ownerName)
		if ownerName != "" {
			name = ownerName
			const suffix = "'s Organization"
			if len(name)+len(suffix) > 255 {
				name = strings.TrimSpace(name[:255-len(suffix)])
			}
			if name == "" {
				return "Personal Organization"
			}
			return name + suffix
		}
	}

	const suffix = " Organization"
	if len(name)+len(suffix) > 255 {
		name = strings.TrimSpace(name[:255-len(suffix)])
	}
	if name == "" {
		return "Personal Organization"
	}
	return name + suffix
}

func personalOrganizationSlug(workspaceName string, organizationID string) string {
	base := slugifyName(workspaceName)
	if base == "" {
		base = "personal"
	}
	const prefix = "personal-"
	suffix := organizationID
	if parts := strings.SplitN(organizationID, "_", 2); len(parts) == 2 && strings.TrimSpace(parts[1]) != "" {
		suffix = strings.TrimSpace(parts[1])
	}
	if len(suffix) > 8 {
		suffix = suffix[len(suffix)-8:]
	}

	maxBaseLen := 63 - len(prefix) - len(suffix) - 1
	if maxBaseLen <= 0 {
		return prefix + suffix
	}
	if len(base) > maxBaseLen {
		base = strings.TrimRight(base[:maxBaseLen], "-")
	}
	if base == "" {
		base = "personal"
	}
	return prefix + base + "-" + suffix
}

func slugifyName(name string) string {
	slug := strings.ToLower(strings.TrimSpace(name))
	slug = strings.Map(func(r rune) rune {
		switch {
		case r >= 'a' && r <= 'z':
			return r
		case r >= '0' && r <= '9':
			return r
		default:
			return '-'
		}
	}, slug)
	slug = strings.Trim(slug, "-")
	for strings.Contains(slug, "--") {
		slug = strings.ReplaceAll(slug, "--", "-")
	}
	if len(slug) > 63 {
		slug = strings.TrimRight(slug[:63], "-")
	}
	return slug
}

func slugWithUserSuffix(baseSlug string, userID string) (string, error) {
	trimmedBase := strings.TrimSpace(baseSlug)
	if trimmedBase == "" {
		return "", fmt.Errorf("workspace slug is required")
	}

	parts := strings.SplitN(userID, "_", 2)
	if len(parts) != 2 || strings.TrimSpace(parts[1]) == "" {
		return "", fmt.Errorf("invalid user id %q", userID)
	}
	suffix := strings.TrimSpace(parts[1])
	if len(suffix) > 8 {
		suffix = suffix[len(suffix)-8:]
	}

	const maxLen = 63
	maxBaseLen := maxLen - len(suffix) - 1
	if maxBaseLen <= 0 {
		return "", fmt.Errorf("workspace slug suffix is too long")
	}
	if len(trimmedBase) > maxBaseLen {
		trimmedBase = strings.TrimRight(trimmedBase[:maxBaseLen], "-")
	}
	if trimmedBase == "" {
		return "", fmt.Errorf("workspace slug base is empty after trimming")
	}
	return trimmedBase + "-" + suffix, nil
}
