package content

import (
	"context"
	"errors"
	"fmt"
	"io"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/contentpayload"
)

// AuthorizedService is the only Content Store boundary wired into product
// APIs and domain consumers. The core Service remains the storage coordinator;
// this boundary resolves persisted facts and authorizes before delegating.
type AuthorizedService struct {
	core   *Service
	policy ContentAccessPolicy
}

func NewAuthorizedService(core *Service, policy ContentAccessPolicy) (*AuthorizedService, error) {
	if core == nil || policy == nil {
		return nil, fmt.Errorf("content store core and access policy are required")
	}
	return &AuthorizedService{core: core, policy: policy}, nil
}

func (service *AuthorizedService) CreateContent(ctx context.Context, request CreateContentRequest) (Content, error) {
	principal := principalFromContext(ctx, request.WorkspaceID)
	if strings.TrimSpace(request.Actor) == "" || strings.TrimSpace(request.Actor) != strings.TrimSpace(principal.Subject) {
		return Content{}, ErrAccessDenied
	}
	access := AccessRequest{
		Principal: principal, WorkspaceID: request.WorkspaceID,
		Domain: request.Domain, BindingKey: request.BindingKey, Generation: 1, Action: AccessCreateLineage,
	}
	if err := service.authorize(ctx, access); err != nil {
		return Content{}, err
	}
	return service.core.CreateContent(ctx, request)
}

func (service *AuthorizedService) FindContentByBinding(ctx context.Context, workspaceID, domain, bindingKey string) (Content, bool, error) {
	access := AccessRequest{
		Principal: principalFromContext(ctx, workspaceID), WorkspaceID: workspaceID,
		Domain: domain, BindingKey: bindingKey, Action: AccessReadMetadata,
	}
	if err := service.authorize(ctx, access); err != nil {
		return Content{}, false, err
	}
	return service.core.FindContentByBinding(ctx, workspaceID, domain, bindingKey)
}

func (service *AuthorizedService) GetContent(ctx context.Context, workspaceID, contentID string) (Content, error) {
	content, err := service.authorizeContentVersions(ctx, workspaceID, contentID, AccessReadMetadata)
	if err != nil {
		return Content{}, err
	}
	return content, nil
}

func (service *AuthorizedService) CommitVersion(ctx context.Context, request CommitVersionRequest) (CommitVersionResult, error) {
	if strings.TrimSpace(request.Actor) == "" || strings.TrimSpace(request.Actor) != strings.TrimSpace(principalFromContext(ctx, request.WorkspaceID).Subject) {
		return CommitVersionResult{}, ErrAccessDenied
	}
	if _, err := service.authorizeContentVersions(ctx, request.WorkspaceID, request.ContentID, AccessPublish); err != nil {
		return CommitVersionResult{}, err
	}
	return service.core.CommitVersion(ctx, request)
}

// PublishDetachedMigrationVersion is intentionally separate from ordinary
// publication authorization. Only an exact migration-scoped principal may
// reach the detached core primitive.
func (service *AuthorizedService) PublishDetachedMigrationVersion(ctx context.Context, request PublishDetachedMigrationVersionRequest) (CommitVersionResult, error) {
	if strings.TrimSpace(request.Actor) == "" || strings.TrimSpace(request.Actor) != strings.TrimSpace(principalFromContext(ctx, request.WorkspaceID).Subject) {
		return CommitVersionResult{}, ErrAccessDenied
	}
	versionIDs := make([]string, 0, 1)
	if parentVersionID := strings.TrimSpace(request.ParentVersionID); parentVersionID != "" {
		versionIDs = append(versionIDs, parentVersionID)
	}
	if _, err := service.authorizeContentVersions(ctx, request.WorkspaceID, request.ContentID, AccessPublishDetachedMigration, versionIDs...); err != nil {
		return CommitVersionResult{}, err
	}
	return service.core.publishDetachedMigrationVersion(ctx, request)
}

func (service *AuthorizedService) FindVersionOperation(ctx context.Context, workspaceID, contentID, operationKey string) (VersionOperationResult, bool, error) {
	if _, err := service.authorizeContentVersions(ctx, workspaceID, contentID, AccessPublish); err != nil {
		return VersionOperationResult{}, false, err
	}
	return service.core.FindVersionOperation(ctx, workspaceID, contentID, operationKey)
}

// FindDetachedMigrationVersionOperation resolves only receipts owned by the
// detached migration action, preserving the stronger authorization boundary.
func (service *AuthorizedService) FindDetachedMigrationVersionOperation(ctx context.Context, workspaceID, contentID, operationKey string) (VersionOperationResult, bool, error) {
	if _, err := service.authorizeContentVersions(ctx, workspaceID, contentID, AccessPublishDetachedMigration); err != nil {
		return VersionOperationResult{}, false, err
	}
	return service.core.FindVersionOperation(ctx, workspaceID, contentID, operationKey)
}

func (service *AuthorizedService) GetVersion(ctx context.Context, workspaceID, contentID, versionID string) (Version, error) {
	if _, err := service.authorizeContentVersions(ctx, workspaceID, contentID, AccessReadMetadata, versionID); err != nil {
		return Version{}, err
	}
	return service.core.GetVersion(ctx, workspaceID, contentID, versionID)
}

func (service *AuthorizedService) ListVersionPaths(ctx context.Context, workspaceID, contentID, versionID string, limit int) ([]Path, error) {
	if _, err := service.authorizeContentVersions(ctx, workspaceID, contentID, AccessReadPayload, versionID); err != nil {
		return nil, err
	}
	return service.core.ListVersionPaths(ctx, workspaceID, contentID, versionID, limit)
}

func (service *AuthorizedService) ListVersions(ctx context.Context, workspaceID, contentID string, limit int) ([]Version, error) {
	if _, err := service.authorizeContentVersions(ctx, workspaceID, contentID, AccessReadHistory); err != nil {
		return nil, err
	}
	return service.core.ListVersions(ctx, workspaceID, contentID, limit)
}

func (service *AuthorizedService) PathHistory(ctx context.Context, workspaceID, contentID, path string, limit int) ([]Path, error) {
	if _, err := service.authorizeContentVersions(ctx, workspaceID, contentID, AccessReadHistory); err != nil {
		return nil, err
	}
	return service.core.PathHistory(ctx, workspaceID, contentID, path, limit)
}

func (service *AuthorizedService) Compare(ctx context.Context, workspaceID, contentID, beforeVersionID, afterVersionID string) ([]CompareEntry, error) {
	if _, err := service.authorizeContentVersions(ctx, workspaceID, contentID, AccessCompare, beforeVersionID, afterVersionID); err != nil {
		return nil, err
	}
	return service.core.Compare(ctx, workspaceID, contentID, beforeVersionID, afterVersionID)
}

func (service *AuthorizedService) OpenFile(ctx context.Context, workspaceID, contentID, versionID, path string, byteRange *contentpayload.ByteRange) (io.ReadCloser, contentpayload.BlobMeta, error) {
	if _, err := service.authorizeContentVersions(ctx, workspaceID, contentID, AccessReadPayload, versionID); err != nil {
		return nil, contentpayload.BlobMeta{}, err
	}
	return service.core.OpenFile(ctx, workspaceID, contentID, versionID, path, byteRange)
}

func (service *AuthorizedService) MaterializeTree(ctx context.Context, workspaceID, contentID, versionID, target string, limits contentpayload.MaterializeLimits) error {
	if _, err := service.authorizeContentVersions(ctx, workspaceID, contentID, AccessReadPayload, versionID); err != nil {
		return err
	}
	return service.core.MaterializeTree(ctx, workspaceID, contentID, versionID, target, limits)
}

func (service *AuthorizedService) OpenTreeArchive(ctx context.Context, workspaceID, contentID, versionID string, limits ArchiveLimits) (io.ReadCloser, ArchiveMeta, error) {
	if _, err := service.authorizeContentVersions(ctx, workspaceID, contentID, AccessReadArchive, versionID); err != nil {
		return nil, ArchiveMeta{}, err
	}
	return service.core.OpenTreeArchive(ctx, workspaceID, contentID, versionID, limits)
}

func (service *AuthorizedService) CreatePin(ctx context.Context, request CreatePinRequest) (Pin, error) {
	if strings.TrimSpace(request.Actor) == "" || strings.TrimSpace(request.Actor) != strings.TrimSpace(principalFromContext(ctx, request.WorkspaceID).Subject) {
		return Pin{}, ErrAccessDenied
	}
	if _, err := service.authorizeContentVersions(ctx, request.WorkspaceID, request.ContentID, AccessManagePin, request.VersionID); err != nil {
		return Pin{}, err
	}
	return service.core.CreatePin(ctx, request)
}

func (service *AuthorizedService) ListPins(ctx context.Context, workspaceID, contentID string, limit int) ([]Pin, error) {
	if _, err := service.authorizeContentVersions(ctx, workspaceID, contentID, AccessManagePin); err != nil {
		return nil, err
	}
	return service.core.ListPins(ctx, workspaceID, contentID, limit)
}

func (service *AuthorizedService) DeletePin(ctx context.Context, workspaceID, contentID, pinID string) error {
	if _, err := service.authorizeContentVersions(ctx, workspaceID, contentID, AccessManagePin); err != nil {
		return err
	}
	return service.core.DeletePin(ctx, workspaceID, contentID, pinID)
}

func (service *AuthorizedService) authorizeContentVersions(ctx context.Context, workspaceID, contentID string, action AccessAction, versionIDs ...string) (Content, error) {
	content, err := service.core.GetContent(ctx, workspaceID, contentID)
	if err != nil {
		return Content{}, err
	}
	resolvedVersions := make([]string, 0, len(versionIDs))
	for _, versionID := range versionIDs {
		versionID = strings.TrimSpace(versionID)
		if versionID == "" {
			return Content{}, ErrAccessDenied
		}
		if _, err := service.core.GetVersion(ctx, workspaceID, content.ID, versionID); err != nil {
			return Content{}, err
		}
		resolvedVersions = append(resolvedVersions, versionID)
	}
	access := AccessRequest{
		Principal: principalFromContext(ctx, workspaceID), WorkspaceID: content.WorkspaceID,
		Domain: content.Domain, BindingKey: content.BindingKey, ContentID: content.ID,
		VersionIDs: resolvedVersions, Generation: content.Generation, Action: action,
	}
	if err := service.authorize(ctx, access); err != nil {
		return Content{}, err
	}
	return content, nil
}

func (service *AuthorizedService) authorize(ctx context.Context, request AccessRequest) error {
	if !knownAccessAction(request.Action) || strings.TrimSpace(request.Principal.Subject) == "" {
		return ErrAccessDenied
	}
	if err := service.policy.AuthorizeContentAccess(ctx, request); err != nil {
		if errors.Is(err, ErrAccessDenied) {
			return ErrAccessDenied
		}
		return fmt.Errorf("%w: %v", ErrPolicyUnavailable, err)
	}
	return nil
}
