package workspace

import (
	"context"
	"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"
)

type EnsureAllChannelMembershipFunc func(context.Context, coredb.Tx, string, string) error
type EnsureServiceAccountAudienceMembershipsFunc func(context.Context, coredb.Tx, string, string) error
type MarkMemberSpaceProjectionsRemovedFunc func(context.Context, coredb.QueryExecer, string, time.Time) error
type RemovePrivateEntriesForOwnerFunc func(context.Context, coredb.QueryExecer, string, time.Time) error

// EnsureHumanWorkspaceMemberInTx materializes or synchronizes the human member projection inside a transaction.
func EnsureHumanWorkspaceMemberInTx(ctx context.Context, tx coredb.Tx, workspaceID string, userID string, requestedRole string, ensureAllChannelMembership EnsureAllChannelMembershipFunc, ensureServiceAccountAudienceMemberships EnsureServiceAccountAudienceMembershipsFunc) (string, string, error) {
	resolvedRole := strings.TrimSpace(requestedRole)
	if workspaceRoleRank(resolvedRole) == 0 {
		return "", "", badRequest("invalid workspace role")
	}

	row := tx.QueryRow(ctx, `
SELECT id, workspace_role, status
FROM members
WHERE workspace_id = $1 AND user_id = $2 AND type = 'human'
`, workspaceID, userID)

	var memberID string
	var currentRole string
	var status string
	if err := row.Scan(&memberID, &currentRole, &status); err == nil {
		if resolvedRole != currentRole || status != "active" {
			if _, err := tx.Exec(ctx, `
UPDATE members
SET workspace_role = $3, status = 'active', updated_at = NOW()
WHERE workspace_id = $1 AND user_id = $2 AND type = 'human'
`, workspaceID, userID, resolvedRole); err != nil {
				return "", "", fmt.Errorf("update workspace member role projection: %w", err)
			}
		}
		if err := ensureAllChannelMembership(ctx, tx, workspaceID, memberID); err != nil {
			return "", "", err
		}
		if err := ensureServiceAccountAudienceMemberships(ctx, tx, workspaceID, memberID); err != nil {
			return "", "", err
		}
		return memberID, resolvedRole, nil
	} else if !coredb.IsNotFound(err) {
		return "", "", fmt.Errorf("load workspace member projection: %w", err)
	}

	userRow := tx.QueryRow(ctx, `
SELECT COALESCE(email, ''), display_name
FROM users
WHERE id = $1
`, userID)
	var email string
	var displayName string
	if err := userRow.Scan(&email, &displayName); err != nil {
		if coredb.IsNotFound(err) {
			return "", "", ErrNotFound
		}
		return "", "", fmt.Errorf("load projected user: %w", err)
	}

	memberID, err := identifiers.NewMemberID()
	if err != nil {
		return "", "", fmt.Errorf("generate projected member id: %w", err)
	}
	now := time.Now().UTC()
	resolvedDisplayName := strings.TrimSpace(displayName)
	if resolvedDisplayName == "" {
		resolvedDisplayName = displayNameFromEmail(email)
	}
	pinyinFull, pinyinInitials := pinyinsearch.Variants(resolvedDisplayName)
	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, status, created_at, updated_at)
VALUES ($1, $2, 'human', $3, $4, $5, $6, $7, 'active', $8, $8)
`, memberID, workspaceID, resolvedDisplayName, pinyinFull, pinyinInitials, userID, resolvedRole, now); err != nil {
		return "", "", fmt.Errorf("insert workspace member projection: %w", err)
	}
	if err := ensureAllChannelMembership(ctx, tx, workspaceID, memberID); err != nil {
		return "", "", err
	}
	if err := ensureServiceAccountAudienceMemberships(ctx, tx, workspaceID, memberID); err != nil {
		return "", "", err
	}
	return memberID, resolvedRole, nil
}

// RestoreHumanWorkspaceMemberBaselineInTx reactivates a projected human member with its baseline role.
func RestoreHumanWorkspaceMemberBaselineInTx(ctx context.Context, tx coredb.Tx, workspaceID string, userID string, baselineRole string, ensureAllChannelMembership EnsureAllChannelMembershipFunc, ensureServiceAccountAudienceMemberships EnsureServiceAccountAudienceMembershipsFunc) (string, error) {
	resolvedRole := strings.TrimSpace(baselineRole)
	if workspaceRoleRank(resolvedRole) == 0 {
		return "", badRequest("invalid baseline workspace role")
	}

	row := tx.QueryRow(ctx, `
SELECT id
FROM members
WHERE workspace_id = $1 AND user_id = $2 AND type = 'human'
`, workspaceID, userID)

	var memberID string
	if err := row.Scan(&memberID); err != nil {
		if coredb.IsNotFound(err) {
			return "", ErrNotFound
		}
		return "", fmt.Errorf("load workspace member baseline projection: %w", err)
	}

	if _, err := tx.Exec(ctx, `
UPDATE members
SET workspace_role = $3, status = 'active', updated_at = NOW()
WHERE workspace_id = $1 AND user_id = $2 AND type = 'human'
`, workspaceID, userID, resolvedRole); err != nil {
		return "", fmt.Errorf("restore workspace member baseline projection: %w", err)
	}
	if err := ensureAllChannelMembership(ctx, tx, workspaceID, memberID); err != nil {
		return "", err
	}
	if err := ensureServiceAccountAudienceMemberships(ctx, tx, workspaceID, memberID); err != nil {
		return "", err
	}
	return memberID, nil
}

// DeactivateHumanWorkspaceMemberInTx disables future workspace access for one human member projection.
func DeactivateHumanWorkspaceMemberInTx(ctx context.Context, tx coredb.Tx, workspaceID string, userID string, markMemberSpaceProjectionsRemoved MarkMemberSpaceProjectionsRemovedFunc, removePrivateEntriesForOwner RemovePrivateEntriesForOwnerFunc) (string, error) {
	row := tx.QueryRow(ctx, `
SELECT id
FROM members
WHERE workspace_id = $1 AND user_id = $2 AND type = 'human'
`, workspaceID, userID)

	var memberID string
	if err := row.Scan(&memberID); err != nil {
		if coredb.IsNotFound(err) {
			return "", ErrNotFound
		}
		return "", fmt.Errorf("load workspace member for deactivate: %w", err)
	}

	if _, err := tx.Exec(ctx, `
UPDATE members
SET status = 'inactive', updated_at = NOW()
WHERE workspace_id = $1 AND user_id = $2 AND type = 'human'
`, workspaceID, userID); err != nil {
		return "", fmt.Errorf("deactivate workspace member projection: %w", err)
	}
	now := time.Now().UTC()
	if err := markMemberSpaceProjectionsRemoved(ctx, tx, memberID, now); err != nil {
		return "", err
	}
	if err := removePrivateEntriesForOwner(ctx, tx, memberID, now); err != nil {
		return "", err
	}
	return memberID, nil
}

func workspaceRoleRank(role string) int {
	switch domain.WorkspaceRole(strings.TrimSpace(role)) {
	case domain.WorkspaceRoleOwner:
		return 3
	case domain.WorkspaceRoleAdmin:
		return 2
	case domain.WorkspaceRoleMember:
		return 1
	default:
		return 0
	}
}
