package webagent

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/forwarder"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
)

const (
	maxDuplicatedKnowledgeBases   = 100
	agentResourceCopyCleanupLimit = 15 * time.Second
)

type AgentResourceCopyIdentity struct {
	User            identity.User
	SessionID       string
	InternalPurpose string
}

func NewAgentResourceCopyIdentityForWeb(user identity.AuthenticatedUser) AgentResourceCopyIdentity {
	return AgentResourceCopyIdentity{User: user.User, SessionID: user.Session.ID}
}

func NewApprovedAgentManagementResourceCopyIdentity(user identity.User) AgentResourceCopyIdentity {
	return AgentResourceCopyIdentity{User: user, InternalPurpose: "approved_agent_management_operation"}
}

type AgentResourceCopyRequest struct {
	WorkspaceID        string
	Identity           AgentResourceCopyIdentity
	Source             AgentResourceCopyAgent
	Target             AgentResourceCopyAgent
	CopyKnowledgeBases bool
}

type AgentResourceCopyAgent struct {
	ID          string
	Name        string
	DisplayName string
}

type AgentResourceCopyResult struct {
	Skills         int
	KnowledgeBases int
	Memory         bool
}

type AgentResourceCopier interface {
	CopyAgentResources(ctx context.Context, request AgentResourceCopyRequest) (AgentResourceCopyResult, error)
}

type forwardedAgentResourceCopier struct {
	forwarder interface {
		ForwardJSON(context.Context, forwarder.JSONRequest, any) error
	}
}

func NewForwardedAgentResourceCopier(dependencies forwarder.Dependencies) (AgentResourceCopier, error) {
	handler, err := forwarder.NewHandler(dependencies)
	if err != nil {
		return nil, err
	}
	return &forwardedAgentResourceCopier{forwarder: handler}, nil
}

func (c *forwardedAgentResourceCopier) CopyAgentResources(ctx context.Context, request AgentResourceCopyRequest) (AgentResourceCopyResult, error) {
	if c == nil || c.forwarder == nil {
		return AgentResourceCopyResult{}, fmt.Errorf("agent resource copier is not configured")
	}
	workspaceID := strings.TrimSpace(request.WorkspaceID)
	sourceAgentID := strings.TrimSpace(request.Source.ID)
	targetAgentID := strings.TrimSpace(request.Target.ID)
	if workspaceID == "" || sourceAgentID == "" || targetAgentID == "" {
		return AgentResourceCopyResult{}, fmt.Errorf("workspaceId, source agent, and target agent are required")
	}

	var result AgentResourceCopyResult
	skills, err := c.copySkills(ctx, request)
	if err != nil {
		return AgentResourceCopyResult{}, err
	}
	result.Skills = skills
	if request.CopyKnowledgeBases {
		knowledgeBases, err := c.copyKnowledgeBases(ctx, request)
		if err != nil {
			return AgentResourceCopyResult{}, err
		}
		result.KnowledgeBases = knowledgeBases
	}
	memoryCopied, err := c.copyMemory(ctx, request)
	if err != nil {
		return AgentResourceCopyResult{}, err
	}
	result.Memory = memoryCopied
	return result, nil
}

func (c *forwardedAgentResourceCopier) copySkills(ctx context.Context, request AgentResourceCopyRequest) (int, error) {
	var bindings []struct {
		SkillID string `json:"skillId"`
		Branch  string `json:"branch"`
	}
	path := "/api/v1/skills/agents/" + url.PathEscape(strings.TrimSpace(request.Source.ID)) + "/bindings"
	if err := c.forward(ctx, request, http.MethodGet, path, nil, &bindings, 0); err != nil {
		return 0, fmt.Errorf("list source agent skill bindings: %w", err)
	}
	count := 0
	for _, binding := range bindings {
		skillID := strings.TrimSpace(binding.SkillID)
		if skillID == "" {
			continue
		}
		branch := strings.TrimSpace(binding.Branch)
		if branch == "" {
			branch = "main"
		}
		path := "/api/v1/skills/agents/" + url.PathEscape(strings.TrimSpace(request.Target.ID)) + "/bindings/" + url.PathEscape(skillID)
		if err := c.forward(ctx, request, http.MethodPut, path, map[string]any{"branch": branch}, nil, 0); err != nil {
			return count, fmt.Errorf("bind target agent skill %s: %w", skillID, err)
		}
		count++
	}
	return count, nil
}

func (c *forwardedAgentResourceCopier) copyKnowledgeBases(ctx context.Context, request AgentResourceCopyRequest) (int, error) {
	var items []struct {
		ID          string `json:"id"`
		AccessLevel string `json:"accessLevel"`
	}
	if err := c.forward(ctx, request, http.MethodGet, agentAccessibleKnowledgeBasesPath(request.Source.ID, 1, maxDuplicatedKnowledgeBases), nil, &items, 0); err != nil {
		return 0, fmt.Errorf("list source agent knowledge bases: %w", err)
	}
	if len(items) == maxDuplicatedKnowledgeBases {
		var overflow []struct {
			ID string `json:"id"`
		}
		if err := c.forward(ctx, request, http.MethodGet, agentAccessibleKnowledgeBasesPath(request.Source.ID, 2, 1), nil, &overflow, 0); err != nil {
			return 0, fmt.Errorf("check source agent knowledge base copy limit: %w", err)
		}
		if len(overflow) > 0 {
			return 0, fmt.Errorf("source agent has too many knowledge bases to copy")
		}
	}
	count := 0
	for _, item := range items {
		kbID := strings.TrimSpace(item.ID)
		if kbID == "" {
			continue
		}
		accessLevel := "read"
		if strings.TrimSpace(item.AccessLevel) == "write" {
			accessLevel = "write"
		}
		path := "/api/v1/kb/agents/" + url.PathEscape(strings.TrimSpace(request.Target.ID)) + "/knowledge-bases/" + url.PathEscape(kbID)
		if err := c.forward(ctx, request, http.MethodPut, path, map[string]any{"accessLevel": accessLevel}, nil, 0); err != nil {
			return count, fmt.Errorf("grant target agent knowledge base %s: %w", kbID, err)
		}
		count++
	}
	return count, nil
}

func agentAccessibleKnowledgeBasesPath(agentID string, page int, perPage int) string {
	return "/api/v1/kb/agents/" + url.PathEscape(strings.TrimSpace(agentID)) + "/accessible?page=" + strconv.Itoa(page) + "&per_page=" + strconv.Itoa(perPage)
}

func (c *forwardedAgentResourceCopier) copyMemory(ctx context.Context, request AgentResourceCopyRequest) (bool, error) {
	var memory struct {
		Enabled  bool   `json:"enabled"`
		MemoryID string `json:"memoryId"`
		Name     string `json:"name"`
		Branch   string `json:"branch"`
	}
	path := "/api/v1/memory/agents/" + url.PathEscape(strings.TrimSpace(request.Source.ID)) + "/cloud-memory"
	if err := c.forward(ctx, request, http.MethodGet, path, nil, &memory, 0); err != nil {
		return false, fmt.Errorf("load source agent memory: %w", err)
	}
	sourceMemoryID := strings.TrimSpace(memory.MemoryID)
	if !memory.Enabled || sourceMemoryID == "" {
		return false, nil
	}
	memoryName := strings.TrimSpace(memory.Name)
	if memoryName == "" {
		memoryName = strings.TrimSpace(request.Source.DisplayName)
	}
	if memoryName == "" {
		memoryName = strings.TrimSpace(request.Source.Name)
	}
	if memoryName == "" {
		memoryName = "Memory"
	}
	var forked struct {
		ID string `json:"id"`
	}
	if err := c.forward(ctx, request, http.MethodPost, "/api/v1/memory/"+url.PathEscape(sourceMemoryID)+"/fork", map[string]any{"name": memoryName + " copy"}, &forked, 0); err != nil {
		return false, fmt.Errorf("fork source agent memory: %w", err)
	}
	forkedMemoryID := strings.TrimSpace(forked.ID)
	if forkedMemoryID == "" {
		return false, fmt.Errorf("fork source agent memory: missing forked memory id")
	}
	branch := strings.TrimSpace(memory.Branch)
	if branch == "" {
		branch = "main"
	}
	body := map[string]any{
		"memoryId":                    forkedMemoryID,
		"branch":                      branch,
		"importExistingLocalContent":  false,
		"replaceLocalContentOnSwitch": false,
	}
	path = "/api/v1/memory/agents/" + url.PathEscape(strings.TrimSpace(request.Target.ID)) + "/binding"
	if err := c.forward(ctx, request, http.MethodPut, path, body, nil, 0); err != nil {
		if cleanupErr := c.deleteForkedMemory(ctx, request, forkedMemoryID); cleanupErr != nil {
			return false, fmt.Errorf("bind forked memory to target agent: %w; cleanup forked memory: %v", err, cleanupErr)
		}
		return false, fmt.Errorf("bind forked memory to target agent: %w", err)
	}
	return true, nil
}

func (c *forwardedAgentResourceCopier) deleteForkedMemory(ctx context.Context, request AgentResourceCopyRequest, memoryID string) error {
	cleanupCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), agentResourceCopyCleanupLimit)
	defer cancel()
	path := "/api/v1/memory/" + url.PathEscape(strings.TrimSpace(memoryID)) + "/permanent"
	return c.forward(cleanupCtx, request, http.MethodDelete, path, nil, nil, http.StatusNoContent)
}

func (c *forwardedAgentResourceCopier) forward(ctx context.Context, request AgentResourceCopyRequest, method string, path string, body any, response any, expectedStatus int) error {
	return c.forwarder.ForwardJSON(ctx, forwarder.JSONRequest{
		Method:                    method,
		Path:                      path,
		WorkspaceID:               strings.TrimSpace(request.WorkspaceID),
		Identity:                  trustedIdentityForResourceCopy(request.Identity),
		AgentOwnershipWorkspaceID: strings.TrimSpace(request.WorkspaceID),
		OwnedAgents: []forwarder.TrustedOwnedAgent{
			agentResourceCopyOwnedAgent(request.Source),
			agentResourceCopyOwnedAgent(request.Target),
		},
		Body:            body,
		ExpectedStatus:  expectedStatus,
		InternalPurpose: request.Identity.InternalPurpose,
	}, response)
}

func trustedIdentityForResourceCopy(identity AgentResourceCopyIdentity) forwarder.TrustedIdentity {
	return forwarder.TrustedIdentity{
		UserID:      identity.User.ID,
		AgentTeamUserID:  identity.User.ID,
		Email:       identity.User.Email,
		Name:        identity.User.Name,
		DisplayName: identity.User.DisplayName,
		AvatarURL:   identity.User.AvatarURL,
		SessionID:   identity.SessionID,
	}
}

func agentResourceCopyOwnedAgent(agent AgentResourceCopyAgent) forwarder.TrustedOwnedAgent {
	return forwarder.TrustedOwnedAgent{
		ID:          strings.TrimSpace(agent.ID),
		Name:        strings.TrimSpace(agent.Name),
		DisplayName: strings.TrimSpace(agent.DisplayName),
	}
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if trimmed := strings.TrimSpace(value); trimmed != "" {
			return trimmed
		}
	}
	return ""
}
