package server

import (
	"context"
	"fmt"
	"sync"

	"github.com/tidbcloud/AgentTeam/internal/extension/providers/mail/mailruntime"
	"github.com/tidbcloud/AgentTeam/internal/service/extensionflow"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/app"
	"github.com/tidbcloud/AgentTeam/internal/store/attachment"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

// mailAttachmentAuthority is the trusted bridge between the Mail runtime and
// the AgentTeam attachment authority. Both directions re-authorize through the
// member-scoped store layer; the runtime never receives raw paths or blobs
// from Action input.
type mailAttachmentAuthority struct {
	attachments *attachment.Store
	app         *app.Store
}

var _ interface {
	mailruntime.AttachmentLoader
	mailruntime.AttachmentMaterializer
} = mailAttachmentAuthority{}

// LoadAgentAttachment resolves one AgentTeam attachment the invoking agent member
// can already read. Authorization follows the attachment's message-read
// authority; cross-workspace or unbound IDs are rejected by the store.
func (authority mailAttachmentAuthority) LoadAgentAttachment(
	ctx context.Context,
	workspaceID string,
	agentMemberID string,
	attachmentID string,
) (mailruntime.LoadedAttachment, error) {
	download, err := authority.attachments.LoadAttachmentForMember(
		ctx,
		agentMemberID,
		workspaceID,
		attachmentID,
	)
	if err != nil {
		return mailruntime.LoadedAttachment{}, err
	}
	return mailruntime.LoadedAttachment{
		Filename:  download.Filename,
		MediaType: download.ContentType,
		Blob:      download.Blob,
	}, nil
}

// MaterializeAgentAttachment uploads one bounded inbound mail attachment into
// the single conversation target resolved from the untrusted invocation
// context hint. Both the target resolution and the scope upload authorize the
// agent member server-side and fail closed.
func (authority mailAttachmentAuthority) MaterializeAgentAttachment(
	ctx context.Context,
	workspaceID string,
	agentID string,
	agentMemberID string,
	target string,
	upload mailruntime.AttachmentUpload,
) (mailruntime.MaterializedAttachment, error) {
	scopeID, err := authority.app.ResolveWritableAgentTargetScopeID(
		ctx,
		agent.AuthenticatedAgent{
			AgentID:     agentID,
			WorkspaceID: workspaceID,
			MemberID:    agentMemberID,
		},
		target,
	)
	if err != nil {
		return mailruntime.MaterializedAttachment{}, err
	}
	created, err := authority.attachments.UploadAttachmentsForMember(
		ctx,
		agentMemberID,
		workspaceID,
		scopeID,
		[]attachment.CreateAttachmentParams{{
			Filename:    upload.Filename,
			ContentType: upload.MediaType,
			Blob:        upload.Blob,
		}},
	)
	if err != nil {
		return mailruntime.MaterializedAttachment{}, err
	}
	if len(created) != 1 {
		return mailruntime.MaterializedAttachment{}, fmt.Errorf(
			"materialize mail attachment: expected one attachment",
		)
	}
	return mailruntime.MaterializedAttachment{
		AttachmentID: created[0].ID,
		Filename:     created[0].Filename,
		MediaType:    created[0].ContentType,
		SizeBytes:    created[0].SizeBytes,
	}, nil
}

// mailInvocationAuthorizer is the default-deny late-bound holder for the Mail
// fresh-authorization fence. The extensionflow catalog service is constructed
// after the Mail runtime in the wiring order, so the holder starts unbound and
// every mutation stays denied until a profile that owns the authoritative
// resolver binds it.
type mailInvocationAuthorizer struct {
	mu       sync.RWMutex
	resolver *extensionflow.Service
}

// Bind attaches the authoritative generic resolver. Only call this once the
// extensionflow Service with the full registry is available.
func (authorizer *mailInvocationAuthorizer) Bind(
	resolver *extensionflow.Service,
) {
	authorizer.mu.Lock()
	defer authorizer.mu.Unlock()
	authorizer.resolver = resolver
}

func (authorizer *mailInvocationAuthorizer) ReauthorizeInvocation(
	ctx context.Context,
	authorization mailruntime.InvocationAuthorization,
) error {
	authorizer.mu.RLock()
	resolver := authorizer.resolver
	authorizer.mu.RUnlock()
	if resolver == nil {
		return storeerr.Forbidden(
			"mail fresh authorization resolver is not bound",
		)
	}
	return resolver.ReauthorizeBuiltin(ctx, extensionflow.BuiltinReauthorization{
		WorkspaceID:                    authorization.WorkspaceID,
		AgentID:                        authorization.AgentID,
		AgentMemberID:                  authorization.AgentMemberID,
		AuthorizedByMemberID:           authorization.AuthorizedByMemberID,
		ServiceInstallationID:          authorization.InstallationID,
		ServiceConnectionID:            authorization.ConnectionID,
		ServiceConnectionGeneration:    authorization.ConnectionGeneration,
		ServiceInstallationExtensionID: authorization.SelectionID,
		ExtensionName:                  authorization.ExtensionName,
		Action:                         authorization.Action,
		DefinitionVersion:              authorization.DefinitionVersion,
		DefinitionDigest:               authorization.DefinitionDigest,
		CatalogVersion:                 authorization.CatalogVersion,
	})
}
