package app

import (
	"context"
	"crypto/sha1"
	"encoding/hex"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
)

const (
	roleDefinitionAssetType   = "role_definition_asset"
	rolePortableKnowledgeType = "portable_knowledge"
	rolePortableSkillType     = "skill"
)

func (store *Store) UpsertAgentRoleMaterialization(ctx context.Context, input agent.AgentRoleMaterializationInput) error {
	if strings.TrimSpace(input.UserID) == "" || strings.TrimSpace(input.WorkspaceID) == "" || strings.TrimSpace(input.AgentID) == "" {
		return badRequest("userId, workspaceId, and agentId are required")
	}
	if strings.TrimSpace(input.RoleID) == "" {
		return badRequest("roleId is required")
	}
	if strings.TrimSpace(input.RoleVersionID) == "" {
		return badRequest("roleVersionId is required")
	}
	if _, err := store.requireAgentOperation(ctx, input.UserID, input.WorkspaceID, input.AgentID); err != nil {
		return err
	}

	tx, err := store.db.BeginTx(ctx, coredb.TxOptions{})
	if err != nil {
		return fmt.Errorf("begin agent role materialization tx: %w", err)
	}
	defer rollback(tx, ctx)

	if err := upsertAgentRoleBindingProjection(ctx, tx, input, time.Now().UTC()); err != nil {
		return err
	}
	if err := replaceAgentRoleManifestProjection(ctx, tx, input); err != nil {
		return err
	}
	if err := tx.Commit(ctx); err != nil {
		return fmt.Errorf("commit agent role materialization: %w", err)
	}
	return nil
}

func upsertAgentRoleBindingProjection(ctx context.Context, tx coredb.Tx, input agent.AgentRoleMaterializationInput, now time.Time) error {
	bindingID := strings.TrimSpace(input.BindingID)
	if bindingID == "" {
		var err error
		bindingID, err = identifiers.NewEntityID("arb")
		if err != nil {
			return fmt.Errorf("generate agent role binding id: %w", err)
		}
	}
	bindingKind := strings.TrimSpace(input.Source.BindingKind)
	if bindingKind == "" {
		bindingKind = "my_role"
	}
	activeDefinitionPath := agent.CloudMemoryIdentityPath
	sourcePresetRoleID := firstNonEmpty(input.Source.SourcePresetRoleID, input.Source.PresetID)
	sourcePresetVersionID := input.Source.SourcePresetVersionID
	_, err := tx.Exec(ctx, `
INSERT INTO agent_role_bindings (
    id, workspace_id, agent_id, binding_kind, role_id, role_version_id,
    source_preset_role_id, source_preset_version_id, active_definition_path,
    realization_id, created_by, updated_by, created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $11, $12, $12)
ON DUPLICATE KEY UPDATE
    binding_kind = VALUES(binding_kind),
    role_id = VALUES(role_id),
    role_version_id = VALUES(role_version_id),
    source_preset_role_id = VALUES(source_preset_role_id),
    source_preset_version_id = VALUES(source_preset_version_id),
    active_definition_path = VALUES(active_definition_path),
    realization_id = VALUES(realization_id),
    updated_by = VALUES(updated_by),
    updated_at = VALUES(updated_at)
`, bindingID, strings.TrimSpace(input.WorkspaceID), strings.TrimSpace(input.AgentID), bindingKind, strings.TrimSpace(input.RoleID), strings.TrimSpace(input.RoleVersionID), sourcePresetRoleID, sourcePresetVersionID, activeDefinitionPath, strings.TrimSpace(input.RealizationID), strings.TrimSpace(input.UserID), now)
	if err != nil {
		return fmt.Errorf("upsert agent role binding projection: %w", err)
	}
	return nil
}

func replaceAgentRoleManifestProjection(ctx context.Context, tx coredb.Tx, input agent.AgentRoleMaterializationInput) error {
	roleVersionID := strings.TrimSpace(input.RoleVersionID)
	if _, err := tx.Exec(ctx, `DELETE FROM role_package_manifest_items WHERE role_version_id = $1`, roleVersionID); err != nil {
		return fmt.Errorf("delete role package manifest projection: %w", err)
	}
	sortOrder := 0
	for _, item := range input.ManifestItems {
		itemType := normalizeRoleManifestItemType(item.Type)
		if !portableRoleManifestItemType(itemType) {
			continue
		}
		if strings.TrimSpace(item.Content) == "" {
			continue
		}
		itemID := roleManifestProjectionItemID(roleVersionID, sortOrder, itemType, item.Name)
		if _, err := tx.Exec(ctx, `
INSERT INTO role_package_manifest_items (
    id, role_version_id, role_id, workspace_id, item_type, name, description,
    required, realization_action, source_kind, source_resource_id, source_ref,
    content_ref, content_hash, access_level, branch, content, sort_order, metadata_json
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 'agentteam_projection', $10, '', '', '', $11, $12, $13, $14, '{}')
ON DUPLICATE KEY UPDATE
    role_id = VALUES(role_id),
    workspace_id = VALUES(workspace_id),
    item_type = VALUES(item_type),
    name = VALUES(name),
    description = VALUES(description),
    required = VALUES(required),
    realization_action = VALUES(realization_action),
    source_kind = VALUES(source_kind),
    source_resource_id = VALUES(source_resource_id),
    source_ref = VALUES(source_ref),
    content_ref = VALUES(content_ref),
    content_hash = VALUES(content_hash),
    access_level = VALUES(access_level),
    branch = VALUES(branch),
    content = VALUES(content),
    sort_order = VALUES(sort_order),
    metadata_json = VALUES(metadata_json)
`, itemID, roleVersionID, strings.TrimSpace(input.RoleID), strings.TrimSpace(input.WorkspaceID), itemType, strings.TrimSpace(item.Name), strings.TrimSpace(item.Description), item.Required, strings.TrimSpace(item.Action), strings.TrimSpace(item.ResourceID), strings.TrimSpace(item.AccessLevel), strings.TrimSpace(item.Branch), item.Content, sortOrder); err != nil {
			return fmt.Errorf("insert role package manifest projection: %w", err)
		}
		sortOrder++
	}
	return nil
}

// roleManifestProjectionItemID keeps role-version projection rewrites idempotent
// across concurrent materializations of the same immutable role version.
func roleManifestProjectionItemID(roleVersionID string, sortOrder int, itemType string, name string) string {
	hash := sha1.New()
	for _, part := range []string{
		strings.TrimSpace(roleVersionID),
		strconv.Itoa(sortOrder),
		strings.TrimSpace(itemType),
		strings.TrimSpace(name),
	} {
		hash.Write([]byte{0})
		hash.Write([]byte(part))
	}
	return "rpmi_" + hex.EncodeToString(hash.Sum(nil))[:24]
}

func normalizeRoleManifestItemType(value string) string {
	switch strings.TrimSpace(value) {
	case "", "role_definition":
		return roleDefinitionAssetType
	default:
		return strings.TrimSpace(value)
	}
}

func portableRoleManifestItemType(value string) bool {
	switch strings.TrimSpace(value) {
	case roleDefinitionAssetType, rolePortableKnowledgeType, rolePortableSkillType:
		return true
	default:
		return false
	}
}
