package serviceactor

import (
	"context"
	"fmt"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/pinyinsearch"
)

// ServiceMemberSubstrateInsert describes the visible workspace member that
// backs one service actor.
type ServiceMemberSubstrateInsert struct {
	ID          string
	WorkspaceID string
	DisplayName string
	Now         time.Time
}

type workspaceMemberSubstrate struct {
	ID          string
	WorkspaceID string
	Type        string
	UserID      string
	Status      string
}

// InsertServiceMemberSubstrate creates the service-type member row required by
// service_members. Callers must keep this write in the same transaction as the
// installation and service-member projections.
func (store *TxStore) InsertServiceMemberSubstrate(ctx context.Context, params ServiceMemberSubstrateInsert) error {
	execer, err := execerFromTxStore(store)
	if err != nil {
		return err
	}
	memberID := trim(params.ID)
	workspaceID := trim(params.WorkspaceID)
	if memberID == "" || workspaceID == "" {
		return badRequest("service member substrate id and workspaceId are required")
	}
	now := params.Now.UTC()
	if now.IsZero() {
		now = time.Now().UTC()
	}
	displayName := trim(params.DisplayName)
	pinyinFull, pinyinInitials := pinyinsearch.Variants(displayName)
	if _, err := execer.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, 'service', $3, $4, $5, NULL, NULL, 'active', $6, $6)
`, memberID, workspaceID, displayName, pinyinFull, pinyinInitials, now); err != nil {
		return fmt.Errorf("insert service member substrate: %w", err)
	}
	return nil
}

// EnsureServiceMemberSubstrate creates or idempotently refreshes the visible
// service-type member that backs a built-in service actor. An existing row is
// reusable only when its immutable workspace/type identity matches.
func (store *TxStore) EnsureServiceMemberSubstrate(ctx context.Context, params ServiceMemberSubstrateInsert) error {
	execer, err := execerFromTxStore(store)
	if err != nil {
		return err
	}
	memberID := trim(params.ID)
	workspaceID := trim(params.WorkspaceID)
	if memberID == "" || workspaceID == "" {
		return badRequest("service member substrate id and workspaceId are required")
	}
	now := params.Now.UTC()
	if now.IsZero() {
		now = time.Now().UTC()
	}
	member, found, err := lockWorkspaceMemberSubstrate(ctx, execer, memberID)
	if err != nil {
		return err
	}
	if found {
		if member.WorkspaceID != workspaceID || member.Type != "service" || member.UserID != "" {
			return conflict("Service member substrate id is already bound to another identity.")
		}
		displayName := trim(params.DisplayName)
		pinyinFull, pinyinInitials := pinyinsearch.Variants(displayName)
		if _, err := execer.Exec(ctx, `
UPDATE members
SET display_name = $3,
    display_name_pinyin = $5,
    display_name_pinyin_initials = $6,
    status = 'active',
    updated_at = CASE
        WHEN display_name <> $3 OR status <> 'active' THEN $4
        ELSE updated_at
    END
WHERE id = $1
  AND workspace_id = $2
  AND type = 'service'
  AND user_id IS NULL
`, memberID, workspaceID, displayName, now, pinyinFull, pinyinInitials); err != nil {
			return fmt.Errorf("refresh service member substrate: %w", err)
		}
		return nil
	}
	return store.InsertServiceMemberSubstrate(ctx, params)
}

func lockWorkspaceMemberSubstrate(ctx context.Context, queryer coredb.QueryRower, memberID string) (workspaceMemberSubstrate, bool, error) {
	var member workspaceMemberSubstrate
	err := queryer.QueryRow(ctx, `
SELECT id, workspace_id, type, COALESCE(user_id, ''), status
FROM members
WHERE id = $1
FOR UPDATE
`, trim(memberID)).Scan(&member.ID, &member.WorkspaceID, &member.Type, &member.UserID, &member.Status)
	if err != nil {
		if coredb.IsNotFound(err) {
			return workspaceMemberSubstrate{}, false, nil
		}
		return workspaceMemberSubstrate{}, false, fmt.Errorf("lock workspace member substrate: %w", err)
	}
	member.ID = trim(member.ID)
	member.WorkspaceID = trim(member.WorkspaceID)
	member.Type = trim(member.Type)
	member.UserID = trim(member.UserID)
	member.Status = trim(member.Status)
	return member, true, nil
}

func validateInstallationCreatorSubstrate(ctx context.Context, queryer coredb.QueryRower, workspaceID string, memberID string, actorType string, actorID string) error {
	if memberID == "" {
		if actorType == "human" {
			return conflict("Human service installation creators require a workspace member attribution.")
		}
		return nil
	}
	member, found, err := lockWorkspaceMemberSubstrate(ctx, queryer, memberID)
	if err != nil {
		return err
	}
	if !found || member.WorkspaceID != workspaceID || member.Type != "human" {
		return conflict("Service installation creator must be a Human member of the installation workspace.")
	}
	if actorType == "human" && actorID != member.ID && actorID != member.UserID {
		return conflict("Human service installation actor does not match its workspace member attribution.")
	}
	return nil
}

func validateServiceMemberSubstrate(ctx context.Context, queryer coredb.QueryRower, workspaceID string, memberID string) error {
	member, found, err := lockWorkspaceMemberSubstrate(ctx, queryer, memberID)
	if err != nil {
		return err
	}
	if !found || member.WorkspaceID != workspaceID || member.Type != "service" || member.Status != StatusActive {
		return conflict("Service member substrate must be an active service-type member of the same workspace.")
	}
	return nil
}
